├── .gitignore
├── config.ru
├── public
├── bg1.png
├── bg2.png
├── bg3.png
├── bg4.png
├── bg5.png
├── tab.png
├── win.mp3
├── assets.zip
├── back.png
├── fail.mp3
├── jewels.png
├── levels.png
├── logo.png
├── menu.png
├── pipes.png
├── player.png
├── sizzle.mp3
├── tile.png
├── turn1.mp3
├── turn2.mp3
├── turn3.mp3
├── turn4.mp3
├── bubbles.mp3
├── sparkle.mp3
├── star_med.png
├── button_flow.png
├── button_play.png
├── button_time.png
├── jewel_med.png
├── label_time.png
├── titlecard.png
├── audio_buttons.png
├── clear_buttons.png
├── clear_screen.png
├── jewels_large.png
├── select_level.png
├── button_adventure.png
├── label_adventure.png
├── index.html
└── app.css
├── src
├── states
│ ├── boot.coffee
│ ├── levels.coffee
│ ├── play.coffee
│ ├── load.coffee
│ └── menu.coffee
├── init.js
├── levels
│ ├── l021.js
│ ├── l001.js
│ ├── l017.js
│ ├── l000.js
│ ├── l004.js
│ ├── l005.js
│ ├── l009.js
│ ├── l002.js
│ ├── l006.js
│ ├── l012.js
│ ├── l011.js
│ ├── l013.js
│ ├── l015.js
│ ├── l018.js
│ ├── l010.js
│ ├── l003.js
│ ├── l007.js
│ ├── l008.js
│ ├── l016.js
│ ├── l019.js
│ └── l014.js
├── components
│ ├── button_back.coffee
│ ├── release_button.coffee
│ ├── button_sound.coffee
│ ├── button_music.coffee
│ ├── count_star.coffee
│ ├── button_tab.coffee
│ ├── count_gem.coffee
│ ├── jewel.coffee
│ ├── pipes
│ │ ├── start.coffee
│ │ ├── end.coffee
│ │ ├── straight.coffee
│ │ ├── corner.coffee
│ │ ├── cross.coffee
│ │ └── double_corner.coffee
│ ├── spill.coffee
│ ├── counter.coffee
│ ├── godmode.coffee
│ ├── editor
│ │ ├── pipe_button.coffee
│ │ └── toolbar.coffee
│ ├── clear_screen.coffee
│ ├── pipe.coffee
│ ├── level.coffee
│ └── grid.coffee
├── game.coffee
├── data.coffee
└── lib
│ ├── store.js
│ ├── lz.js
│ └── mithril.js
├── Gemfile
├── package.json
├── README.md
├── Gemfile.lock
├── server.rb
└── gulpfile.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /files
2 | /node_modules
3 | /**/.DS_Store
4 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | require 'rack-livereload'
2 | use Rack::LiveReload
3 |
--------------------------------------------------------------------------------
/public/bg1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/bg1.png
--------------------------------------------------------------------------------
/public/bg2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/bg2.png
--------------------------------------------------------------------------------
/public/bg3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/bg3.png
--------------------------------------------------------------------------------
/public/bg4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/bg4.png
--------------------------------------------------------------------------------
/public/bg5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/bg5.png
--------------------------------------------------------------------------------
/public/tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/tab.png
--------------------------------------------------------------------------------
/public/win.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/win.mp3
--------------------------------------------------------------------------------
/public/assets.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/assets.zip
--------------------------------------------------------------------------------
/public/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/back.png
--------------------------------------------------------------------------------
/public/fail.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/fail.mp3
--------------------------------------------------------------------------------
/public/jewels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/jewels.png
--------------------------------------------------------------------------------
/public/levels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/levels.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/logo.png
--------------------------------------------------------------------------------
/public/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/menu.png
--------------------------------------------------------------------------------
/public/pipes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/pipes.png
--------------------------------------------------------------------------------
/public/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/player.png
--------------------------------------------------------------------------------
/public/sizzle.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/sizzle.mp3
--------------------------------------------------------------------------------
/public/tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/tile.png
--------------------------------------------------------------------------------
/public/turn1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/turn1.mp3
--------------------------------------------------------------------------------
/public/turn2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/turn2.mp3
--------------------------------------------------------------------------------
/public/turn3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/turn3.mp3
--------------------------------------------------------------------------------
/public/turn4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/turn4.mp3
--------------------------------------------------------------------------------
/public/bubbles.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/bubbles.mp3
--------------------------------------------------------------------------------
/public/sparkle.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/sparkle.mp3
--------------------------------------------------------------------------------
/public/star_med.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/star_med.png
--------------------------------------------------------------------------------
/public/button_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/button_flow.png
--------------------------------------------------------------------------------
/public/button_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/button_play.png
--------------------------------------------------------------------------------
/public/button_time.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/button_time.png
--------------------------------------------------------------------------------
/public/jewel_med.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/jewel_med.png
--------------------------------------------------------------------------------
/public/label_time.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/label_time.png
--------------------------------------------------------------------------------
/public/titlecard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/titlecard.png
--------------------------------------------------------------------------------
/public/audio_buttons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/audio_buttons.png
--------------------------------------------------------------------------------
/public/clear_buttons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/clear_buttons.png
--------------------------------------------------------------------------------
/public/clear_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/clear_screen.png
--------------------------------------------------------------------------------
/public/jewels_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/jewels_large.png
--------------------------------------------------------------------------------
/public/select_level.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/select_level.png
--------------------------------------------------------------------------------
/public/button_adventure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/button_adventure.png
--------------------------------------------------------------------------------
/public/label_adventure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omenking/pipezania/HEAD/public/label_adventure.png
--------------------------------------------------------------------------------
/src/states/boot.coffee:
--------------------------------------------------------------------------------
1 | _states.boot =
2 | create: ->
3 | window.game.stage.disableVisibilityChange = true
4 | window.game.state.start 'load'
5 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # If you have OpenSSL installed, we recommend updating
2 | # the following line to use "https"
3 | source 'http://rubygems.org'
4 |
5 | gem 'thin'
6 | gem 'sinatra'
7 | gem 'pry'
8 | gem 'rack-livereload'
9 |
--------------------------------------------------------------------------------
/src/init.js:
--------------------------------------------------------------------------------
1 | window.editor = false
2 |
3 | window.game = null
4 | window._states = {}
5 | window.Component = {Editor: {}}
6 | window._o = {}
7 | window._l = []
8 | window._i = []
9 | window._d = null
10 |
--------------------------------------------------------------------------------
/src/levels/l021.js:
--------------------------------------------------------------------------------
1 | _l[21] = {countdown: 10, speed: 5, pipes: [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,'4110','2110','2110','2110','2110','5330',null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}
--------------------------------------------------------------------------------
/src/components/button_back.coffee:
--------------------------------------------------------------------------------
1 | class window.Component.ButtonBack
2 | constructor:->
3 | create:=>
4 | @sprite = game.add.sprite 0, 0, 'back_button'
5 | @sprite.anchor.setTo 0.5, 0.5
6 | @sprite.y = 64
7 | @sprite.x = 96
8 | @sprite.inputEnabled = true
9 | @sprite.events.onInputDown.add @onclick, this
10 | onclick:=>
11 | game.state.start 'menu'
12 |
--------------------------------------------------------------------------------
/src/game.coffee:
--------------------------------------------------------------------------------
1 | window._d = new Data()
2 | window.game = new Phaser.Game 768, 576, Phaser.AUTO, 'phaser-example'
3 | window.godmode = new Component.GodMode()
4 | game.state.add 'boot' , _states.boot
5 | game.state.add 'load' , _states.load
6 | game.state.add 'menu' , _states.menu
7 | game.state.add 'levels', _states.levels
8 | game.state.add 'play' , _states.play
9 |
10 | game.state.start 'boot'
11 |
--------------------------------------------------------------------------------
/src/components/release_button.coffee:
--------------------------------------------------------------------------------
1 | class Component.ReleaseButton
2 | constructor:(args)->
3 | @btn = null
4 | create:(onclick)=>
5 | x = 16 #game.world.width / 2
6 | y = (game.world.height - 32)
7 | @btn = game.add.sprite x, y, 'button_flow'
8 | @btn.inputEnabled = true
9 | @btn.events.onInputDown.add onclick, this
10 | @btn.frame = 0
11 | @btn.anchor.setTo 0, 0.5
12 | flow:=>
13 | @btn.frame = 1
14 |
--------------------------------------------------------------------------------
/src/components/button_sound.coffee:
--------------------------------------------------------------------------------
1 | class window.Component.ButtonSound
2 | constructor:->
3 | create:=>
4 | @sprite = game.add.sprite 680, 512, 'audio_buttons'
5 | @sprite.frame = if _d.get_sound() then 0 else 1
6 | @sprite.anchor.setTo 0.5, 0.5
7 | @sprite.inputEnabled = true
8 | @sprite.events.onInputDown.add @onclick, this
9 | onclick:=>
10 | if @sprite.frame is 0
11 | @sprite.frame = 1
12 | _d.set_sound false
13 | else
14 | @sprite.frame = 0
15 | _d.set_sound true
16 |
--------------------------------------------------------------------------------
/src/components/button_music.coffee:
--------------------------------------------------------------------------------
1 | class window.Component.ButtonMusic
2 | constructor:->
3 | create:=>
4 | @spirte = game.add.sprite 620, 512, 'audio_buttons'
5 | @spirte.frame = if _d.get_music() then 2 else 3
6 | @spirte.anchor.setTo 0.5, 0.5
7 | @spirte.inputEnabled = true
8 | @spirte.events.onInputDown.add @onclick, this
9 | onclick:=>
10 | if @sprite.frame is 2
11 | @sprite.frame = 3
12 | _d.set_music false
13 | else
14 | @sprite.frame = 2
15 | _d.set_music true
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pipezania",
3 | "version": "0.0.1",
4 | "description": "Pipe Game",
5 | "main": "gulpfile.js",
6 | "dependencies": {
7 | "gulp-browserify": "^0.5.1",
8 | "gulp": "^3.9.0",
9 | "gulp-coffee": "^2.3.1",
10 | "gulp-concat": "^2.6.0",
11 | "gulp-watch": "^4.3.5"
12 | },
13 | "devDependencies": {
14 | "coffee-script": "^1.10.0",
15 | "gulp-sass-bulk-import": "^0.3.2"
16 | },
17 | "keywords": [
18 | "surfing"
19 | ],
20 | "author": "Ham Steak Games",
21 | "license": "MIT",
22 | }
23 |
--------------------------------------------------------------------------------
/src/levels/l001.js:
--------------------------------------------------------------------------------
1 | _l[1] = {countdown: 60, speed: 5, pipes: [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,'0230','0140','0230','0140',null,null,null,null,null,null,null,'0430','1210','1210','1310','1210','0342',null,null,null,null,null,null,'0120','3213','3110','3110','3110','0210',null,null,null,null,null,'4110','2410','1410','3110','3110','1310','2210','5330',null,null,null,null,null,'0330','3110','3110','3110','3210','0440',null,null,null,null,null,null,'0320','1210','1310','1210','1310','0410',null,null,null,null,null,null,null,'0120','0311','0420','0210',null,null,null,null]}
2 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pipezania
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/levels/l017.js:
--------------------------------------------------------------------------------
1 | _l[17] = {countdown: 10, speed: 5, pipes: [null,null,null,null,null,null,null,null,null,null,'4220',null,null,null,null,'1330','1220','0330','1210','1110','1440','1110','0210',null,null,null,'1330','1430','1320','1220','1110','1220','1320','0410','1440',null,null,'1330','1432','1330','1430','1320','1431','1320','1220','1140',null,null,null,'1140','1210','1440','1140','1440','1110','1210','1110',null,null,null,null,'1210','1320','1240','1240','1110','1210','1110',null,null,null,null,'1110','2320','1440','1133','1440','1210',null,null,null,null,null,null,null,'5440','1140','1210',null,null,null,null,null,null,null,null]}
--------------------------------------------------------------------------------
/src/levels/l000.js:
--------------------------------------------------------------------------------
1 | _l[0] = {countdown: 60, speed: 5, pipes: [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,'0330','2110','2410','2310','2110','0440',null,null,null,'4110','2110','2430','3110','2312','2310','2430','0330','1230','1110',null,null,'0430','2410','2210','3110','2111','2310','2210','0210','2320','1110',null,null,'0120','2310','2210','3110','2310','2410','2410','0340','2320','1110',null,null,'5110','2130','2230','3110','2113','2330','2410','0220','1340','1110',null,null,null,null,null,'0120','2210','2310','2310','2110','0110',null,null,null,null,null,null,null,null,null,null,null,null,null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l004.js:
--------------------------------------------------------------------------------
1 | _l[4] = {countdown: 50, speed: 5, pipes: [null,null,'0130','2330','2330','2330','0240',null,null,null,null,null,null,'0332','3220','0240',null,null,'2320',null,null,'4220',null,null,null,'2140','2340','2340','0230','2110','3220','0240','0130','0310',null,null,'0130','1310','1110','1330','0310',null,'2320','2320','2320',null,null,null,'2440','1140','1410','3220','5330','0430','1130','3220','0310',null,null,null,'2440','2340','1340','0310',null,'0420','3320','3220','0240',null,null,null,'0423','1220','0310',null,null,null,'0121','3220','0410',null,null,null,null,'0320','2430','2330','2430','2330','2430','0310',null,null,null,null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l005.js:
--------------------------------------------------------------------------------
1 | _l[5] = {countdown: 20, speed: 5, pipes: [null,null,null,null,null,null,null,null,null,null,'0230','4330',null,null,null,null,'1410','1310','1310',null,null,null,'2320',null,null,'1110','1310','1411','3110','3110','3110','1310',null,null,'2320',null,'1210','3110','3210','1210','3110','1110','3110','1210','3210','3110','0110',null,'1212','3110','3110','1210','3110','3110','3110','1410','3110','3110','1210',null,'1310','3110','3110','3110','3113','1110','3110','3110','3110','3110','1110',null,null,'1210','1110','1110','3110','3110','3110','1110','1110','1110','1110',null,'5110','2330','2430','2330','0310','1210','1110','1110',null,null,null,null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l009.js:
--------------------------------------------------------------------------------
1 | _l[9] = {countdown: 15, speed: 5, pipes: [null,null,null,null,null,null,'0230','2110','2111','0140',null,null,'0230','0140','0330','2110','2210','2310','3110','2210','2110','1440','0140',null,'2340','0320','1430','2110','0240','4220','2140','0330','0340','2320','2320',null,'0220','0340','2220','5220','2140','0120','0210','2140','2140','2220','2220',null,'0230','1430','1210','3110','3110','2210','2210','1440','3110','0110','2320',null,'2142','0420','1220','1120','3110','2230','0340','2140','0120','2330','0110',null,'2140','0130','1220','1320','1210','2230','3110','0210',null,null,null,null,'0320','0410','0320','0410','0320','2313','0410',null,null,null,null,null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l002.js:
--------------------------------------------------------------------------------
1 | _l[2] = {countdown: 40, speed: 5, pipes: [null,'0230','0440',null,null,null,null,'0130','0240','2110','0240',null,null,'2140','1240','2110','0140',null,null,'2140','2240','1430','3220','0243',null,'1130','2210','2210','3220','0440','0130','0310','0420','3320','3220','1430',null,'2320',null,'0330','1440','4440','5440','1440','2140','2440','2320','2140',null,'2220','0130','0241','2320','0230','2430','2430','1220','3220','1130','0310','0430','3220','3220','3220','1440','3220','2330','2430','1320','0112','2140',null,'2220','0320','0410','2440','2220','0320','2210','0240','2140',null,'2440',null,'0220','2330','2330','0310','0320','2110','2110','0310','0420','2110','0310',null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l006.js:
--------------------------------------------------------------------------------
1 | _l[6] = {countdown: 20, speed: 5, pipes: [null,null,null,null,null,'0340','2340','1110','3112','0140',null,null,'0310','2330','2340','2310','0420','1110','2340','3110','3110','1210','2120','0420','2230','0110','2110','2120','3110','1210','2210','3110','3110','3110','1230','2440','2230','2440','4110','2220','3110','1210','2120','3110','3110','1210','0210','2110','0220','3110','0420',null,'2220','2310','1220','3110','1220','2330',null,'2410','0340','1110','3110','2121','3110','1240','2340','3110','3110','1110','2340','0110','2230','0230','0130',null,'2230','0140','2440','3110','3113','1230',null,null,'0230','2110','2110','2120','0140',null,null,'5440','0230','0140',null,null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l012.js:
--------------------------------------------------------------------------------
1 | _l[12] = {countdown: 10, speed: 5, pipes: ['0430','0140',null,'0230','0140','5220','0230','2330','1140','0440',null,null,'2220','0220','2430','0110','0420','1210','3110','2210','1220','3113','0140',null,'0120','2330','0440','0330','2330','3110','1430','2210','3110','3110','3110','0240',null,'0330','3110','3110','2110','3110','3110','2110','3110','3110','0110','2220','2110','3110','3110','0120','2430','3110','3210','2230','3110','1322','2110','3110','2330','1140','3110','2330','2330','3110','3110','3110','0410','2440',null,'2220',null,'0220','1140','2111','2210','3110','1430','2210','2210','1320','2110','0110',null,null,'2440',null,null,'4440','2440',null,null,'2440',null,null]}
--------------------------------------------------------------------------------
/src/levels/l011.js:
--------------------------------------------------------------------------------
1 | _l[11] = {countdown: 20, speed: 5, pipes: [null,'0230','2330','2430','0440',null,'0230','2330','0140',null,'0440',null,null,'2220','5110','0140','2440','0330','1330','0440','3110','1110','3110','2110',null,'1220','0440','2440','3110','1330','1110','3110','1223','0140','2220',null,'0430','2210','1110','1440','1330','3110','3110','1220','3110','3110','1110',null,'2440','0430','3110','0240','2140','0121','3110','1220','3110','1220','2220',null,'0120','3110','1440','3110','0110','4110','0310','0420','0210','2320','3110','1110',null,'0322','3110','1320','2430','0140','2220','0110','1110','3110','0210',null,null,null,'0320','0410',null,'0220','3110','2330','0110','0220','0440',null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l013.js:
--------------------------------------------------------------------------------
1 | _l[13] = {countdown: 30, speed: 5, pipes: [null,null,'0230','0140','2320','0430','0140','0430','0140',null,'2440',null,null,'0430','1210','3110','3110','3110','1320','1430','1140','0340','3110','2330',null,'2320','0420','0210','3110','0420','1120','3110','1210','2440','2440',null,'0430','1220','0440','4110','0140',null,'1320','1210','1333','1440','0140',null,'0120','1220','3110','2110','0210','0430','1220','5220','0320','3110','1440','0340','1330','1331','1330','1110','2210','3110','1440','1210','2130','3110','1220','0210','0110','2440','2440','2220','0330','0110','0320','3110','2210','0410','0320','2330','0330','1330','2440','0322','0410',null,null,'2220',null,null,'2220',null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l015.js:
--------------------------------------------------------------------------------
1 | _l[15] = {countdown: 30, speed: 5, pipes: [null,'0330','2430','3110','2430','1440','3110','0440','2320',null,null,null,null,'1330','2430','3110','2430','2130','1140','3110','3110','2430','2330','2430','2330','2140','0131','1220','0140','4220','2220','0320','3110','2330','0440',null,null,'1320','3210','1210','1220','1440','0210','0130','3110','2430','3110','2330','2110','1310','3110','3110','0410','1140','2330','1140','3110','0440','2220',null,'2330','0223','3110','3110','2130','0142','5220','2320','2220','0220','3110','2430',null,'2420','2240','2440','0330','1110','1410','0110','2220','0430','0110',null,'2330','0110','0320','1330','0320','1130','0220','2110','0110','2440',null,null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l018.js:
--------------------------------------------------------------------------------
1 | _l[18] = {countdown: 10, speed: 5, pipes: ['0440','0330','2220','3110','2430','3110','0440',null,'2220','2440',null,null,'3110','3110','1110','2220','0230','3110','1140','2330','1320','0110','5220',null,'3110','3110','1210','3110','1331','3110','1440','0440','1140','0440','2220',null,'2220','2220','2220','1110','1430','3110','1320','1320','0242','2140','2320','2110','0230','3110','1330','3110','3110','4110','1110','1210','1110','1110','2220','0330','3110','1220','1440','1420','1140','3110','1110','0410','2440','0220','3110','0110','2440','2440','2223','2440','0320','3110','1210','0110','2440','0330','3110','2110','0220','3110','1110','1110','2210','3110',null,null,'0220','3110','0110',null]}
--------------------------------------------------------------------------------
/src/levels/l010.js:
--------------------------------------------------------------------------------
1 | _l[10] = {countdown: 25, speed: 5, pipes: ['0430','2330','2330','0140',null,'1330','0140',null,'1420','0140','4110','0240','2320',null,null,'2140','1330','1210','3110','2431','1320','1320','2210','1210','2320','0330','2210','3110','1110','3110','1110','0130','1220','3110','0440',null,'0120','1110','2210','3110','1110','3110','2110','3110','3110','3110','3110','0340','0430','3110','1330','2440','0422','1220','2110','3110','3110','3110','3110','0210','3110','1220','0410','2140','0430','1440','2430','1330','0110','2220','2220',null,'2223','3110',null,'0320','3110','1320','2430','1330','1330','3110','3110','5330','0120','3110','2210','2210','0210','0320','2110','2310','1440','0320','0410',null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l003.js:
--------------------------------------------------------------------------------
1 | _l[3] = {countdown: 80, speed: 5, pipes: ['0430','2330','0440','0230','2130','0340','0130','0240','0430','0140',null,null,'2322','0430','3110','3110','0140','2220','2320','2440','0320','1440','2210','0340','2220','2440','2140','2140','2140','2320','2220','2140','0330','1430','0240','2220','2220','2140','0220','1330','3110','3210','3110','1330','3110','1330','0310','2323','0120','3110','2210','3110','3110','3110','3110','1330','3210','1330','2210','1210',null,'2140','0130','3210','0410','0120','3110','3110','1330','1440','2430','1220',null,'4440','0320','3110','2330','2431','3110','3210','3110','1430','5220','2320',null,null,null,'0320','2330','2130','0310','0220','0110','0220','1320','0110']}
2 |
--------------------------------------------------------------------------------
/src/levels/l007.js:
--------------------------------------------------------------------------------
1 | _l[7] = {countdown: 15, speed: 5, pipes: [null,'0330','3110','2210','3110','2110','3110','2212','2110','3110','2210','0240','0330','3110','0230','2210','0140','4220','0230','2430','2330','0140','3110','2140','0330','1210','3110','2210','3110','1210','3110','2210','2110','3110','0440','2440','2320','2320','2320','3110','2140','0220','3110','2410','0340','2320','3110','2140','2220','2220','2220',null,'2440','5220','0220','2410','0210','2320','3110','2140','2220','0320','3110','2330','3110','1310','2330','3110','2430','3110','1330','0110','2220',null,'0320','2110','3110','3210','2110','2410','2110','0210','2140','3110','0420','2330','2433','2330','0110','0220','2330','3110','2330','2431','0110',null]}
2 |
--------------------------------------------------------------------------------
/src/levels/l008.js:
--------------------------------------------------------------------------------
1 | _l[8] = {countdown: 10, speed: 5, pipes: ['1330','2220','3110','3110','3110','3110','2220','3110','2210','2120','3110','1220','2110','0210','3110','1230','3110','1340','2113','3110','2220','2110','3110','1120','3110','3110','1220','1410','1220','3110','2440','3110','2130','2440','1310','3110','2110','2111','2330','4220','2110','2330','0440','1420','2440','2330','1330','3210','3110','3110','1210','1410','1220','3110','3110','3110','2430','2440','3210','1110','2210','2110','2230','5440','2330','1410','3110','3110','2442','2130','3110','1410','1410','3110','1210','1110','1340','3110','3110','3110','2310','2220','3210','1320','1220','1220','3110','3110','3110','3110','3110','3110','2220','2210','3110','0220']}
--------------------------------------------------------------------------------
/src/levels/l016.js:
--------------------------------------------------------------------------------
1 | _l[16] = {countdown: 70, speed: 5, pipes: ['1440','0230','1220','1410','0440',null,'0430','0340','0110','1320','3110',null,'0430','3140','3130','1330','0110','0330','3110','1330','3110','3110','1220','3110','0220','3110','3110','2140','0430','3110','1210','3110','1320','3110','3210','0110',null,'2140','0130','3110','3110','1310','1130','3110','1220','1110','0310','2110','4110','3110','0210','0430','3110','1430','1220','1220','3110','3110','2210','5330','0442','1440','0340','3111','1110','3110','3110','3113','3110','1210','0240','2110','2220','0230','3110','3110','0440','1320','0410','2220','1220','1220','1220','0440','0320','2110','1110','1110','1210','2210','2210','0210','0220','1220','2440','0220']}
2 |
--------------------------------------------------------------------------------
/src/levels/l019.js:
--------------------------------------------------------------------------------
1 | _l[19] = {countdown: 5, speed: 2, pipes: ['0430','0340','0430','0340','4220','0330','0440','0330','0440','0230','2330','0140','2220','0220','1140','1220','1430','3110','3110','3110','3110','0110','5220','2220','2221','0330','1430','3110','1140','3110','3110','3110','3110','2330','0210','2320','2220','0220','3110','1220','1110','0320','3110','0210','2140','2110','0330','0410','0120','0440','0220','1110','1110','2430','3110','2330','3110','0440','2220','2220','0430','3110','0140','1110','3110','0140','2220','0330','3110','3110','1220','0140','0120','0210','2220','0220','1440','0313','2320','2320','2140','2140','2220','2220',null,null,'0320','2330','0410',null,'0320','0410','0320','0412','0220','0110']}
2 |
--------------------------------------------------------------------------------
/src/levels/l014.js:
--------------------------------------------------------------------------------
1 | _l[14] = {countdown: 25, speed: 5, pipes: ['0430','3110','2430','2330','3110','2430','2430','2330','2330','2430','3110','0440','3110','1110','2430','0140','0220','2210','1430','0440','1330','2210','0140','2440','2320','0420','0140','2140','4220','0230','3110','1330','0410','2210','3110','3110','2220','0233','3110','3110','0210','0320','3110','3110','0440','5110','0110','2440','0120','3110','3110','3110','2110','0240','2120','0222','0110',null,null,'2140','0430','3110','0110','1320','2210','3110','1220','0440','1330','2110','2110','3110','3110','0420','2430','3110','2330','1110','0320','1430','3110','2110','2110','2140','0220','3110','2110','3110','2210','3110','2210','2211','3110','2210','2110','0310']}
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Editor
2 |
3 | Hotkeys ======
4 |
5 | Select Pipes: (press again to rotate, hold shift opposite rotate)
6 | a s d f g h
7 | Eraser
8 | v
9 | Clear All:
10 | ~
11 | Alternate Pipe Layout
12 | t
13 |
14 | Editor
15 | You need to set in the init.coffee the level you want to edit.
16 | eg window.editor = 4. To save your layout you need to start the flow.
17 | When you get the clear screen it saves the layout.
18 |
19 | Ideas:
20 |
21 | * A pipe on the other edge of the map can connect to another
22 | pipe to the outer edge.
23 |
24 | * pipes that can be transformed into other pipe types
25 |
26 | * triggers on pipes that cause other pipes to rotate
27 |
28 | * triggers that cause other pipes to vanish
29 |
--------------------------------------------------------------------------------
/src/components/count_star.coffee:
--------------------------------------------------------------------------------
1 | class window.Component.CountStar
2 | constructor:->
3 | create:=>
4 | @group = game.add.group()
5 | i = _d.star_count()
6 | txt = game.make.text 0,0, "#{i} / 100",
7 | font: '16px Verdana'
8 | fill: '#fff'
9 | align: 'center'
10 | txt.anchor.setTo 0.5, 0.5
11 | @sprite = game.add.sprite -75, -20, 'star_med'
12 | @group.addChild @sprite
13 | @group.addChild txt
14 | @group.x = 270
15 | @group.y = 512
16 | @animate()
17 | animate:=>
18 | tween = game.add.tween @sprite.scale
19 | tween.to {x: 1.07, y: 1.07},250, Phaser.Easing.Linear.None
20 | tween.to {x: 1, y: 1} ,250, Phaser.Easing.Linear.None
21 | tween.loop true
22 | tween.start()
23 |
--------------------------------------------------------------------------------
/src/components/button_tab.coffee:
--------------------------------------------------------------------------------
1 | class Component.ButtonTab
2 | constructor:->
3 | create:(x,y,kind,callback)=>
4 | @kind = kind
5 | @callback = callback
6 | @sprite = game.add.sprite 0, 0, 'tab'
7 | @sprite.inputEnabled = true
8 | @sprite.events.onInputDown.add @onclick, @
9 | @sprite.anchor.setTo 0.5, 0.5
10 | @sprite.angle += 180 if @kind is 'prev'
11 | @sprite.x = x
12 | @sprite.y = y
13 | @preview()
14 | onclick:=>
15 | if @kind is 'next'
16 | return if _d.page is 4
17 | _d.page += 1
18 | else
19 | return if _d.page is 0
20 | _d.page -= 1
21 | @callback()
22 | preview:=>
23 | @sprite.visible =
24 | if @kind is 'next'
25 | !(_d.page is 4)
26 | else
27 | !(_d.page is 0)
28 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | coderay (1.1.1)
5 | daemons (1.2.4)
6 | eventmachine (1.0.3)
7 | method_source (0.8.2)
8 | pry (0.10.4)
9 | coderay (~> 1.1.0)
10 | method_source (~> 0.8.1)
11 | slop (~> 3.4)
12 | rack (1.5.2)
13 | rack-livereload (0.3.15)
14 | rack
15 | rack-protection (1.5.3)
16 | rack
17 | sinatra (1.4.7)
18 | rack (~> 1.5)
19 | rack-protection (~> 1.4)
20 | tilt (>= 1.3, < 3)
21 | slop (3.6.0)
22 | thin (1.6.3)
23 | daemons (~> 1.0, >= 1.0.9)
24 | eventmachine (~> 1.0)
25 | rack (~> 1.0)
26 | tilt (1.3.7)
27 |
28 | PLATFORMS
29 | ruby
30 |
31 | DEPENDENCIES
32 | pry
33 | rack-livereload
34 | sinatra
35 | thin
36 |
37 | BUNDLED WITH
38 | 1.10.5
39 |
--------------------------------------------------------------------------------
/public/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | background: rgb(120,120,120);
5 | }
6 | canvas {
7 | display: block;
8 | margin: 0px auto;
9 | }
10 |
11 | .godmode {
12 | position: fixed;
13 | width: 300px;
14 | height: 100%;
15 | background: #fff;
16 | border-right: solid 2px rgb(40,40,40);
17 | overflow: auto;
18 | font-family: arial;
19 | font-size: 12px;
20 | }
21 | .godmode .wrap {
22 | padding: 16px;
23 | }
24 | .godmode table { border-collapse: collapse; width: 100%; }
25 | .godmode tr:hover td { background: rgb(250,250,250); }
26 | .godmode td.id {
27 | width: 1%;
28 | white-space: nowrap;
29 | border-right: solid 1px rgb(220,220,220);
30 | }
31 | .godmode td {
32 | padding: 6px 4px;
33 | text-align: center;
34 | border-bottom: solid 1px rgb(220,220,220);
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/count_gem.coffee:
--------------------------------------------------------------------------------
1 | class window.Component.CountGem
2 | constructor:->
3 | create:=>
4 | @group = game.add.group()
5 | i = _d.gem_count()
6 | txt = game.make.text 0,0, "#{i} / 300",
7 | font: '16px Verdana'
8 | fill: '#fff'
9 | align: 'center'
10 | txt.anchor.setTo 0.5, 0.5
11 | @sprite = game.add.sprite -75, -18, 'jewel_med'
12 | @group.addChild @sprite
13 | @group.addChild txt
14 | @group.x = 140
15 | @group.y = 512
16 | @animate()
17 | animate:=>
18 | game.time.events.add Phaser.Timer.SECOND * 0.25, @animate_delay, this
19 | animate_delay:=>
20 | tween = game.add.tween @sprite.scale
21 | tween.to {x: 1.07, y: 1.07},250, Phaser.Easing.Linear.None
22 | tween.to {x: 1, y: 1} ,250, Phaser.Easing.Linear.None
23 | tween.loop true
24 | tween.start()
25 |
--------------------------------------------------------------------------------
/server.rb:
--------------------------------------------------------------------------------
1 | require 'sinatra'
2 | require 'json'
3 | require 'pry'
4 |
5 | set :port, 1234
6 |
7 | get '/' do
8 | File.read(File.join('public', 'index.html'))
9 | end
10 |
11 | get '/:file' do
12 | path = "/Users/monsterlite/Games/pipes/public/"
13 | send_file "#{path}#{params[:file]}", disposition: 'inline'
14 | end
15 |
16 | post '/save/:level/:pipes' do
17 | i = params['level'].to_i
18 | l = i.to_s.rjust 3, "0"
19 | pipes = params['pipes']
20 | pipes = pipes.split(',')
21 | pipes = pipes.map {|p|
22 | p == 'null' ? p : "'#{p}'"
23 | }
24 | pipes = pipes.join(',')
25 |
26 | path = "#{File.dirname(__FILE__)}/src/levels/l#{l}.js"
27 | json = "_l[#{i}] = {countdown: 10, speed: 5, pipes: [#{pipes}]}"
28 |
29 | File.open(path, 'w') { |f| f.write(json) }
30 |
31 | content_type :json
32 | {success: true}.to_json
33 | end
34 |
--------------------------------------------------------------------------------
/src/components/jewel.coffee:
--------------------------------------------------------------------------------
1 | class Component.Jewel
2 | constructor:(i,x,y)->
3 | @collected = false
4 | @i = i
5 | @kind =
6 | switch i
7 | when 1 then 'red'
8 | when 2 then 'green'
9 | when 3 then 'blue'
10 | @sparkle = game.add.audio 'sparkle'
11 | @jewel = game.add.sprite x, y, 'jewels'
12 | @jewel.frame = i
13 | @jewel.anchor.setTo 0.5, 0.5
14 | @jewel
15 | get_i:=>
16 | @i
17 | animate:=>
18 | tween = game.add.tween(@jewel.scale)
19 | tween2 = game.add.tween(@jewel)
20 | tween.to {x: 2, y: 2},300, Phaser.Easing.Linear.None
21 | tween2.to {alpha: 0} ,300
22 | tween.start()
23 | tween2.start()
24 | collect:(grid)=>
25 | return if @collected
26 | @collected = true
27 | if _d.get_sound()
28 | @sparkle.play()
29 | @animate()
30 | grid.collect_jewel @kind
31 |
--------------------------------------------------------------------------------
/src/components/pipes/start.coffee:
--------------------------------------------------------------------------------
1 | class Component.PipeStart extends Component.Pipe
2 | tick_progress:=>
3 | speed = @grid.speed
4 | dir = @flow()
5 | @per1 += speed
6 | console.log speed, dir, @per1
7 | @done = true if @per1 is 100
8 | render:=>
9 | super
10 | @g.lineStyle 8, 0xffd900
11 |
12 | if @per1 >= 50
13 | v1 = 8
14 | v2 = (32 / 50) * (@per1 - 50)
15 | else if @per1 < 50
16 | v1 = (8 / 50) * @per1
17 | v2 = 0
18 |
19 | @g.moveTo 0 , 0
20 | @g.lineTo v2 , 0
21 |
22 | @g.beginFill 0xffd900
23 | @g.drawEllipse -1, 0, v1, v1
24 | @g.endFill()
25 |
26 | #markers for dumb people
27 | @g.beginFill 0xffd900
28 | @g.lineStyle 0, 0xffd900
29 | @g.drawEllipse -1, 0, 4, 4
30 | @g.endFill()
31 | set:=>
32 | @kind = 'start'
33 | @pipe.frame = 4
34 | super
35 | dir:=>
36 | switch @angle()
37 | when 0 then 'east'
38 | when 90 then 'south'
39 | when 180 then 'west'
40 | when 270 then 'north'
41 |
42 |
--------------------------------------------------------------------------------
/src/components/pipes/end.coffee:
--------------------------------------------------------------------------------
1 | class Component.PipeEnd extends Component.Pipe
2 | tick_progress:=>
3 | speed = @grid.speed
4 | dir = @flow()
5 | @per1 += speed
6 | @done = true if @per1 is 100
7 | render:=>
8 | super
9 | @g.lineStyle 8, 0xffd900
10 |
11 | if @per1 >= 50
12 | v1 = 0
13 | v2 = (25 / 50) * (@per1 - 50)
14 | else if @per1 < 50
15 | v1 = (27 / 50) * @per1
16 | v2 = 0
17 |
18 | @g.moveTo 32 , 0
19 | @g.lineTo 32-v1 , 0
20 |
21 | @g.beginFill 0xffd900
22 | @g.lineStyle 0, 0x000000
23 | @g.drawRect 11-v2, -12, v2, 24
24 | @g.endFill()
25 |
26 | #markers for dumb people
27 | @g.beginFill 0xffd900
28 | @g.lineStyle 0, 0xffd900
29 | @g.drawEllipse -1, 0, 4, 4
30 | @g.endFill()
31 | set:=>
32 | @kind = 'end'
33 | @pipe.frame = 5
34 | super
35 | dir:=>
36 | null
37 | switch @flow()
38 | when 'east' then @angle() is 180
39 | when 'south' then @angle() is 270
40 | when 'west' then @angle() is 0
41 | when 'north' then @angle() is 90
42 |
--------------------------------------------------------------------------------
/src/components/spill.coffee:
--------------------------------------------------------------------------------
1 | class Component.Spill
2 | constructor:->
3 | reset:=>
4 | @g.clear()
5 | @per = 0
6 | @spilling = false
7 | @tick = null
8 | create:=>
9 | @group = game.add.group()
10 | @g = game.make.graphics 0, 0
11 | @group.addChild @g
12 | @reset()
13 | spill:(fail,pipe,flow_dir)=>
14 | switch flow_dir
15 | when 'east'
16 | @group.x = pipe.pipe.x + 32
17 | @group.y = pipe.pipe.y
18 | when 'south'
19 | @group.x = pipe.pipe.x
20 | @group.y = pipe.pipe.y + 32
21 | when 'north'
22 | @group.x = pipe.pipe.x
23 | @group.y = pipe.pipe.y - 32
24 | when 'west'
25 | @group.x = pipe.pipe.x - 32
26 | @group.y = pipe.pipe.y
27 | return if @spilling
28 | @spilling = true
29 | @tick = game.time.now
30 | fail()
31 | render:=>
32 | @g.reset()
33 | @g.beginFill 0xffd900
34 | @g.drawEllipse -1, 0, @per, @per
35 | @g.endFill()
36 | update:=>
37 | if @spilling && @per < 320 && (game.time.now - @tick > 10)
38 | @tick = game.time.now
39 | if @per < 50 then @per += 1
40 | else if @per < 100 then @per += 0.5
41 | else if @per < 200 then @per += 0.25
42 | else if @per < 250 then @per += 0.10
43 | else if @per < 320 then @per += 0.05
44 | @render()
45 |
--------------------------------------------------------------------------------
/src/components/pipes/straight.coffee:
--------------------------------------------------------------------------------
1 | class Component.PipeStraight extends Component.Pipe
2 | tick_progress:=>
3 | speed = @grid.speed
4 | dir = @flow()
5 | if (dir in ['east' ,'west'] && @angle() in [0,180]) ||
6 | (dir in ['south','north'] && @angle() in [90,270])
7 | @per1 += speed
8 | @done = true if @per1 is 100
9 | render:=>
10 | super
11 | v = (64 / 100 ) * @per1
12 | @g.lineStyle 8, 0xffd900
13 |
14 | @rev1 =
15 | switch @flow()
16 | when 'east' then @angle() is 180
17 | when 'south' then @angle() is 270
18 | when 'west' then @angle() is 0
19 | when 'north' then @angle() is 90
20 |
21 | if @rev1
22 | @g.moveTo 32-v, 0
23 | @g.lineTo 32 , 0
24 | else
25 | @g.moveTo -32 , 0
26 | @g.lineTo v-32 , 0
27 | set:=>
28 | @kind = 'straight'
29 | @pipe.frame = 2
30 | super
31 | dir:=>
32 | switch @flow()
33 | when 'east'
34 | switch @angle()
35 | when 0,180 then 'east'
36 | else
37 | false
38 |
39 | when 'south'
40 | switch @angle()
41 | when 90, 270 then 'south'
42 | else
43 | false
44 | when 'west'
45 | switch @angle()
46 | when 0, 180 then 'west'
47 | else
48 | false
49 | when 'north'
50 | switch @angle()
51 | when 90, 270 then 'north'
52 | else
53 | false
54 |
--------------------------------------------------------------------------------
/src/components/counter.coffee:
--------------------------------------------------------------------------------
1 | class Component.Counter
2 | constructor:(args)->
3 | style:=>
4 | font: '28px Verdana'
5 | fill: '#fff'
6 | align: 'center'
7 | reset:=>
8 | @text = null
9 | @timer = null
10 | @timer_ev = null
11 | @secs = 0
12 | stop:=>
13 | @secs = @t_secs()
14 | @timer.stop()
15 | format_time:(s)=>
16 | mins = "0" + Math.floor(s / 60)
17 | secs = "0" + (s - mins * 60)
18 | mins.substr(-2) + ":" + secs.substr(-2)
19 | create:(onend,seconds=10)=>
20 | @reset()
21 | x = game.world.width - 60
22 | y = (game.world.height - 32)
23 | @text = game.add.text x , y, '', @style()
24 | @text.anchor.setTo 0.5, 0.5
25 | switch _d.mode
26 | when 'time'
27 | @timer = game.time.create()
28 | @timer_ev = @timer.add(Phaser.Timer.SECOND * seconds, onend, this)
29 | @timer.start()
30 | when 'adventure'
31 | time = @format_time 0
32 | @text.setText time
33 | game.time.events.loop(Phaser.Timer.SECOND, @update_counter, this)
34 | time:=>
35 | @secs
36 | t_secs:=>
37 | Math.round (@timer_ev.delay - @timer.ms) / 1000
38 | update_counter:=>
39 | @secs++
40 | time = @format_time @secs
41 | @text.setText time
42 | update:=>
43 | switch _d.mode
44 | when 'time'
45 | return unless @timer.running
46 | s = @t_secs()
47 | @text.fill = '#d60225' if s <= 5
48 | time = @format_time s
49 | @text.setText time
50 |
--------------------------------------------------------------------------------
/src/components/pipes/corner.coffee:
--------------------------------------------------------------------------------
1 | class Component.PipeCorner extends Component.Pipe
2 | tick_progress:=>
3 | speed = @grid.speed
4 | dir = @flow()
5 | @per1 += speed
6 | @done = true if @per1 is 100
7 | render:=>
8 | super
9 | v = (90 / 100) * @per1
10 | @g.lineStyle 8, 0xffd900
11 |
12 | @rev1 =
13 | switch @flow()
14 | when 'east' then @angle() is 180
15 | when 'south' then @angle() is 270
16 | when 'west' then @angle() is 0
17 | when 'north' then @angle() is 90
18 |
19 | if @rev1
20 | @g.arc -32, -32, 32, game.math.degToRad(0), game.math.degToRad(v), false
21 | else
22 | @g.arc -32, -32, 32, game.math.degToRad(90-v), game.math.degToRad(90), false
23 | set:=>
24 | @kind = 'corner'
25 | @pipe.frame = 0
26 | super
27 | dir:=>
28 | switch @flow()
29 | when 'east'
30 | switch @angle()
31 | when 0 then 'north'
32 | when 270 then 'south'
33 | else
34 | false
35 | when 'south'
36 | switch @angle()
37 | when 0 then 'west'
38 | when 90 then 'east'
39 | else
40 | false
41 | when 'west'
42 | switch @angle()
43 | when 90 then 'north'
44 | when 180 then 'south'
45 | else
46 | false
47 | when 'north'
48 | switch @angle()
49 | when 270 then 'west'
50 | when 180 then 'east'
51 | else
52 | false
53 |
--------------------------------------------------------------------------------
/src/components/godmode.coffee:
--------------------------------------------------------------------------------
1 | class controller
2 | constructor:->
3 | @$ =
4 | visible: false
5 | update: @update
6 | toggle:=>
7 | @$.visible = !@$.visible
8 | m.redraw(true)
9 | update:(i,kind)=>
10 | =>
11 | console.log 'update', i, kind
12 | jewels = {}
13 | jewels.red = true if kind is 'r'
14 | jewels.green = true if kind is 'g'
15 | jewels.blue = true if kind is 'b'
16 | _d.level_complete i, jewels, null
17 |
18 | class view
19 | constructor:(ctrl)->
20 | @$ = ctrl
21 | level:(i)=>
22 | m 'tr',
23 | m 'td.id', i+1
24 | m 'td.checkbox',
25 | m 'span', 'r'
26 | m "input[type='checkbox']", onclick: @$.update(i,'r'), checked: _d.level_status(i,'red')
27 | m 'td.checkbox',
28 | m 'span', 'g'
29 | m "input[type='checkbox']", onclick: @$.update(i,'g'), checked: _d.level_status(i,'green')
30 | m 'td.checkbox',
31 | m 'span', 'b'
32 | m "input[type='checkbox']", onclick: @$.update(i,'b'), checked: _d.level_status(i,'blue')
33 | render:=>
34 | return unless @$.visible
35 | m '.godmode',
36 | m '.wrap',
37 | m 'table',
38 | for i in [0..99]
39 | @level i
40 |
41 |
42 | ctrl = new controller()
43 | comp =
44 | controller: (args)-> ctrl.$
45 | view : (c)-> new view(c).render()
46 |
47 | class window.Component.GodMode
48 | constructor:->
49 | el = document.createElement "div"
50 | document.body.appendChild el
51 | m.mount el, comp
52 |
53 | document.addEventListener 'keydown', (ev)=>
54 | if ev.which is 187
55 | ctrl.toggle()
56 |
--------------------------------------------------------------------------------
/src/components/editor/pipe_button.coffee:
--------------------------------------------------------------------------------
1 | class Component.Editor.PipeButton
2 | constructor:(toolbar,kind)->
3 | @toolbar = toolbar
4 | @kind = kind
5 | create:(x)=>
6 | @group = game.make.group()
7 | @group.x = x
8 |
9 | @sprite = game.make.sprite 24, 24, 'pipes'
10 | @sprite.frame = @frame()
11 | @sprite.scale.setTo 0.75, 0.75
12 | @sprite.anchor.setTo 0.5, 0.5
13 |
14 | @g = game.make.graphics 0,0
15 | @render()
16 | @group.addChild @g
17 | @group.addChild @sprite
18 | @group
19 | render:(active=false)=>
20 | @g.reset()
21 | if active
22 | @g.lineStyle 2, 0xffd900
23 | else
24 | @g.lineStyle 2, 0x1c1c1c
25 | @g.beginFill 0x1c1c1c
26 | @g.drawRect 0 , 0, 48, 48
27 | @g.endFill()
28 | inactive:=>
29 | @render false
30 | frame:=>
31 | switch @kind
32 | when 'start' then 4
33 | when 'end' then 5
34 | when 'corner' then 0
35 | when 'double_corner' then 1
36 | when 'straight' then 2
37 | when 'cross' then 3
38 | when 'null' then null
39 | active:(shift)=>
40 | if @kind is @toolbar.active
41 | if shift
42 | @sprite.angle -= 90
43 | else
44 | @sprite.angle += 90
45 | @toolbar.set_active @kind
46 | @render true
47 | str:=>
48 | if @kind is 'null'
49 | null
50 | else
51 | ang =
52 | switch @angle()
53 | when 0 then 1
54 | when 90 then 2
55 | when 180 then 3
56 | when 270 then 4
57 | "#{@frame()}#{ang}#{ang}0"
58 | angle:=>
59 | # For some reason angles end up negative even though
60 | # we alawys add by 90. so we need to ensure its stays
61 | # positive
62 | angle = @sprite.angle
63 | angle = 360 + angle if angle < 0
64 | angle
65 |
--------------------------------------------------------------------------------
/src/components/pipes/cross.coffee:
--------------------------------------------------------------------------------
1 | class Component.PipeCross extends Component.Pipe
2 | tick_progress:=>
3 | speed = @grid.speed
4 | dir = @flow()
5 | if (dir in ['east' ,'west'] && @angle() in [0,180]) ||
6 | (dir in ['south','north'] && @angle() in [90,270])
7 | @per1 += speed
8 | @done = true if @per1 is 100
9 | else if (dir in ['east' ,'west'] && @angle() in [90,270]) ||
10 | (dir in ['south','north'] && @angle() in [0,180])
11 | @per2 += speed
12 | @done = true if @per2 is 100
13 | render:=>
14 | super
15 | v1 = (64 / 100 ) * @per1
16 |
17 | v2 = 0
18 | v3 = 0
19 | if @per2 < 45
20 | v2 = (24 / 45 ) * @per2
21 | else if @per2 >= 55
22 | v2 = 24
23 | v3 = (24 / 45 ) * (@per2-55)
24 | else if @per2 >= 45
25 | v2 = 24
26 | # 0 45
27 | # 55 100
28 | #24
29 |
30 | @g.lineStyle 8, 0xffd900
31 |
32 | @rev1 =
33 | switch @flow()
34 | when 'west' then @angle() is 0
35 | when 'north' then @angle() is 90
36 | when 'east' then @angle() is 180
37 | when 'south' then @angle() is 270
38 |
39 | @rev2 =
40 | switch @flow()
41 | when 'north' then @angle() is 0
42 | when 'south' then @angle() is 180
43 | when 'west' then @angle() is 270
44 | when 'east' then @angle() is 90
45 |
46 | if @rev1
47 | @g.moveTo 32-v1, 0
48 | @g.lineTo 32 , 0
49 | else
50 | @g.moveTo -32 , 0
51 | @g.lineTo v1-32, 0
52 |
53 | if @rev2
54 | @g.moveTo 0, 32
55 | @g.lineTo 0, 32-v2
56 |
57 | @g.moveTo 0, -8
58 | @g.lineTo 0, -8-v3
59 | else
60 | @g.moveTo 0, -32
61 | @g.lineTo 0, v2-32
62 | @g.moveTo 0, 8
63 | @g.lineTo 0, 8+v3
64 | set:=>
65 | @kind = 'cross'
66 | @pipe.frame = 3
67 | super
68 | dir:=>
69 | @flow()
70 |
--------------------------------------------------------------------------------
/src/components/pipes/double_corner.coffee:
--------------------------------------------------------------------------------
1 | class Component.PipeDoubleCorner extends Component.Pipe
2 | tick_progress:=>
3 | speed = @grid.speed
4 | dir = @flow()
5 | if @angle() is 0 && dir in ['west','north'] ||
6 | @angle() is 90 && dir in ['north','east'] ||
7 | @angle() is 180 && dir in ['east','south'] ||
8 | @angle() is 270 && dir in ['south','west']
9 | @per1 += speed
10 | @done = true if @per1 is 100
11 | if @angle() is 0 && dir in ['east','south'] ||
12 | @angle() is 90 && dir in ['south','west'] ||
13 | @angle() is 180 && dir in ['west','north'] ||
14 | @angle() is 270 && dir in ['north','east']
15 | @per2 += speed
16 | @done = true if @per2 is 100
17 | render:=>
18 | super
19 | v1 = (90 / 100) * @per1
20 | v2 = (90 / 100) * @per2
21 | @g.lineStyle 8, 0xffd900
22 |
23 | @rev1 =
24 | switch @flow()
25 | when 'east' then @angle() is 180
26 | when 'south' then @angle() is 270
27 | when 'west' then @angle() is 0
28 | when 'north' then @angle() is 90
29 |
30 | @rev2 =
31 | switch @flow()
32 | when 'east' then @angle() is 0
33 | when 'south' then @angle() is 90
34 | when 'west' then @angle() is 180
35 | when 'north' then @angle() is 270
36 |
37 | if @rev1
38 | @g.arc -32, -32, 32, game.math.degToRad(0), game.math.degToRad(v1), false
39 | else
40 | @g.arc -32, -32, 32, game.math.degToRad(90-v1), game.math.degToRad(90), false
41 |
42 | if @rev2
43 | @g.arc 32 , 32 , 32, game.math.degToRad(180), game.math.degToRad(180+v2), false
44 | else
45 | @g.arc 32 , 32 , 32, game.math.degToRad(270-v2), game.math.degToRad(270), false
46 | set:=>
47 | @kind = 'double_corner'
48 | @pipe.frame = 1
49 | super
50 | dir:=>
51 | switch @flow()
52 | when 'east'
53 | switch @angle()
54 | when 0,180 then 'north'
55 | when 90,270 then 'south'
56 | when 'south'
57 | switch @angle()
58 | when 0,180 then 'west'
59 | when 90,270 then 'east'
60 | when 'west'
61 | switch @angle()
62 | when 0,180 then 'south'
63 | when 90,270 then 'north'
64 | when 'north'
65 | switch @angle()
66 | when 90,270 then 'west'
67 | when 0,180 then 'east'
68 |
69 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp')
2 | //var reload = require('gulp-livereload')
3 | var coffee = require('gulp-coffee')
4 | var concat = require('gulp-concat')
5 | var addsrc = require('gulp-add-src')
6 | var uglify = require('gulp-uglify')
7 |
8 | var js_init = './src/init.js'
9 |
10 | var js_lib = [
11 | "./src/lib/mithril.js"
12 | , "./src/lib/lz.js"
13 | , "./src/lib/store.js"
14 | , "./src/lib/phaser.js"
15 | ]
16 |
17 | var js_lvls = "./src/levels/*.js"
18 |
19 | var js_app = [
20 | , './src/components/clear_screen.coffee'
21 | , './src/components/counter.coffee'
22 | , './src/components/grid.coffee'
23 | , './src/components/jewel.coffee'
24 | , './src/components/pipe.coffee'
25 | , './src/components/release_button.coffee'
26 | , './src/components/spill.coffee'
27 | , './src/components/godmode.coffee'
28 |
29 | , './src/components/editor/pipe_button.coffee'
30 | , './src/components/editor/toolbar.coffee'
31 |
32 | , './src/components/pipes/corner.coffee'
33 | , './src/components/pipes/cross.coffee'
34 | , './src/components/pipes/double_corner.coffee'
35 | , './src/components/pipes/end.coffee'
36 | , './src/components/pipes/start.coffee'
37 | , './src/components/pipes/straight.coffee'
38 |
39 | , './src/components/level.coffee'
40 | , './src/components/count_gem.coffee'
41 | , './src/components/count_star.coffee'
42 | , './src/components/button_sound.coffee'
43 | , './src/components/button_music.coffee'
44 | , './src/components/button_back.coffee'
45 | , './src/components/button_tab.coffee'
46 |
47 | , './src/states/boot.coffee'
48 | , './src/states/levels.coffee'
49 | , './src/states/load.coffee'
50 | , './src/states/menu.coffee'
51 | , './src/states/play.coffee'
52 |
53 | , './src/data.coffee'
54 | , './src/game.coffee'
55 | ]
56 |
57 | gulp.task('js', function() {
58 | return gulp.src(js_app)
59 | .pipe(coffee())
60 | .pipe(addsrc.append(js_lvls))
61 | .pipe(addsrc.prepend(js_lib))
62 | .pipe(addsrc.prepend(js_init))
63 | .pipe(concat('app.js'))
64 | //.pipe(uglify())
65 | .pipe(gulp.dest('./public/'));
66 | });
67 |
68 | gulp.task('default', function () {
69 | gulp.run('js');
70 | });
71 |
72 | gulp.task('watch', function () {
73 | //reload.listen()
74 | gulp.watch(
75 | [
76 | './src/init.js',
77 | './src/**/*.coffee',
78 | ]
79 | , ['js']);
80 | });
81 |
--------------------------------------------------------------------------------
/src/states/levels.coffee:
--------------------------------------------------------------------------------
1 | class controller
2 | constructor:(args)->
3 | @count_gem = new Component.CountGem()
4 | @count_star = new Component.CountStar()
5 |
6 | @button_sound = new Component.ButtonSound()
7 | @button_music = new Component.ButtonMusic()
8 | @button_back = new Component.ButtonBack()
9 |
10 | @tab_prev = new Component.ButtonTab()
11 | @tab_next = new Component.ButtonTab()
12 | update_tabs:=>
13 | @tab_prev.preview()
14 | @tab_next.preview()
15 | @update_levels()
16 | for bg in @bgs
17 | bg.visible = false
18 | @bg_active = @bgs[_d.page]
19 | @bg_active.visible = true
20 | update_levels:=>
21 | offset = _d.page * 20
22 | for level,i in @levels
23 | level.set offset+i
24 | create_title:=>
25 | @title = game.add.sprite 0, 0, "label_#{_d.mode}"
26 | @title.anchor.setTo 0.5, 0.5
27 | @title.y = 64
28 | @title.x = game.world.centerX
29 | create_levels:=>
30 | @group = game.add.group()
31 | @group.createMultiple 20, 'levels', 0, true
32 | @group.align 5, 4, 128, 96
33 | @group.x = (game.world.width / 2) - (@group.width / 2)
34 | @group.y = (game.world.height / 2) - (@group.height / 2)
35 | @levels = []
36 | for level,i in @group.children
37 | @levels.push @create_level(level,i)
38 | create_level:(level,i)=>
39 | lvl = new Component.Level()
40 | lvl.create level, i
41 | lvl
42 | create:=>
43 | _d.page = 0
44 | game.stage.backgroundColor = '#1c1c1c'
45 | @bgs = []
46 | @bgs.push game.add.tileSprite 0, 0, game.world.width, game.world.height, 'bg1'
47 | @bgs.push game.add.tileSprite 0, 0, game.world.width, game.world.height, 'bg2'
48 | @bgs.push game.add.tileSprite 0, 0, game.world.width, game.world.height, 'bg3'
49 | @bgs.push game.add.tileSprite 0, 0, game.world.width, game.world.height, 'bg4'
50 | @bgs.push game.add.tileSprite 0, 0, game.world.width, game.world.height, 'bg5'
51 |
52 | @create_title()
53 | @button_back.create()
54 |
55 | @button_sound.create()
56 | @button_music.create()
57 |
58 | @create_levels()
59 |
60 | cx = game.world.width
61 | cy = game.world.centerY
62 | fun = =>
63 | @tab_prev.preview()
64 | @tab_next.preview()
65 | @tab_prev.create 24 , cy, 'prev', @update_tabs
66 | @tab_next.create cx-24, cy, 'next', @update_tabs
67 | @update_tabs()
68 |
69 | @count_star.create()
70 | @count_gem.create()
71 | ctrl = new controller()
72 |
73 | _states.levels =
74 | create: ctrl.create
75 |
--------------------------------------------------------------------------------
/src/states/play.coffee:
--------------------------------------------------------------------------------
1 | class controller
2 | constructor:(args)->
3 | @toolbar = new Component.Editor.Toolbar()
4 | @spill = new Component.Spill()
5 | @counter = new Component.Counter()
6 | @grid = new Component.Grid @spill, @counter, @toolbar, @success, @fail
7 | @clear_screen = new Component.ClearScreen()
8 | @release_button = new Component.ReleaseButton()
9 |
10 | backup:=> #backup last edited level
11 | return if window.editor is false
12 | data = @grid.backup()
13 | ajax = new XMLHttpRequest()
14 | ajax.open 'POST', "http://localhost:1234/save/#{_d.level}/#{data}"
15 | ajax.setRequestHeader 'Content-Type', 'application/json'
16 | ajax.send()
17 | success:(jewels,time)=>
18 | @backup()
19 | if _d.get_sound()
20 | @sound_win.play()
21 | @clear_screen.show(true,jewels,time)
22 | fail_sound:=>
23 | #if _d.get_sound()
24 | #@sound_fail.play()
25 | fail:=>
26 | game.time.events.add(Phaser.Timer.SECOND * 0.2, @fail_sound, this)
27 | @backup()
28 | @clear_screen.show(false)
29 | release_goo:=>
30 | console.log 'release goo'
31 | @release_button.flow()
32 |
33 | if _d.get_sound()
34 | @sound_go.play()
35 | if _d.mode is 'time'
36 | @counter.stop()
37 | @grid.start_flowing()
38 | set_jewel:=>
39 | x = game.input.x
40 | y = game.input.y
41 | nx = Math.floor (x / 64)
42 | ny = Math.floor (y / 64)
43 | i = if ny is 0 then nx else (ny * 12) + nx
44 | @grid.set_jewel(i)
45 | menubar_bg:=>
46 | g = game.add.graphics(0,0)
47 | g.beginFill 0x181919
48 | g.drawRect 0, game.world.height-64, 800, 64
49 | g.endFill()
50 | create:=>
51 | game.stage.disableVisibilityChange = true
52 | if window.editor != false
53 | save_hotkey = game.input.keyboard.addKey(Phaser.Keyboard.U)
54 | save_hotkey.onDown.add @backup
55 |
56 | jewel = game.input.keyboard.addKey(Phaser.Keyboard.ONE)
57 | jewel.onDown.add @set_jewel
58 |
59 | #@sound_spill = game.add.audio 'spill'
60 | @sound_go = game.add.audio 'bubbles'
61 | @sound_win = game.add.audio 'win'
62 | @sound_fail = game.add.audio 'fail'
63 |
64 | space = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR)
65 | space.onDown.add @release_goo
66 |
67 | game.canvas.oncontextmenu = (e)-> e.preventDefault()
68 | game.stage.backgroundColor = 0x000000
69 | _d.cleared = false
70 |
71 | time = if window.editor != false then 10000 else _l[_d.level].countdown
72 | @menubar_bg()
73 | @release_button.create @release_goo
74 | @counter.create @release_goo, time
75 | @grid.create _d.level
76 | if editor != false
77 | @toolbar.create @grid
78 | @clear_screen.create()
79 |
80 | update:=>
81 | @counter.update()
82 | @grid.update()
83 | @spill.update()
84 | ctrl = new controller()
85 |
86 | _states.play =
87 | create: ctrl.create
88 | update: ctrl.update
89 |
--------------------------------------------------------------------------------
/src/components/clear_screen.coffee:
--------------------------------------------------------------------------------
1 | class Component.ClearScreen
2 | constructor:(win,fail)->
3 | @success = null
4 | @bg = null
5 | @btn_level = null
6 | @btn_again = null
7 | @btn_next = null
8 | show:(success,jewels,time)=>
9 | @jewel_red.frame = if jewels && jewels.red then 1 else 3
10 | @jewel_green.frame = if jewels && jewels.green then 0 else 3
11 | @jewel_blue.frame = if jewels && jewels.blue then 2 else 3
12 | if success
13 | _d.level_complete _d.level, jewels, time
14 | game.time.events.add(Phaser.Timer.SECOND * 0.5, @now_show, this)
15 | else
16 | game.time.events.add(Phaser.Timer.SECOND * 1, @now_show, this)
17 | if _d.is_unlocked(_d.level+1)
18 | @btn_next.exists = true
19 | else
20 | @btn_next.exists = false
21 | now_show:=>
22 | @bg.alpha = 0
23 | @btn_levels.scale.setTo 0,0
24 | @btn_again.scale.setTo 0,0
25 | @btn_next.scale.setTo 0,0
26 | @bg.exists = true
27 |
28 | tween = game.add.tween @bg
29 | tween.to {alpha: 1} ,200, Phaser.Easing.Linear.None
30 | tween.start()
31 | tween.onComplete.add @bounce_actions, this
32 | bounce_actions:=>
33 | @bounce_btn @btn_levels, 0
34 | @bounce_btn @btn_again , 0.25
35 | @bounce_btn @btn_next , 0.5
36 | bounce_btn:(btn,secs)=>
37 | bounce = =>
38 | tween = game.add.tween btn.scale
39 | tween.to {x: 1, y: 1},600, Phaser.Easing.Elastic.Out
40 | tween.start()
41 | game.time.events.add(Phaser.Timer.SECOND * secs, bounce, this)
42 | onclick_levels:=>
43 | game.state.start 'levels'
44 | onclick_again:=>
45 | game.state.start 'play'
46 | onclick_next:=>
47 | _d.level += 1
48 | game.state.start 'play'
49 | create:=>
50 | @bg = game.add.sprite 0,0, 'clear_screen'
51 |
52 | @jewel_red = game.make.sprite 0, 0, 'jewels_large'
53 | @jewel_red.anchor.setTo 0.5, 0.5
54 | @jewel_red.x = 240
55 | @jewel_red.y = (4*64)+24
56 |
57 | @jewel_green = game.make.sprite 0, 0, 'jewels_large'
58 | @jewel_green.anchor.setTo 0.5, 0.5
59 | @jewel_green.x = game.world.width / 2
60 | @jewel_green.y = (4*64)+24
61 |
62 | @jewel_blue = game.make.sprite 0, 0, 'jewels_large'
63 | @jewel_blue.anchor.setTo 0.5, 0.5
64 | @jewel_blue.x = game.world.width - 240
65 | @jewel_blue.y = (4*64)+24
66 |
67 | @btn_levels = game.make.button 384-96, 384, 'clear_buttons', @onclick_levels, this, 0, 0, 0
68 | @btn_again = game.make.button 384 , 384, 'clear_buttons', @onclick_again , this, 3, 3, 3
69 | @btn_next = game.make.button 384+96, 384, 'clear_buttons', @onclick_next , this, 6, 6, 6
70 |
71 | @btn_levels.anchor.setTo 0.5, 0.5
72 | @btn_again.anchor.setTo 0.5, 0.5
73 | @btn_next.anchor.setTo 0.5, 0.5
74 |
75 | @bg.addChild @btn_again
76 | @bg.addChild @btn_next
77 | @bg.addChild @btn_levels
78 |
79 | @bg.addChild @jewel_red
80 | @bg.addChild @jewel_green
81 | @bg.addChild @jewel_blue
82 | @bg.exists = false
83 | update:=>
84 |
--------------------------------------------------------------------------------
/src/states/load.coffee:
--------------------------------------------------------------------------------
1 | sizzle = null
2 | titlecard = null
3 |
4 | fadein = =>
5 | if _d.get_sound()
6 | sizzle.fadeIn(200)
7 | tween = game.add.tween titlecard
8 | tween.to { alpha: 1 }, 750 , Phaser.Easing.Linear.None
9 | tween.to { alpha: 1 }, 3500, Phaser.Easing.Linear.None
10 | tween.onComplete.add fadeout, this
11 | tween.start()
12 | fadeout = =>
13 | if _d.get_sound()
14 | sizzle.fadeOut(800)
15 | tween = game.add.tween titlecard
16 | tween.to { alpha: 0 }, 1000 , Phaser.Easing.Linear.None
17 | tween.onComplete.add complete, this
18 | tween.start()
19 | fadeout_card = =>
20 |
21 | complete = ->
22 | game.state.start 'menu'
23 |
24 | _states.load =
25 | preload: ->
26 |
27 | game.load.audio 'sizzle' , './sizzle.mp3'
28 | game.load.image 'titlecard', './titlecard.png'
29 |
30 | game.load.audio 'win' , './win.mp3'
31 | game.load.audio 'fail', './fail.mp3'
32 |
33 | game.load.audio 'sparkle' , './sparkle.mp3'
34 | game.load.audio 'bubbles' , './bubbles.mp3'
35 |
36 | game.load.audio 'turn1' , './turn1.mp3'
37 | game.load.audio 'turn2' , './turn2.mp3'
38 | game.load.audio 'turn3' , './turn3.mp3'
39 | game.load.audio 'turn4' , './turn4.mp3'
40 |
41 | game.load.image 'back_button' , './back.png'
42 | game.load.image 'label_adventure', './label_adventure.png'
43 | game.load.image 'label_time' , './label_time.png'
44 |
45 | game.load.image 'tab', './tab.png'
46 |
47 | game.load.image 'bg1', './bg1.png'
48 | game.load.image 'bg2', './bg2.png'
49 | game.load.image 'bg3', './bg3.png'
50 | game.load.image 'bg4', './bg4.png'
51 | game.load.image 'bg5', './bg5.png'
52 |
53 | game.load.image 'button_adventure', './button_adventure.png'
54 | game.load.image 'button_time' , './button_time.png'
55 |
56 | game.load.spritesheet 'button_flow' , './button_flow.png' , 128, 64 , 2
57 | game.load.spritesheet 'jewels' , './jewels.png' , 28 , 28 , 4
58 | game.load.spritesheet 'jewels_large', './jewels_large.png', 100, 100, 4
59 |
60 | game.load.spritesheet 'audio_buttons', './audio_buttons.png', 48 , 48 , 4
61 | game.load.spritesheet 'levels' , './levels.png' , 128, 96 , 6
62 | game.load.spritesheet 'pipes' , './pipes.png' , 64 , 64 , 6
63 | game.load.spritesheet 'clear_buttons', './clear_buttons.png', 64 , 64 , 9
64 | game.load.image 'tile' , './tile.png'
65 | game.load.image 'clear_screen' , './clear_screen.png'
66 |
67 | game.load.image 'menu' , './menu.png'
68 | game.load.image 'logo' , './logo.png'
69 |
70 | game.load.image 'jewel_med', './jewel_med.png'
71 | game.load.image 'star_med' , './star_med.png'
72 | create: ->
73 | _d.load()
74 | titlecard = game.add.image 0, 0, 'titlecard'
75 | titlecard.alpha = 0
76 |
77 | if window.editor != false
78 | _d.level = window.editor
79 | game.state.start 'play'
80 | else
81 | sizzle = game.add.audio 'sizzle'
82 | if _d.get_sound()
83 | sizzle.onDecoded.add fadein, this
84 | else
85 | fadein()
86 |
--------------------------------------------------------------------------------
/src/data.coffee:
--------------------------------------------------------------------------------
1 | class window.Data
2 | ver: '1.0.0'
3 | changelog:=>
4 | []
5 | constructor:->
6 | @data = {
7 | levels : []
8 | tlevels: [] #timed_levels
9 | }
10 | tlevel:(i)=>
11 | return null unless @data.tlevels
12 | @data.tlevels[i]
13 | level_status:(i,v)=>
14 | if @data.levels
15 | lvl = @data.levels[i]
16 | switch v
17 | when 'complete' then lvl && lvl.indexOf('t') != -1
18 | when 'red' then lvl && lvl.indexOf('r') != -1
19 | when 'green' then lvl && lvl.indexOf('g') != -1
20 | when 'blue' then lvl && lvl.indexOf('b') != -1
21 | else
22 | false
23 | get_sound:=>
24 | if @data.sound is undefined
25 | true
26 | else
27 | @data.sound
28 | set_sound:(v)=>
29 | @data.sound = v
30 | @save()
31 | get_music:=>
32 | if @data.music is undefined
33 | true
34 | else
35 | @data.music
36 | set_music:(v)=>
37 | @data.music = v
38 | @save()
39 | needed_to_unlock:(level)=>
40 | switch level
41 | when 95 then 30
42 | when 96 then 45
43 | when 97 then 60
44 | when 98 then 75
45 | when 99 then 98
46 | else
47 | (level*3)-2
48 | gem_count:=>
49 | count = 0
50 | if @data.levels
51 | for lvl in @data.levels
52 | if lvl
53 | count++ if lvl.indexOf('r') != -1
54 | count++ if lvl.indexOf('g') != -1
55 | count++ if lvl.indexOf('b') != -1
56 | count
57 | star_count:=>
58 | count = 0
59 | if @data.levels
60 | for lvl in @data.levels
61 | if lvl && lvl.indexOf('r') != -1 &&
62 | lvl.indexOf('g') != -1 &&
63 | lvl.indexOf('b') != -1
64 | count++
65 | count
66 | is_unlocked:(level)=>
67 | switch level
68 | when 95 then @star_count() is 30
69 | when 96 then @star_count() is 45
70 | when 97 then @star_count() is 60
71 | when 98 then @star_count() is 75
72 | when 99 then @star_count() is 98
73 | when 0 then true
74 | else
75 | @gem_count() >= (level*3)-2
76 | level_complete:(i,jewels,time)=>
77 | switch @mode
78 | when 'time'
79 | if jewels.red && jewels.green && jewels.blue
80 | @data.tlevels = [] unless @data.tlevels
81 | @data.tlevels[i] = time
82 | @save()
83 | when 'adventure'
84 | str = ['t']
85 | str.push 'r' if jewels.red || @level_status i, 'red'
86 | str.push 'g' if jewels.green || @level_status i, 'green'
87 | str.push 'b' if jewels.blue || @level_status i, 'blue'
88 | str = str.join ''
89 | @data.levels = [] unless @data.levels
90 | @data.levels[i] = str
91 | @save()
92 | reset:=>
93 | store.remove 'save'
94 | load:=>
95 | if save = store.get 'save'
96 | @data = JSON.parse LZString.decompressFromBase64(save)
97 | save:=>
98 | save = JSON.stringify(@data)
99 | save = LZString.compressToBase64 save
100 | store.set 'save', save
101 | save
102 |
--------------------------------------------------------------------------------
/src/components/editor/toolbar.coffee:
--------------------------------------------------------------------------------
1 | class Component.Editor.Toolbar
2 | constructor:->
3 | @btn_corner = new Component.Editor.PipeButton @, 'corner'
4 | @btn_double_corner = new Component.Editor.PipeButton @, 'double_corner'
5 | @btn_straight = new Component.Editor.PipeButton @, 'straight'
6 | @btn_cross = new Component.Editor.PipeButton @, 'cross'
7 |
8 | @btn_start = new Component.Editor.PipeButton @, 'start'
9 | @btn_end = new Component.Editor.PipeButton @, 'end'
10 | @btn_null = new Component.Editor.PipeButton @, 'null'
11 | reset:=>
12 | @alt = false
13 | @active = false
14 | @toolbar = null
15 | @hotkeys = null
16 | @hotkey_clear = null
17 | str:=>
18 | @["btn_#{@active}"].str()
19 | inputs:=>
20 | @hotkeys = []
21 | @shift = game.input.keyboard.addKey(Phaser.Keyboard.SHIFT)
22 | @hotkeys.push game.input.keyboard.addKey(Phaser.Keyboard.A)
23 | @hotkeys.push game.input.keyboard.addKey(Phaser.Keyboard.S)
24 | @hotkeys.push game.input.keyboard.addKey(Phaser.Keyboard.D)
25 | @hotkeys.push game.input.keyboard.addKey(Phaser.Keyboard.F)
26 | @hotkeys.push game.input.keyboard.addKey(Phaser.Keyboard.G)
27 | @hotkeys.push game.input.keyboard.addKey(Phaser.Keyboard.H)
28 | @hotkeys.push game.input.keyboard.addKey(Phaser.Keyboard.V)
29 | for hotkey,i in @hotkeys
30 | hotkey.onDown.add @hotkey(i)
31 |
32 | @hotkey_alt = game.input.keyboard.addKey(Phaser.Keyboard.T)
33 | @hotkey_alt.onDown.add @toggle_alt
34 |
35 | @hotkey_clear = game.input.keyboard.addKey(Phaser.Keyboard.TILDE)
36 | @hotkey_clear.onDown.add @clear_active
37 | toggle_alt:=>
38 | @alt = !@alt
39 | @grid.set_alt @alt
40 | @g.clear()
41 | if @alt
42 | @g.beginFill 0xe30202
43 | @g.drawEllipse 0, 0, 8, 8
44 | @g.endFill()
45 | get_active:=>
46 | @active
47 | set_active:(kind)=>
48 | @active = kind
49 | clear_active:=>
50 | @active = false
51 | @hotkey(null)()
52 | hotkey:(n)=>
53 | =>
54 | btns = [
55 | @btn_corner
56 | @btn_double_corner
57 | @btn_straight
58 | @btn_cross
59 | @btn_start
60 | @btn_end
61 | @btn_null
62 | ]
63 | for btn,i in btns
64 | if n is i
65 | shift = @shift.isDown
66 | btn.active(shift)
67 | else
68 | btn.inactive()
69 | create:(grid)=>
70 | @grid = grid
71 |
72 | @reset()
73 | @inputs()
74 |
75 | @g = game.make.graphics (6*48) + (6*8), 0
76 | @g.reset()
77 |
78 | @toolbar = game.add.group()
79 | @toolbar.addChild @btn_corner.create (0*48)
80 | @toolbar.addChild @btn_double_corner.create (1*48) + (1*8)
81 | @toolbar.addChild @btn_straight.create (2*48) + (2*8)
82 | @toolbar.addChild @btn_cross.create (3*48) + (3*8)
83 | @toolbar.addChild @btn_start.create (4*48) + (4*8)
84 | @toolbar.addChild @btn_end.create (5*48) + (5*8)
85 | @toolbar.addChild @btn_null.create (6*48) + (6*8)
86 | @toolbar.addChild @g
87 | @toolbar.x = 160
88 | @toolbar.y = game.world.height - 56
89 |
--------------------------------------------------------------------------------
/src/components/pipe.coffee:
--------------------------------------------------------------------------------
1 | class Component.Pipe
2 | constructor:(grid,pipe,ang1,ang2,index,jewel)->
3 | @index = index
4 | @grid = grid
5 | @pipe = pipe
6 | @angle_set = ang1
7 | @angle_rnd = ang2
8 | @tick = null
9 | @g = game.make.graphics 0, 0
10 | @pipe.addChild @g # stores the flowing graphic
11 |
12 | px = @grid.gridpx() / 2
13 | @jewel = null
14 | @jewel = new Component.Jewel jewel, @pipe.x+px, @pipe.y+px if jewel != 0
15 | @pipe.inputEnabled = true
16 | @pipe.events.onInputDown.add @onclick, this
17 |
18 | @pipe.scale.setTo @grid.gridscale(), @grid.gridscale()
19 | @pipe.anchor.setTo 0.5, 0.5
20 | @pipe.x += px
21 | @pipe.y += px
22 | @pipe.exists = true
23 | @per1 = 0 # progress 1
24 | @per2 = 0 # progress 2
25 | @rev1 = false # reverse flow of progress 1
26 | @rev2 = false # reverse flow of progress 2
27 | @done = false
28 | @g.clear()
29 | @set()
30 |
31 | @render()
32 | set_jewel:=>
33 | if @jewel is null
34 | @jewel = new Component.Jewel 1, @pipe.x, @pipe.y
35 | else
36 | @jewel.jewel.destroy()
37 | if @jewel.get_i() is 3
38 | @jewel = null
39 | else
40 | i = @jewel.get_i()
41 | i += 1
42 | @jewel = new Component.Jewel i, @pipe.x, @pipe.y
43 | get_index:=>
44 | @index
45 | flow:=>
46 | @grid.flow_dir
47 | update:(ondone)=>
48 | if (game.time.now - @tick > 10)
49 | @tick = game.time.now
50 | if @done
51 | ondone()
52 | else
53 | @tick_progress()
54 | @render()
55 | render:=>
56 | kind = @pipe.kind
57 | g = @pipe.children[0]
58 | angle = @angle()
59 | rev1 = false
60 | rev2 = false
61 | if @jewel && (@per1 > 50 || @per2 > 50)
62 | @jewel.collect(@grid)
63 | alt:(alt)=>
64 | ang = if @grid.get_alt() then @angle_rnd else @angle_set
65 | @pipe.angle = ang
66 | angle:=>
67 | # For some reason angles end up negative even though
68 | # we alawys add by 90. so we need to ensure its stays
69 | # positive
70 | angle = @pipe.angle
71 | angle = 360 + angle if angle < 0
72 | angle
73 | str:=>
74 | ang1 =
75 | switch @angle_rnd
76 | when 0 then 1
77 | when 90 then 2
78 | when 180 then 3
79 | when 270 then 4
80 | ang2 =
81 | switch @angle_set
82 | when 0 then 1
83 | when 90 then 2
84 | when 180 then 3
85 | when 270 then 4
86 | jewel = if @jewel then @jewel.get_i() else 0
87 | "#{@pipe.frame}#{ang1}#{ang2}#{jewel}"
88 | set_angle:=>
89 | if @grid.get_alt()
90 | @angle_rnd = @angle()
91 | else
92 | @angle_set = @angle()
93 | set:=>
94 | ang = if @grid.get_alt() then @angle_rnd else @angle_set
95 | @pipe.angle = ang
96 | onclick:(pipe)=>
97 | if editor != false && @grid.is_toolbar_active()
98 | px = @grid.gridpx() / 2
99 | @grid.onclick_cell pipe.x-px, pipe.y-px
100 | else
101 | unless @grid.rotating_pipe() ||
102 | @kind in ['start','end'] ||
103 | @per1 > 0 ||
104 | @per2 > 0
105 | @grid.set_rotating pipe
106 |
--------------------------------------------------------------------------------
/src/lib/store.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2010-2016 Marcus Westin */
2 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.store = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;odocument.w=window"+a+'>'),u.close(),c=u.w.frames[0].document,t=c.createElement("div")}catch(l){t=i.createElement("div"),c=i.body}var f=function(e){return function(){var n=Array.prototype.slice.call(arguments,0);n.unshift(t),c.appendChild(t),t.addBehavior("#default#userData"),t.load(o);var i=e.apply(r,n);return c.removeChild(t),i}},d=new RegExp("[!\"#$%&'()*+,/\\\\:;<=>?@[\\]^`{|}~]","g"),s=function(e){return e.replace(/^d/,"___$&").replace(d,"___")};r.set=f(function(e,t,n){return t=s(t),void 0===n?r.remove(t):(e.setAttribute(t,r.serialize(n)),e.save(o),n)}),r.get=f(function(e,t,n){t=s(t);var i=r.deserialize(e.getAttribute(t));return void 0===i?n:i}),r.remove=f(function(e,t){t=s(t),e.removeAttribute(t),e.save(o)}),r.clear=f(function(e){var t=e.XMLDocument.documentElement.attributes;e.load(o);for(var r=t.length-1;r>=0;r--)e.removeAttribute(t[r].name);e.save(o)}),r.getAll=function(e){var t={};return r.forEach(function(e,r){t[e]=r}),t},r.forEach=f(function(e,t){for(var n,i=e.XMLDocument.documentElement.attributes,o=0;n=i[o];++o)t(n.name,r.deserialize(e.getAttribute(n.name)))})}try{var v="__storejs__";r.set(v,v),r.get(v)!=v&&(r.disabled=!0),r.remove(v)}catch(l){r.disabled=!0}return r.enabled=!r.disabled,r}();
5 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
6 | },{}]},{},[1])(1)
7 | });
8 |
--------------------------------------------------------------------------------
/src/components/level.coffee:
--------------------------------------------------------------------------------
1 | class window.Component.Level
2 | constructor:->
3 | create:(sprite,i)->
4 | @sprite = sprite
5 | @create_label()
6 | @create_count()
7 | @create_jewels()
8 | @set i
9 | create_count:=>
10 | @count = game.make.text 50, 72, '',
11 | font: '12px Verdana'
12 | fill: '#000'
13 | align: 'center'
14 | @count.anchor.setTo 0.5, 0.5
15 | @sprite.addChild @count
16 | create_label:=>
17 | @label = game.make.text 0,0, ''
18 | @label.anchor.setTo 0.5, 0.5
19 | @sprite.addChild @label
20 | create_jewels:=>
21 | inline = 30
22 | @red = game.make.sprite inline , 68, 'jewels'
23 | @green = game.make.sprite (128/2)-3 , 68, 'jewels'
24 | @blue = game.make.sprite 128-(inline+4), 68, 'jewels'
25 |
26 | scale = 0.7
27 | @red.scale.setTo scale, scale
28 | @green.scale.setTo scale, scale
29 | @blue.scale.setTo scale, scale
30 |
31 | anchor = 0.5
32 | @red.anchor.setTo anchor, anchor
33 | @green.anchor.setTo anchor, anchor
34 | @blue.anchor.setTo anchor, anchor
35 |
36 | @sprite.addChild @red
37 | @sprite.addChild @blue
38 | @sprite.addChild @green
39 | set:(i)=>
40 | @sprite.i = i
41 | if _d.is_unlocked(i)
42 | switch _d.mode
43 | when 'time' then @unlocked_time i
44 | when 'adventure' then @unlocked_adventure i
45 | else
46 | @locked i
47 | locked:(i)=>
48 | @label.visible = false
49 | @red.visible = false
50 | @green.visible = false
51 | @blue.visible = false
52 |
53 | ids = [95,96,97,98,99]
54 | @sprite.frame = if i in ids then 3 else 1
55 | count = _d.needed_to_unlock i
56 | @count.setText count
57 | @count.visible = true
58 | unlocked_time:(i)=>
59 | @sprite.frame = 4
60 | @red.visible = false
61 | @count.visible = false
62 | @green.visible = false
63 | @blue.visible = false
64 |
65 |
66 | @label.setText "#{i+1}"
67 | @label.visible = true
68 | @label.x = @sprite.width / 2
69 | @label.y = (@sprite.height / 2) - 8
70 | @label.font = 'Verdana'
71 | @label.fontSize = 18
72 | @label.fill = '#000'
73 | @label.align = 'center'
74 |
75 | @sprite.inputEnabled = true
76 | @sprite.events.onInputDown.add @onclick, this
77 |
78 | t = _d.tlevel(i)
79 | if t != null && t != undefined
80 | @sprite.frame = 5
81 |
82 | unlocked_adventure:(i)=>
83 | @sprite.frame = 0
84 | @count.visible = false
85 | @red.visible = true
86 | @green.visible = true
87 | @blue.visible = true
88 |
89 | @red.frame = @jewel_color i, 'red'
90 | @green.frame = @jewel_color i, 'green'
91 | @blue.frame = @jewel_color i, 'blue'
92 |
93 | @label.setText "#{i+1}"
94 | @label.visible = true
95 | @label.x = @sprite.width / 2
96 | @label.y = (@sprite.height / 2) - 14
97 | @label.font = 'Verdana'
98 | @label.fontSize = 18
99 | @label.fill = '#fff'
100 | @label.align = 'center'
101 |
102 | if _d.level_status(i,'red') &&
103 | _d.level_status(i,'green') &&
104 | _d.level_status(i,'blue')
105 | @sprite.frame = 2
106 | @label.fill = '#000'
107 |
108 | @sprite.inputEnabled = true
109 | @sprite.events.onInputDown.add @onclick, this
110 |
111 | jewel_color:(i,kind)=>
112 | if _d.level_status(i,kind)
113 | switch kind
114 | when 'red' then 1
115 | when 'green' then 2
116 | when 'blue' then 3
117 | else
118 | 0
119 | onclick:=>
120 | _d.level = @sprite.i
121 | game.state.start 'play'
122 |
--------------------------------------------------------------------------------
/src/states/menu.coffee:
--------------------------------------------------------------------------------
1 | red = null
2 | blue = null
3 |
4 | g = null
5 |
6 | part = 1
7 | rev = false
8 |
9 | per = 0
10 |
11 | tick = null
12 |
13 | cal_p1 = (n)=>
14 | p = if per >= 100 then 100 else per
15 | p = 0 if p < 0
16 | v = (n / 100) * p
17 | cal_p2 = (n)=>
18 | p =
19 | if per >= 200 then 100
20 | else if per >= 100 && per < 200 then per-100
21 | else
22 | 0
23 | v = (n / 100) * p
24 | cal_p3 = (n)=>
25 | p =
26 | if per >= 300 then 100
27 | else if per >= 200 && per < 300 then per-200
28 | else
29 | 0
30 | v = (n / 100) * p
31 | cal_p4 = (n)=>
32 | p =
33 | if per >= 400 then 100
34 | else if per >= 300 && per < 400 then per-300
35 | else
36 | 0
37 | v = (n / 100) * p
38 |
39 |
40 | animate = =>
41 | per += 10
42 | g.clear()
43 | g.lineStyle 8, 0xffd900
44 | switch part
45 | when 1 then animate1()
46 | when 2 then animate2()
47 | when 3 then animate3()
48 | when 4 then animate4()
49 | if per >= 400
50 | if rev
51 | if part is 4
52 | part = 1
53 | else
54 | part++
55 | rev = !rev
56 | per = 0
57 |
58 | animate1 = =>
59 | yoff = 7*64
60 |
61 | v = cal_p1(90)
62 | if rev
63 | g.arc 0, yoff+64, 32, game.math.degToRad(270+v), game.math.degToRad(360), false
64 | else
65 | g.arc 0, yoff+64, 32, game.math.degToRad(270), game.math.degToRad(270+v), false
66 |
67 | v = cal_p2(90)
68 | if rev
69 | g.arc 64, yoff+64, 32, game.math.degToRad(180-v), game.math.degToRad(90), true
70 | else
71 | g.arc 64, yoff+64, 32, game.math.degToRad(180), game.math.degToRad(180-v), true
72 |
73 | v = cal_p3(64)
74 | if rev
75 | g.moveTo 128 , 64+32+yoff
76 | g.lineTo 64+v, 64+32+yoff
77 | else
78 | g.moveTo 64 , 64+32+yoff
79 | g.lineTo 64+v, 64+32+yoff
80 |
81 | v = cal_p4(90)
82 | if rev
83 | g.arc 128, 128+yoff, 32, game.math.degToRad(270+v), game.math.degToRad(360), false
84 | else
85 | g.arc 128, 128+yoff, 32, game.math.degToRad(270), game.math.degToRad(270+v), false
86 |
87 | animate2 = =>
88 | xoff = 9*64
89 | yoff = 7*64
90 |
91 | v = cal_p1(64)
92 | if rev
93 | g.moveTo xoff+32, yoff+128-v
94 | g.lineTo xoff+32, yoff+64
95 | else
96 | g.moveTo xoff+32, yoff+128
97 | g.lineTo xoff+32, yoff+128-v
98 |
99 | v = cal_p2(90)
100 | if rev
101 | g.arc xoff+64, yoff+64, 32, game.math.degToRad(180+v), game.math.degToRad(270), false
102 | else
103 | g.arc xoff+64, yoff+64, 32, game.math.degToRad(180), game.math.degToRad(180+v), false
104 |
105 | v = cal_p3(64)
106 | if rev
107 | g.moveTo xoff+128 , 32+yoff
108 | g.lineTo xoff+64+v, 32+yoff
109 | else
110 | g.moveTo xoff+64 , 32+yoff
111 | g.lineTo xoff+64+v, 32+yoff
112 |
113 | v = cal_p4(64)
114 | if rev
115 | g.moveTo xoff+64+128 , 32+yoff
116 | g.lineTo xoff+64+64+v, 32+yoff
117 | else
118 | g.moveTo xoff+64+64 , 32+yoff
119 | g.lineTo xoff+64+64+v, 32+yoff
120 |
121 |
122 | animate3 = =>
123 | xoff = 8*64
124 |
125 | v = cal_p1(90)
126 | if rev
127 | g.arc xoff+128+64, 0, 32, game.math.degToRad(v), game.math.degToRad(90), false
128 | else
129 | g.arc xoff+128+64, 0, 32, game.math.degToRad(0), game.math.degToRad(v), false
130 |
131 | v = cal_p2(64)
132 | if rev
133 | g.moveTo xoff+128 , 32
134 | g.lineTo xoff+128+64-v, 32
135 | else
136 | g.moveTo xoff+128+64 , 32
137 | g.lineTo xoff+128+64-v, 32
138 |
139 | v = cal_p3(64)
140 | if rev
141 | g.moveTo xoff+64 , 32
142 | g.lineTo xoff+128-v, 32
143 | else
144 | g.moveTo xoff+128 , 32
145 | g.lineTo xoff+128-v, 32
146 |
147 | v = cal_p4(90)
148 | if rev
149 | g.arc xoff+64, 0, 32, game.math.degToRad(90+v), game.math.degToRad(180), false
150 | else
151 | g.arc xoff+64, 0, 32, game.math.degToRad(90), game.math.degToRad(90+v), false
152 |
153 | animate4 = =>
154 | v = cal_p1(90)
155 | if rev
156 | g.arc 128, 0, 32, game.math.degToRad(v), game.math.degToRad(90), false
157 | else
158 | g.arc 128, 0, 32, game.math.degToRad(0), game.math.degToRad(v), false
159 |
160 | v = cal_p2(64)
161 | if rev
162 | g.moveTo 64 , 32
163 | g.lineTo 128-v, 32
164 | else
165 | g.moveTo 128 , 32
166 | g.lineTo 128-v, 32
167 |
168 | v = cal_p3(90)
169 | if rev
170 | g.arc 64, 64, 32, game.math.degToRad(180), game.math.degToRad(270-v), false
171 | else
172 | g.arc 64, 64, 32, game.math.degToRad(270-v), game.math.degToRad(270), false
173 |
174 | v = cal_p4(90)
175 | if rev
176 | g.arc 0, 64, 32, game.math.degToRad(v), game.math.degToRad(90), false
177 | else
178 | g.arc 0, 64, 32, game.math.degToRad(0), game.math.degToRad(v), false
179 |
180 |
181 |
182 | start_adventure_onclick = ->
183 | _d.mode = 'adventure'
184 | game.state.start 'levels'
185 |
186 | start_time_onclick = ->
187 | _d.mode = 'time'
188 | game.state.start 'levels'
189 |
190 | tween_red = =>
191 | tween = game.add.tween(red.scale)
192 | tween.to {x: 1.3, y: 1.3},250, Phaser.Easing.Linear.None
193 | tween.to {x: 1, y: 1} ,250, Phaser.Easing.Linear.None
194 | tween.loop true
195 | tween.start()
196 |
197 | tween_blue = =>
198 | tween = game.add.tween(blue.scale)
199 | tween.to {x: 1.3, y: 1.3},250, Phaser.Easing.Linear.None
200 | tween.to {x: 1, y: 1} ,250, Phaser.Easing.Linear.None
201 | tween.loop true
202 | tween.start()
203 |
204 | create = ->
205 | game.add.sprite 0 , 0 , 'menu'
206 |
207 | tick = game.time.now
208 | g = game.add.graphics 0, 0
209 |
210 | btn = game.add.button (4*64)+32, (4*64)+32, 'button_adventure', start_adventure_onclick, this, 0,0,0
211 | btn = game.add.button (4*64)+32, (5*64)+32, 'button_time' , start_time_onclick , this, 0,0,0
212 |
213 | tween = game.add
214 |
215 | red = game.add.sprite (2*64)+32, (2*64)+32, 'jewels', 1
216 | blue = game.add.sprite (9*64)+32, (2*64)+32, 'jewels', 3
217 | red.anchor.setTo 0.5, 0.5
218 | blue.anchor.setTo 0.5, 0.5
219 |
220 | tween_red()
221 | game.time.events.add(Phaser.Timer.SECOND * 0.25, tween_blue, this)
222 |
223 | update = ->
224 | if (game.time.now - tick > 10)
225 | tick = game.time.now
226 | animate()
227 |
228 | _states.menu =
229 | create: create
230 | update: update
231 |
--------------------------------------------------------------------------------
/src/components/grid.coffee:
--------------------------------------------------------------------------------
1 | class Component.Grid
2 | constructor:(spill,counter,toolbar,success,fail)->
3 | @toolbar = toolbar
4 | @counter = counter
5 | @spill = spill
6 | @callback_success = success
7 | @callback_fail = fail
8 | @reset()
9 | reset:=>
10 | @jewels =
11 | green: false
12 | blue : false
13 | red : false
14 | @alt = false
15 | @size = null
16 | @level = null
17 | @speed = null
18 | @flow_dir = null
19 | @flowing = false
20 | @grid = null
21 | @pipes = []
22 | @pipe_start = null
23 | @pipe_end = null
24 | @_pipe = null #current pipe returned by flowing
25 | @rotating =
26 | pipe: null
27 | angle: 0
28 | get_alt:=>
29 | @alt
30 | set_jewel:(i)=>
31 | if @pipes[i]
32 | @pipes[i].set_jewel()
33 | set_alt:(alt)=>
34 | @alt = alt
35 | for pipe in @pipes
36 | if pipe
37 | pipe.alt alt
38 | backup:=>
39 | data = []
40 | for pipe in @pipes
41 | v = if pipe then pipe.str() else 'null'
42 | data.push v
43 | data.join(',')
44 | is_toolbar_active:=>
45 | @toolbar.get_active()
46 | collect_jewel:(kind)=>
47 | @jewels[kind] = true
48 | start_flowing:=>
49 | return if @flowing
50 | @flowing = true
51 | @_pipe = @pipe_start
52 | @flow_dir = @_pipe.dir()
53 | @_pipe.tick = game.time.now
54 | update_rotating:=>
55 | if @rotating.pipe
56 | if @rotating.angle is 90
57 | @stop_rotating()
58 | else
59 | @rotate()
60 | rotating_pipe:=>
61 | @rotating.pipe
62 | set_rotating:(pipe)=>
63 | if _d.get_sound()
64 | switch @turnb
65 | when 1 then @turn1.play()
66 | when 2 then @turn2.play()
67 | when 3 then @turn3.play()
68 | when 4 then @turn4.play()
69 | @turnb++
70 | @turnb = 1 if @turnb is 5
71 | @rotating.pipe = pipe
72 | stop_rotating:=>
73 | @pipes[@rotating.pipe.i].set_angle()
74 | @rotating =
75 | pipe: null
76 | angle: 0
77 | rotate:=>
78 | @rotating.angle += 10
79 | @rotating.pipe.angle += 10
80 | get:(i)=>
81 | switch @flow_dir
82 | when 'east' then @pipes[i+1]
83 | when 'south' then @pipes[i+@griddim()[0]]
84 | when 'west' then @pipes[i-1]
85 | when 'north' then @pipes[i-@griddim()[0]]
86 | next:=>
87 | pipe_last = @_pipe
88 | pipe_next = null
89 | @_pipe = null
90 | if pipe_last.kind is 'end'
91 | @callback_success(@jewels,@counter.time())
92 | else
93 | pipe_next = @get pipe_last.get_index()
94 | if pipe_next && pipe_next.dir()
95 | @_pipe = pipe_next
96 | @_pipe.done = false
97 | @_pipe.tick = game.time.now
98 | @flow_dir = pipe_next.dir()
99 | else
100 | @spill.spill @callback_fail, pipe_last, @flow_dir
101 | onclick_cell:(x,y)=>
102 | ny = if y > 0 then y / @gridpx() else y
103 | nx = if x > 0 then x / @gridpx() else x
104 | i = if ny is 0 then nx else (ny * @griddim()[0]) + nx
105 | old_child = @grid.children[i]
106 | new_child = game.make.sprite 0, 0, 'pipes'
107 | @grid.replace old_child, new_child
108 | child = @grid.children[i]
109 | child.x = x
110 | child.y = y
111 | @create_pipe @toolbar.str(), i
112 | set_tile:(tile)=>
113 | tile.scale.setTo @gridscale(), @gridscale()
114 | if window.editor != false
115 | tile.inputEnabled = true
116 | fun = =>
117 | @onclick_cell tile.x, tile.y
118 | tile.events.onInputDown.add fun, this
119 | create_bg:=>
120 | # Nice background tiles.
121 | group = game.add.group()
122 | group.createMultiple @gridsize(), 'tile', 0, true
123 | group.align @griddim()[0], @griddim()[1], @gridpx(), @gridpx()
124 | for tile in group.children
125 | @set_tile tile
126 | gridscale:=>
127 | switch @size
128 | when 'large' then 0.75
129 | when 'med' then 1
130 | when 'small' then 1.5
131 | griddim:=>
132 | switch @size
133 | when 'large' then [18,10]
134 | when 'med' then [12,8]
135 | when 'small' then [8,5]
136 | gridpx:=>
137 | switch @size
138 | when 'large' then 48
139 | when 'med' then 64
140 | when 'small' then 96
141 | gridsize:=>
142 | switch @size
143 | when 'large' then 180
144 | when 'med' then 96
145 | when 'small' then 40
146 | create_grid:=>
147 | @grid = game.add.group()
148 | @grid.createMultiple @gridsize(), 'pipes', 0, false
149 | @grid.align @griddim()[0], @griddim()[1], @gridpx(), @gridpx()
150 | create_pipes:=>
151 | @turnb = 1
152 | @turn1 = game.add.audio 'turn1'
153 | @turn2 = game.add.audio 'turn2'
154 | @turn3 = game.add.audio 'turn3'
155 | @turn4 = game.add.audio 'turn4'
156 |
157 | pipes = if _l[@level] then _l[@level].pipes else new Array(@gridsize())
158 | for str,i in pipes
159 | @create_pipe str, i,
160 | create_pipe:(str,i)=>
161 | pipe = @grid.children[i]
162 | if str is null || str is undefined
163 | pipe.exists = false
164 | @pipes[i] = null
165 | else
166 | pipe.i = i
167 | data = str.split('')
168 | frame = if data[0] then data[0] else null
169 | ang1 = if data[1] then @angle(data[1]) else 0
170 | ang2 = if data[1] then @angle(data[2]) else 0
171 | jewel = if data[3] then parseInt(data[3]) else 0
172 | @pipes[i] =
173 | switch @kind(frame)
174 | when 'start'
175 | @pipe_start = new Component.PipeStart @, pipe, ang1,ang2, i, 0
176 | @pipe_start
177 | when 'end'
178 | @pipe_end = new Component.PipeEnd @, pipe, ang1,ang2, i, 0
179 | @pipe_end
180 | when 'corner' then new Component.PipeCorner @, pipe, ang1,ang2, i, jewel
181 | when 'double_corner' then new Component.PipeDoubleCorner @, pipe, ang1,ang2, i, jewel
182 | when 'straight' then new Component.PipeStraight @, pipe, ang1,ang2, i, jewel
183 | when 'cross' then new Component.PipeCross @, pipe, ang1,ang2, i, jewel
184 | create:(level)=>
185 | @reset()
186 | @level = level
187 |
188 | @size =
189 | if _l[@level]
190 | len = _l[@level].pipes.length
191 | switch len
192 | when 180 then 'large'
193 | when 96 then 'med'
194 | when 40 then 'small'
195 | else
196 | window.editor_size
197 | console.log 'size', @size
198 | @speed =
199 | switch _d.mode
200 | when 'adventure' then 20
201 | when 'time'
202 | if _l[@level] then _l[@level].speed else 10
203 | else
204 | 20
205 | @create_bg()
206 | @spill.create() # had to do it here it ensure correct layering.
207 | @create_grid()
208 | @create_pipes()
209 | update:=>
210 | @_pipe.update @next if @_pipe
211 | @update_rotating()
212 | kind:(frame)=>
213 | switch parseInt(frame)
214 | when 4 then 'start'
215 | when 5 then 'end'
216 | when 0 then 'corner'
217 | when 1 then 'double_corner'
218 | when 2 then 'straight'
219 | when 3 then 'cross'
220 | angle:(i)=>
221 | switch parseInt(i)
222 | when 1 then 0
223 | when 2 then 90
224 | when 3 then 180
225 | when 4 then 270
226 |
--------------------------------------------------------------------------------
/src/lib/lz.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2013 Pieroxy
2 | // This work is free. You can redistribute it and/or modify it
3 | // under the terms of the WTFPL, Version 2
4 | // For more information see LICENSE.txt or http://www.wtfpl.net/
5 | //
6 | // For more information, the home page:
7 | // http://pieroxy.net/blog/pages/lz-string/testing.html
8 | //
9 | // LZ-based compression algorithm, version 1.4.4
10 | var LZString = (function() {
11 |
12 | // private property
13 | var f = String.fromCharCode;
14 | var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
15 | var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
16 | var baseReverseDic = {};
17 |
18 | function getBaseValue(alphabet, character) {
19 | if (!baseReverseDic[alphabet]) {
20 | baseReverseDic[alphabet] = {};
21 | for (var i=0 ; i>> 8;
66 | buf[i*2+1] = current_value % 256;
67 | }
68 | return buf;
69 | },
70 |
71 | //decompress from uint8array (UCS-2 big endian format)
72 | decompressFromUint8Array:function (compressed) {
73 | if (compressed===null || compressed===undefined){
74 | return LZString.decompress(compressed);
75 | } else {
76 | var buf=new Array(compressed.length/2); // 2 bytes per character
77 | for (var i=0, TotalLen=buf.length; i> 1;
159 | }
160 | } else {
161 | value = 1;
162 | for (i=0 ; i> 1;
184 | }
185 | }
186 | context_enlargeIn--;
187 | if (context_enlargeIn == 0) {
188 | context_enlargeIn = Math.pow(2, context_numBits);
189 | context_numBits++;
190 | }
191 | delete context_dictionaryToCreate[context_w];
192 | } else {
193 | value = context_dictionary[context_w];
194 | for (i=0 ; i> 1;
204 | }
205 |
206 |
207 | }
208 | context_enlargeIn--;
209 | if (context_enlargeIn == 0) {
210 | context_enlargeIn = Math.pow(2, context_numBits);
211 | context_numBits++;
212 | }
213 | // Add wc to the dictionary.
214 | context_dictionary[context_wc] = context_dictSize++;
215 | context_w = String(context_c);
216 | }
217 | }
218 |
219 | // Output the code for w.
220 | if (context_w !== "") {
221 | if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
222 | if (context_w.charCodeAt(0)<256) {
223 | for (i=0 ; i> 1;
244 | }
245 | } else {
246 | value = 1;
247 | for (i=0 ; i> 1;
269 | }
270 | }
271 | context_enlargeIn--;
272 | if (context_enlargeIn == 0) {
273 | context_enlargeIn = Math.pow(2, context_numBits);
274 | context_numBits++;
275 | }
276 | delete context_dictionaryToCreate[context_w];
277 | } else {
278 | value = context_dictionary[context_w];
279 | for (i=0 ; i> 1;
289 | }
290 |
291 |
292 | }
293 | context_enlargeIn--;
294 | if (context_enlargeIn == 0) {
295 | context_enlargeIn = Math.pow(2, context_numBits);
296 | context_numBits++;
297 | }
298 | }
299 |
300 | // Mark the end of the stream
301 | value = 2;
302 | for (i=0 ; i> 1;
312 | }
313 |
314 | // Flush the last char
315 | while (true) {
316 | context_data_val = (context_data_val << 1);
317 | if (context_data_position == bitsPerChar-1) {
318 | context_data.push(getCharFromInt(context_data_val));
319 | break;
320 | }
321 | else context_data_position++;
322 | }
323 | return context_data.join('');
324 | },
325 |
326 | decompress: function (compressed) {
327 | if (compressed == null) return "";
328 | if (compressed == "") return null;
329 | return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); });
330 | },
331 |
332 | _decompress: function (length, resetValue, getNextValue) {
333 | var dictionary = [],
334 | next,
335 | enlargeIn = 4,
336 | dictSize = 4,
337 | numBits = 3,
338 | entry = "",
339 | result = [],
340 | i,
341 | w,
342 | bits, resb, maxpower, power,
343 | c,
344 | data = {val:getNextValue(0), position:resetValue, index:1};
345 |
346 | for (i = 0; i < 3; i += 1) {
347 | dictionary[i] = i;
348 | }
349 |
350 | bits = 0;
351 | maxpower = Math.pow(2,2);
352 | power=1;
353 | while (power!=maxpower) {
354 | resb = data.val & data.position;
355 | data.position >>= 1;
356 | if (data.position == 0) {
357 | data.position = resetValue;
358 | data.val = getNextValue(data.index++);
359 | }
360 | bits |= (resb>0 ? 1 : 0) * power;
361 | power <<= 1;
362 | }
363 |
364 | switch (next = bits) {
365 | case 0:
366 | bits = 0;
367 | maxpower = Math.pow(2,8);
368 | power=1;
369 | while (power!=maxpower) {
370 | resb = data.val & data.position;
371 | data.position >>= 1;
372 | if (data.position == 0) {
373 | data.position = resetValue;
374 | data.val = getNextValue(data.index++);
375 | }
376 | bits |= (resb>0 ? 1 : 0) * power;
377 | power <<= 1;
378 | }
379 | c = f(bits);
380 | break;
381 | case 1:
382 | bits = 0;
383 | maxpower = Math.pow(2,16);
384 | power=1;
385 | while (power!=maxpower) {
386 | resb = data.val & data.position;
387 | data.position >>= 1;
388 | if (data.position == 0) {
389 | data.position = resetValue;
390 | data.val = getNextValue(data.index++);
391 | }
392 | bits |= (resb>0 ? 1 : 0) * power;
393 | power <<= 1;
394 | }
395 | c = f(bits);
396 | break;
397 | case 2:
398 | return "";
399 | }
400 | dictionary[3] = c;
401 | w = c;
402 | result.push(c);
403 | while (true) {
404 | if (data.index > length) {
405 | return "";
406 | }
407 |
408 | bits = 0;
409 | maxpower = Math.pow(2,numBits);
410 | power=1;
411 | while (power!=maxpower) {
412 | resb = data.val & data.position;
413 | data.position >>= 1;
414 | if (data.position == 0) {
415 | data.position = resetValue;
416 | data.val = getNextValue(data.index++);
417 | }
418 | bits |= (resb>0 ? 1 : 0) * power;
419 | power <<= 1;
420 | }
421 |
422 | switch (c = bits) {
423 | case 0:
424 | bits = 0;
425 | maxpower = Math.pow(2,8);
426 | power=1;
427 | while (power!=maxpower) {
428 | resb = data.val & data.position;
429 | data.position >>= 1;
430 | if (data.position == 0) {
431 | data.position = resetValue;
432 | data.val = getNextValue(data.index++);
433 | }
434 | bits |= (resb>0 ? 1 : 0) * power;
435 | power <<= 1;
436 | }
437 |
438 | dictionary[dictSize++] = f(bits);
439 | c = dictSize-1;
440 | enlargeIn--;
441 | break;
442 | case 1:
443 | bits = 0;
444 | maxpower = Math.pow(2,16);
445 | power=1;
446 | while (power!=maxpower) {
447 | resb = data.val & data.position;
448 | data.position >>= 1;
449 | if (data.position == 0) {
450 | data.position = resetValue;
451 | data.val = getNextValue(data.index++);
452 | }
453 | bits |= (resb>0 ? 1 : 0) * power;
454 | power <<= 1;
455 | }
456 | dictionary[dictSize++] = f(bits);
457 | c = dictSize-1;
458 | enlargeIn--;
459 | break;
460 | case 2:
461 | return result.join('');
462 | }
463 |
464 | if (enlargeIn == 0) {
465 | enlargeIn = Math.pow(2, numBits);
466 | numBits++;
467 | }
468 |
469 | if (dictionary[c]) {
470 | entry = dictionary[c];
471 | } else {
472 | if (c === dictSize) {
473 | entry = w + w.charAt(0);
474 | } else {
475 | return null;
476 | }
477 | }
478 | result.push(entry);
479 |
480 | // Add w+entry[0] to the dictionary.
481 | dictionary[dictSize++] = w + entry.charAt(0);
482 | enlargeIn--;
483 |
484 | w = entry;
485 |
486 | if (enlargeIn == 0) {
487 | enlargeIn = Math.pow(2, numBits);
488 | numBits++;
489 | }
490 |
491 | }
492 | }
493 | };
494 | return LZString;
495 | })();
496 |
497 | if (typeof define === 'function' && define.amd) {
498 | define(function () { return LZString; });
499 | } else if( typeof module !== 'undefined' && module != null ) {
500 | module.exports = LZString
501 | }
502 |
--------------------------------------------------------------------------------
/src/lib/mithril.js:
--------------------------------------------------------------------------------
1 | var m = (function app(window, undefined) {
2 | "use strict";
3 | var VERSION = "v0.2.1";
4 | function isFunction(object) {
5 | return typeof object === "function";
6 | }
7 | function isObject(object) {
8 | return type.call(object) === "[object Object]";
9 | }
10 | function isString(object) {
11 | return type.call(object) === "[object String]";
12 | }
13 | var isArray = Array.isArray || function (object) {
14 | return type.call(object) === "[object Array]";
15 | };
16 | var type = {}.toString;
17 | var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/;
18 | var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/;
19 | var noop = function () {};
20 |
21 | // caching commonly used variables
22 | var $document, $location, $requestAnimationFrame, $cancelAnimationFrame;
23 |
24 | // self invoking function needed because of the way mocks work
25 | function initialize(window) {
26 | $document = window.document;
27 | $location = window.location;
28 | $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout;
29 | $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout;
30 | }
31 |
32 | initialize(window);
33 |
34 | m.version = function() {
35 | return VERSION;
36 | };
37 |
38 | /**
39 | * @typedef {String} Tag
40 | * A string that looks like -> div.classname#id[param=one][param2=two]
41 | * Which describes a DOM node
42 | */
43 |
44 | /**
45 | *
46 | * @param {Tag} The DOM node tag
47 | * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs
48 | * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional)
49 | *
50 | */
51 | function m(tag, pairs) {
52 | for (var args = [], i = 1; i < arguments.length; i++) {
53 | args[i - 1] = arguments[i];
54 | }
55 | if (isObject(tag)) return parameterize(tag, args);
56 | var hasAttrs = pairs != null && isObject(pairs) && !("tag" in pairs || "view" in pairs || "subtree" in pairs);
57 | var attrs = hasAttrs ? pairs : {};
58 | var classAttrName = "class" in attrs ? "class" : "className";
59 | var cell = {tag: "div", attrs: {}};
60 | var match, classes = [];
61 | if (!isString(tag)) throw new Error("selector in m(selector, attrs, children) should be a string");
62 | while ((match = parser.exec(tag)) != null) {
63 | if (match[1] === "" && match[2]) cell.tag = match[2];
64 | else if (match[1] === "#") cell.attrs.id = match[2];
65 | else if (match[1] === ".") classes.push(match[2]);
66 | else if (match[3][0] === "[") {
67 | var pair = attrParser.exec(match[3]);
68 | cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true);
69 | }
70 | }
71 |
72 | var children = hasAttrs ? args.slice(1) : args;
73 | if (children.length === 1 && isArray(children[0])) {
74 | cell.children = children[0];
75 | }
76 | else {
77 | cell.children = children;
78 | }
79 |
80 | for (var attrName in attrs) {
81 | if (attrs.hasOwnProperty(attrName)) {
82 | if (attrName === classAttrName && attrs[attrName] != null && attrs[attrName] !== "") {
83 | classes.push(attrs[attrName]);
84 | cell.attrs[attrName] = ""; //create key in correct iteration order
85 | }
86 | else cell.attrs[attrName] = attrs[attrName];
87 | }
88 | }
89 | if (classes.length) cell.attrs[classAttrName] = classes.join(" ");
90 |
91 | return cell;
92 | }
93 | function forEach(list, f) {
94 | for (var i = 0; i < list.length && !f(list[i], i++);) {}
95 | }
96 | function forKeys(list, f) {
97 | forEach(list, function (attrs, i) {
98 | return (attrs = attrs && attrs.attrs) && attrs.key != null && f(attrs, i);
99 | });
100 | }
101 | // This function was causing deopts in Chrome.
102 | function dataToString(data) {
103 | //data.toString() might throw or return null if data is the return value of Console.log in Firefox (behavior depends on version)
104 | try {
105 | if (data == null || data.toString() == null) return "";
106 | } catch (e) {
107 | return "";
108 | }
109 | return data;
110 | }
111 | // This function was causing deopts in Chrome.
112 | function injectTextNode(parentElement, first, index, data) {
113 | try {
114 | insertNode(parentElement, first, index);
115 | first.nodeValue = data;
116 | } catch (e) {} //IE erroneously throws error when appending an empty text node after a null
117 | }
118 |
119 | function flatten(list) {
120 | //recursively flatten array
121 | for (var i = 0; i < list.length; i++) {
122 | if (isArray(list[i])) {
123 | list = list.concat.apply([], list);
124 | //check current index again and flatten until there are no more nested arrays at that index
125 | i--;
126 | }
127 | }
128 | return list;
129 | }
130 |
131 | function insertNode(parentElement, node, index) {
132 | parentElement.insertBefore(node, parentElement.childNodes[index] || null);
133 | }
134 |
135 | var DELETION = 1, INSERTION = 2, MOVE = 3;
136 |
137 | function handleKeysDiffer(data, existing, cached, parentElement) {
138 | forKeys(data, function (key, i) {
139 | existing[key = key.key] = existing[key] ? {
140 | action: MOVE,
141 | index: i,
142 | from: existing[key].index,
143 | element: cached.nodes[existing[key].index] || $document.createElement("div")
144 | } : {action: INSERTION, index: i};
145 | });
146 | var actions = [];
147 | for (var prop in existing) actions.push(existing[prop]);
148 | var changes = actions.sort(sortChanges), newCached = new Array(cached.length);
149 | newCached.nodes = cached.nodes.slice();
150 |
151 | forEach(changes, function (change) {
152 | var index = change.index;
153 | if (change.action === DELETION) {
154 | clear(cached[index].nodes, cached[index]);
155 | newCached.splice(index, 1);
156 | }
157 | if (change.action === INSERTION) {
158 | var dummy = $document.createElement("div");
159 | dummy.key = data[index].attrs.key;
160 | insertNode(parentElement, dummy, index);
161 | newCached.splice(index, 0, {
162 | attrs: {key: data[index].attrs.key},
163 | nodes: [dummy]
164 | });
165 | newCached.nodes[index] = dummy;
166 | }
167 |
168 | if (change.action === MOVE) {
169 | var changeElement = change.element;
170 | var maybeChanged = parentElement.childNodes[index];
171 | if (maybeChanged !== changeElement && changeElement !== null) {
172 | parentElement.insertBefore(changeElement, maybeChanged || null);
173 | }
174 | newCached[index] = cached[change.from];
175 | newCached.nodes[index] = changeElement;
176 | }
177 | });
178 |
179 | return newCached;
180 | }
181 |
182 | function diffKeys(data, cached, existing, parentElement) {
183 | var keysDiffer = data.length !== cached.length;
184 | if (!keysDiffer) {
185 | forKeys(data, function (attrs, i) {
186 | var cachedCell = cached[i];
187 | return keysDiffer = cachedCell && cachedCell.attrs && cachedCell.attrs.key !== attrs.key;
188 | });
189 | }
190 |
191 | return keysDiffer ? handleKeysDiffer(data, existing, cached, parentElement) : cached;
192 | }
193 |
194 | function diffArray(data, cached, nodes) {
195 | //diff the array itself
196 |
197 | //update the list of DOM nodes by collecting the nodes from each item
198 | forEach(data, function (_, i) {
199 | if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes);
200 | })
201 | //remove items from the end of the array if the new array is shorter than the old one. if errors ever happen here, the issue is most likely
202 | //a bug in the construction of the `cached` data structure somewhere earlier in the program
203 | forEach(cached.nodes, function (node, i) {
204 | if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]);
205 | })
206 | if (data.length < cached.length) cached.length = data.length;
207 | cached.nodes = nodes;
208 | }
209 |
210 | function buildArrayKeys(data) {
211 | var guid = 0;
212 | forKeys(data, function () {
213 | forEach(data, function (attrs) {
214 | if ((attrs = attrs && attrs.attrs) && attrs.key == null) attrs.key = "__mithril__" + guid++;
215 | })
216 | return 1;
217 | });
218 | }
219 |
220 | function maybeRecreateObject(data, cached, dataAttrKeys) {
221 | //if an element is different enough from the one in cache, recreate it
222 | if (data.tag !== cached.tag ||
223 | dataAttrKeys.sort().join() !== Object.keys(cached.attrs).sort().join() ||
224 | data.attrs.id !== cached.attrs.id ||
225 | data.attrs.key !== cached.attrs.key ||
226 | (m.redraw.strategy() === "all" && (!cached.configContext || cached.configContext.retain !== true)) ||
227 | (m.redraw.strategy() === "diff" && cached.configContext && cached.configContext.retain === false)) {
228 | if (cached.nodes.length) clear(cached.nodes);
229 | if (cached.configContext && isFunction(cached.configContext.onunload)) cached.configContext.onunload();
230 | if (cached.controllers) {
231 | forEach(cached.controllers, function (controller) {
232 | if (controller.unload) controller.onunload({preventDefault: noop});
233 | });
234 | }
235 | }
236 | }
237 |
238 | function getObjectNamespace(data, namespace) {
239 | return data.attrs.xmlns ? data.attrs.xmlns :
240 | data.tag === "svg" ? "http://www.w3.org/2000/svg" :
241 | data.tag === "math" ? "http://www.w3.org/1998/Math/MathML" :
242 | namespace;
243 | }
244 |
245 | function unloadCachedControllers(cached, views, controllers) {
246 | if (controllers.length) {
247 | cached.views = views;
248 | cached.controllers = controllers;
249 | forEach(controllers, function (controller) {
250 | if (controller.onunload && controller.onunload.$old) controller.onunload = controller.onunload.$old;
251 | if (pendingRequests && controller.onunload) {
252 | var onunload = controller.onunload;
253 | controller.onunload = noop;
254 | controller.onunload.$old = onunload;
255 | }
256 | });
257 | }
258 | }
259 |
260 | function scheduleConfigsToBeCalled(configs, data, node, isNew, cached) {
261 | //schedule configs to be called. They are called after `build`
262 | //finishes running
263 | if (isFunction(data.attrs.config)) {
264 | var context = cached.configContext = cached.configContext || {};
265 |
266 | //bind
267 | configs.push(function() {
268 | return data.attrs.config.call(data, node, !isNew, context, cached);
269 | });
270 | }
271 | }
272 |
273 | function buildUpdatedNode(cached, data, editable, hasKeys, namespace, views, configs, controllers) {
274 | var node = cached.nodes[0];
275 | if (hasKeys) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace);
276 | cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs);
277 | cached.nodes.intact = true;
278 |
279 | if (controllers.length) {
280 | cached.views = views;
281 | cached.controllers = controllers;
282 | }
283 |
284 | return node;
285 | }
286 |
287 | function handleNonexistentNodes(data, parentElement, index) {
288 | var nodes;
289 | if (data.$trusted) {
290 | nodes = injectHTML(parentElement, index, data);
291 | }
292 | else {
293 | nodes = [$document.createTextNode(data)];
294 | if (!parentElement.nodeName.match(voidElements)) insertNode(parentElement, nodes[0], index);
295 | }
296 |
297 | var cached = typeof data === "string" || typeof data === "number" || typeof data === "boolean" ? new data.constructor(data) : data;
298 | cached.nodes = nodes;
299 | return cached;
300 | }
301 |
302 | function reattachNodes(data, cached, parentElement, editable, index, parentTag) {
303 | var nodes = cached.nodes;
304 | if (!editable || editable !== $document.activeElement) {
305 | if (data.$trusted) {
306 | clear(nodes, cached);
307 | nodes = injectHTML(parentElement, index, data);
308 | }
309 | //corner case: replacing the nodeValue of a text node that is a child of a textarea/contenteditable doesn't work
310 | //we need to update the value property of the parent textarea or the innerHTML of the contenteditable element instead
311 | else if (parentTag === "textarea") {
312 | parentElement.value = data;
313 | }
314 | else if (editable) {
315 | editable.innerHTML = data;
316 | }
317 | else {
318 | //was a trusted string
319 | if (nodes[0].nodeType === 1 || nodes.length > 1) {
320 | clear(cached.nodes, cached);
321 | nodes = [$document.createTextNode(data)];
322 | }
323 | injectTextNode(parentElement, nodes[0], index, data);
324 | }
325 | }
326 | cached = new data.constructor(data);
327 | cached.nodes = nodes;
328 | return cached;
329 | }
330 |
331 | function handleText(cached, data, index, parentElement, shouldReattach, editable, parentTag) {
332 | //handle text nodes
333 | return cached.nodes.length === 0 ? handleNonexistentNodes(data, parentElement, index) :
334 | cached.valueOf() !== data.valueOf() || shouldReattach === true ?
335 | reattachNodes(data, cached, parentElement, editable, index, parentTag) :
336 | (cached.nodes.intact = true, cached);
337 | }
338 |
339 | function getSubArrayCount(item) {
340 | if (item.$trusted) {
341 | //fix offset of next element if item was a trusted string w/ more than one html element
342 | //the first clause in the regexp matches elements
343 | //the second clause (after the pipe) matches text nodes
344 | var match = item.match(/<[^\/]|\>\s*[^<]/g);
345 | if (match != null) return match.length;
346 | }
347 | else if (isArray(item)) {
348 | return item.length;
349 | }
350 | return 1;
351 | }
352 |
353 | function buildArray(data, cached, parentElement, index, parentTag, shouldReattach, editable, namespace, configs) {
354 | data = flatten(data);
355 | var nodes = [], intact = cached.length === data.length, subArrayCount = 0;
356 |
357 | //keys algorithm: sort elements without recreating them if keys are present
358 | //1) create a map of all existing keys, and mark all for deletion
359 | //2) add new keys to map and mark them for addition
360 | //3) if key exists in new list, change action from deletion to a move
361 | //4) for each key, handle its corresponding action as marked in previous steps
362 | var existing = {}, shouldMaintainIdentities = false;
363 | forKeys(cached, function (attrs, i) {
364 | shouldMaintainIdentities = true;
365 | existing[cached[i].attrs.key] = {action: DELETION, index: i};
366 | });
367 |
368 | buildArrayKeys(data);
369 | if (shouldMaintainIdentities) cached = diffKeys(data, cached, existing, parentElement);
370 | //end key algorithm
371 |
372 | var cacheCount = 0;
373 | //faster explicitly written
374 | for (var i = 0, len = data.length; i < len; i++) {
375 | //diff each item in the array
376 | var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs);
377 |
378 | if (item !== undefined) {
379 | intact = intact && item.nodes.intact;
380 | subArrayCount += getSubArrayCount(item);
381 | cached[cacheCount++] = item;
382 | }
383 | }
384 |
385 | if (!intact) diffArray(data, cached, nodes);
386 | return cached
387 | }
388 |
389 | function makeCache(data, cached, index, parentIndex, parentCache) {
390 | if (cached != null) {
391 | if (type.call(cached) === type.call(data)) return cached;
392 |
393 | if (parentCache && parentCache.nodes) {
394 | var offset = index - parentIndex, end = offset + (isArray(data) ? data : cached.nodes).length;
395 | clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end));
396 | } else if (cached.nodes) {
397 | clear(cached.nodes, cached);
398 | }
399 | }
400 |
401 | cached = new data.constructor();
402 | //if constructor creates a virtual dom element, use a blank object
403 | //as the base cached node instead of copying the virtual el (#277)
404 | if (cached.tag) cached = {};
405 | cached.nodes = [];
406 | return cached;
407 | }
408 |
409 | function constructNode(data, namespace) {
410 | return namespace === undefined ?
411 | data.attrs.is ? $document.createElement(data.tag, data.attrs.is) : $document.createElement(data.tag) :
412 | data.attrs.is ? $document.createElementNS(namespace, data.tag, data.attrs.is) : $document.createElementNS(namespace, data.tag);
413 | }
414 |
415 | function constructAttrs(data, node, namespace, hasKeys) {
416 | return hasKeys ? setAttributes(node, data.tag, data.attrs, {}, namespace) : data.attrs;
417 | }
418 |
419 | function constructChildren(data, node, cached, editable, namespace, configs) {
420 | return data.children != null && data.children.length > 0 ?
421 | build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) :
422 | data.children;
423 | }
424 |
425 | function reconstructCached(data, attrs, children, node, namespace, views, controllers) {
426 | var cached = {tag: data.tag, attrs: attrs, children: children, nodes: [node]};
427 | unloadCachedControllers(cached, views, controllers);
428 | if (cached.children && !cached.children.nodes) cached.children.nodes = [];
429 | //edge case: setting value on