├── .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'),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