├── img ├── splash.png ├── splash2.png ├── sprites.png └── README │ ├── level2.png │ ├── move1A.png │ ├── move2A.png │ ├── move2B.png │ ├── move3A.png │ ├── move3B.png │ ├── doctorgus.jpg │ ├── forking.png │ ├── pre-fork.png │ ├── planetcute.jpg │ ├── screenshot1.png │ ├── screenshot2.png │ ├── explanation1.png │ ├── explanation2.png │ ├── explanation3.png │ ├── explanation4.png │ ├── explanation5.png │ ├── issue-closed.png │ ├── octocat-link-big.jpg │ └── octocat-link-small.png ├── js ├── Multibuttons.js ├── Keycode.js ├── Moveable.js ├── EventQueue.js ├── Layer.js ├── Button.js ├── Pos.js ├── Multi.js ├── Table.js ├── ForkedBlock.js ├── Buttons.js ├── Shadows.js ├── Multiroom.js ├── Theatre.js ├── Sprite.js ├── README.md ├── Tracker.js ├── ActionQueue.js ├── Tile.js ├── Scene.js ├── Room.js ├── jquery.transit.min.js ├── World.js ├── octocarina.js └── underscore.js ├── .gitattributes ├── README.md ├── index.html └── css ├── bootstrap.min.css ├── octocarina.css └── bootstrap.css /img/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/splash.png -------------------------------------------------------------------------------- /img/splash2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/splash2.png -------------------------------------------------------------------------------- /img/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/sprites.png -------------------------------------------------------------------------------- /img/README/level2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/level2.png -------------------------------------------------------------------------------- /img/README/move1A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/move1A.png -------------------------------------------------------------------------------- /img/README/move2A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/move2A.png -------------------------------------------------------------------------------- /img/README/move2B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/move2B.png -------------------------------------------------------------------------------- /img/README/move3A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/move3A.png -------------------------------------------------------------------------------- /img/README/move3B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/move3B.png -------------------------------------------------------------------------------- /img/README/doctorgus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/doctorgus.jpg -------------------------------------------------------------------------------- /img/README/forking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/forking.png -------------------------------------------------------------------------------- /img/README/pre-fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/pre-fork.png -------------------------------------------------------------------------------- /img/README/planetcute.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/planetcute.jpg -------------------------------------------------------------------------------- /img/README/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/screenshot1.png -------------------------------------------------------------------------------- /img/README/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/screenshot2.png -------------------------------------------------------------------------------- /img/README/explanation1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/explanation1.png -------------------------------------------------------------------------------- /img/README/explanation2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/explanation2.png -------------------------------------------------------------------------------- /img/README/explanation3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/explanation3.png -------------------------------------------------------------------------------- /img/README/explanation4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/explanation4.png -------------------------------------------------------------------------------- /img/README/explanation5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/explanation5.png -------------------------------------------------------------------------------- /img/README/issue-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/issue-closed.png -------------------------------------------------------------------------------- /img/README/octocat-link-big.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/octocat-link-big.jpg -------------------------------------------------------------------------------- /img/README/octocat-link-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Octocarina/game-off-2012/HEAD/img/README/octocat-link-small.png -------------------------------------------------------------------------------- /js/Multibuttons.js: -------------------------------------------------------------------------------- 1 | // handles all the buttons in all copies of the room. 2 | 3 | var Multibuttons = { 4 | create: function(room) { 5 | return Multi.create(room, Buttons.create); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /js/Keycode.js: -------------------------------------------------------------------------------- 1 | // http://unixpapa.com/js/key.html 2 | var Keycode = { 3 | tab: 9, 4 | ctrl: 17, 5 | esc: 27, 6 | space: 32, 7 | left: 37, 8 | up: 38, 9 | right: 39, 10 | down: 40 11 | }; 12 | 13 | (function() { 14 | var s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 15 | for(var i=0; i0;) { 23 | body(events[i]); 24 | } 25 | }, 26 | clear: function() { 27 | events = new Array(); 28 | } 29 | }; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /js/Layer.js: -------------------------------------------------------------------------------- 1 | // a grid of Sprites. 2 | // there is a Layer of Tile.floor sprites below the room, 3 | // on top of which is another Layer containing the actual obstacles. 4 | 5 | var Layer = { 6 | create: function(container, tiles, room, extra_class) { 7 | var element = $('
'); 8 | if (extra_class) element.addClass(extra_class); 9 | container.append(element); 10 | 11 | var spriteIndex = 0; 12 | var lines = new Array(); 13 | for (var i = 1; i <= room.h; i++) 14 | { 15 | var line = $("
"); 16 | line.css('width', (room.w * 101) + 'px'); 17 | element.append(line); 18 | lines.push(line); 19 | } 20 | 21 | var sprites = tiles.map(function(pos, tile) { 22 | return Sprite.create(lines[pos.y], tile); 23 | }); 24 | 25 | return { 26 | sprite_at: function(pos) { 27 | return sprites.at(pos); 28 | } 29 | }; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /js/Button.js: -------------------------------------------------------------------------------- 1 | // handles the logic of a single button. 2 | // 3 | // basically, buttons are activated when you walk over them or 4 | // when you push a block over them. 5 | // 6 | // there are also some special colored buttons which can only 7 | // be activated by a block of the same color. 8 | 9 | var Button = { 10 | create: function(color) { 11 | var weight = 0; 12 | 13 | var regular = true; 14 | if (color) regular = false; 15 | 16 | return { 17 | active: function() { 18 | return (weight >= 0); 19 | }, 20 | 21 | add_weight: function(moveable) { 22 | if (regular || moveable.tile.color == color) { 23 | ++weight; 24 | return true; 25 | } 26 | 27 | return false; 28 | }, 29 | remove_weight: function(moveable) { 30 | if (regular || moveable.tile.color == color) { 31 | --weight; 32 | return true; 33 | } 34 | 35 | return false; 36 | } 37 | }; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /js/Pos.js: -------------------------------------------------------------------------------- 1 | // an (x, y) coordinate. 2 | 3 | var Pos = { 4 | create: function(x, y) { 5 | return { 6 | x: x, 7 | y: y, 8 | plus: function(dx, dy) { 9 | return Pos.create(this.x + dx, this.y + dy); 10 | }, 11 | dir_name: function() { 12 | if (this.x < 0) { 13 | return "left"; 14 | } else if (this.x > 0) { 15 | return "right"; 16 | } else if (this.y < 0) { 17 | return "up"; 18 | } else { 19 | return "down"; 20 | } 21 | } 22 | }; 23 | }, 24 | distance_between: function (a, b) { 25 | var delta_x = b.x - a.x; 26 | var delta_y = b.y - a.y; 27 | return Pos.create(delta_x, delta_y); 28 | }, 29 | each: function(w, h, body) { 30 | for(var y=0; y= 0 && pos.y >= 0 && pos.x < w && pos.y < h); 39 | }, 40 | 41 | each: function(body) { 42 | var self = this; 43 | Pos.each(self.w, self.h, function(pos) { 44 | body(pos, self.at(pos)); 45 | }); 46 | }, 47 | map: function(body) { 48 | var self = this; 49 | return Table.create(self.w, self.h, function(pos) { 50 | return body(pos, self.at(pos)); 51 | }); 52 | }, 53 | copy: function() { 54 | return this.map(function(pos, value) { 55 | return value; 56 | }); 57 | } 58 | }; 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /js/ForkedBlock.js: -------------------------------------------------------------------------------- 1 | var ForkedBlock = { 2 | create: function (room) { 3 | var new_block = null; 4 | var old_block = null; 5 | var observed_moves = EventQueue.create(); 6 | var moves_to_replay = EventQueue.create(); 7 | var moves_to_undo = EventQueue.create(); 8 | 9 | return { 10 | moves_to_replay: moves_to_replay, 11 | moves_to_undo: moves_to_undo, 12 | process_events: function (events) { 13 | events.forks.each(function (fork) { 14 | old_block = fork.old_block; 15 | new_block = fork.new_block; 16 | }); 17 | 18 | events.merges.each(function (merge) { 19 | var room = merge.new_room; 20 | 21 | observed_moves.each(function(move) { 22 | if (move.moveable === new_block) { 23 | moves_to_replay.add(move); 24 | } 25 | 26 | moves_to_undo.add(move); 27 | }); 28 | 29 | old_block.forked = null; 30 | 31 | observed_moves.clear(); 32 | old_block = new_block = null; 33 | }); 34 | 35 | if (new_block) { 36 | events.each_room(function (index, room) { 37 | room.tile_changes.each(function(tile_change) { 38 | observed_moves.add(tile_change); 39 | }); 40 | room.moves.each(function (move) { 41 | if (move.new_pos !== move.old_pos || move.dir || move.start_shaking) { 42 | observed_moves.add(move); 43 | } 44 | }); 45 | }); 46 | } 47 | } 48 | }; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /js/Buttons.js: -------------------------------------------------------------------------------- 1 | // handles the logic of all buttons in a room. 2 | // 3 | // basically, the door should open if and only if 4 | // all switches are activated. 5 | 6 | var Buttons = { 7 | create: function(room) { 8 | // find the door and the buttons 9 | var door = null; 10 | var button_count = 0; 11 | var buttons = Table.create(room.size, function(pos) { 12 | var tile = room.floor_tile_at(pos); 13 | 14 | if (tile == Tile.closed_door) { 15 | door = pos; 16 | } 17 | 18 | if (tile.button) { 19 | ++button_count; 20 | return Button.create(tile.color); 21 | } else { 22 | return null; 23 | } 24 | }); 25 | 26 | var active_buttons = 0; 27 | 28 | return { 29 | solved: function() { 30 | return (button_count > 0 && active_buttons == button_count); 31 | }, 32 | process_events: function(room) { 33 | // remember how things were 34 | var old_active = this.solved(); 35 | 36 | room.moves.each(function(move) { 37 | var old_button = buttons.at(move.old_pos); 38 | var new_button = buttons.at(move.new_pos); 39 | 40 | if (old_button && old_button.remove_weight(move.moveable)) { 41 | --active_buttons; 42 | } 43 | if (new_button && new_button.add_weight(move.moveable)) { 44 | ++active_buttons; 45 | } 46 | }); 47 | 48 | // have things changed? 49 | var new_active = this.solved(); 50 | if (new_active != old_active) { 51 | if (new_active) { 52 | // open the door 53 | if (door) room.change_tile(door, Tile.open_door); 54 | } else { 55 | // close the door 56 | if (door) room.change_tile(door, Tile.closed_door); 57 | } 58 | } 59 | } 60 | }; 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /js/Shadows.js: -------------------------------------------------------------------------------- 1 | // handles the logic of all hint shadows. 2 | // 3 | // basically, shadows should appear at the position where 4 | // moveables will revert when the fork is picked back. 5 | 6 | var Shadows = { 7 | create: function(room) { 8 | var fork_in_block = false; 9 | var already_displayed = {}; 10 | 11 | return { 12 | process_events: function(multiroom, scene) { 13 | multiroom.forks.each(function (fork) { 14 | fork_in_block = true; 15 | already_displayed = {}; 16 | }); 17 | 18 | multiroom.merges.each(function (merge) { 19 | fork_in_block = false; 20 | already_displayed = {}; 21 | }); 22 | 23 | if (fork_in_block) { 24 | multiroom.each_room(function (index, room) { 25 | room.moves.each(function(move) { 26 | if (move.insert) { 27 | // don't add a shadow to the objects which appeared 28 | // during the fork's scope 29 | already_displayed[move.moveable.id] = true; 30 | } else if (move.old_pos.x != move.new_pos.x || 31 | move.old_pos.y != move.new_pos.y) { 32 | if (!move.moveable.forked && !already_displayed[move.moveable.id]) { 33 | already_displayed[move.moveable.id] = true; 34 | 35 | scene.add_hint(move.old_pos, Shadows.shadow_tile_for(move.moveable.tile)); 36 | } 37 | } 38 | }); 39 | }); 40 | } 41 | } 42 | }; 43 | }, 44 | shadow_tile_for: function(tile) { 45 | if (tile.player) { 46 | return Tile.player_shadow; 47 | } else if (tile.lover) { 48 | return Tile.lover_shadow; 49 | } else if (tile.color) { 50 | return Tile.gem_shadow; 51 | } else if (tile.forktopus) { 52 | return Tile.forktopus_shadow; 53 | } else { 54 | return Tile.block_shadow; 55 | } 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /js/Multiroom.js: -------------------------------------------------------------------------------- 1 | // a set of parallel Rooms. 2 | // the current room can be changed, and you can listen for those changes. 3 | 4 | var Multiroom = { 5 | max_rooms: 1, 6 | 7 | create: function (room) { 8 | var rooms = new Array(1); 9 | rooms[0] = room; 10 | 11 | var room_changes = EventQueue.create(); 12 | var forks = EventQueue.create(); 13 | var merges = EventQueue.create(); 14 | 15 | return { 16 | room_at: function (index) { 17 | return rooms[index]; 18 | }, 19 | each_room: function (body) { 20 | for(var i=0; iDoctor-Gus, an artist who makes jewelry out of forks! So awesome! 42 | ![](https://raw.github.com/Octocarina/game-off-2012/master/img/README/doctorgus.jpg) 43 | -------------------------------------------------------------------------------- /js/Theatre.js: -------------------------------------------------------------------------------- 1 | // a set of parallel Scenes. 2 | // it watches a multiroom change, and updates its scenes to match. 3 | 4 | var Theatre = { 5 | queue: ActionQueue.create(), 6 | 7 | empty: function() { 8 | // same API as a regular Theatre, but holds no rooms. 9 | // useful to simplify the logic of callers, who don't have 10 | // to distinguish between a Theatre and null. 11 | return { 12 | remove: function() {} 13 | }; 14 | }, 15 | create: function(container, room, name) { 16 | var element = container; 17 | 18 | var scenes = Multi.create(room, function(room) { 19 | return Scene.create(element, room); 20 | }); 21 | 22 | var title = $('
').html(name); 23 | title.transition({opacity: 0}, 0); 24 | element.append(title); 25 | title.transition({opacity: 1}); 26 | 27 | var scene = scenes.current(); 28 | Theatre.queue.enqueue(function() { 29 | scene.show(); 30 | }).then_wait_for(scene.queue); 31 | 32 | return { 33 | current_scene: function() { 34 | return scenes.current(); 35 | }, 36 | process_events: function(events) { 37 | // remember how things were 38 | var old_count = scenes.count(); 39 | var old_scene = scenes.current(); 40 | 41 | // process all events 42 | scenes.process_events(events); 43 | 44 | // have things changed? 45 | var new_count = scenes.count(); 46 | var new_scene = scenes.current(); 47 | 48 | if (new_count != old_count) { 49 | // rearrange the scenes (max 2 for now) 50 | 51 | if (new_count == 1) { 52 | scenes.at(0).move_center(); 53 | } else if (new_count == 2) { 54 | scenes.at(0).move_left(); 55 | scenes.at(1).move_right(); 56 | } 57 | } 58 | if (new_scene != old_scene) { 59 | // highlight the current scene 60 | 61 | old_scene.darken(); 62 | new_scene.lighten(); 63 | } 64 | }, 65 | 66 | remove: function() { 67 | var n = scenes.count(); 68 | 69 | function remove_scene(scene) { 70 | return function() { 71 | scene.remove(); 72 | }; 73 | } 74 | 75 | title.transition({opacity: 0}, function () { 76 | title.remove(); 77 | }); 78 | 79 | var j=0; 80 | for(var i=0; i').addClass(Tile.player.sprite_class); 6 | element.css('position', 'absolute'); 7 | element.css('top', '0'); 8 | element.css('left', '0'); 9 | container.append(element); 10 | 11 | return { 12 | element: element, 13 | move_to: function(sprite) { 14 | 15 | } 16 | }; 17 | }, 18 | create: function(container, tile) { 19 | var element = $('
').addClass(tile.sprite_class); 20 | var dir = null; 21 | var say = null; 22 | var forked = null; 23 | var leaving = false; 24 | var shaking = false; 25 | var pushing = false; 26 | var dying = false; 27 | var holder = false; 28 | 29 | container.append(element); 30 | 31 | return { 32 | element: element, 33 | change_tile: function(new_tile) { 34 | element.removeClass(tile.sprite_class); 35 | if (say) { 36 | element.removeClass("say").removeClass(say); 37 | say = null; 38 | } 39 | if (forked) { 40 | element.removeClass(forked); 41 | forked = null; 42 | } 43 | if (leaving) { 44 | element.removeClass("under-door"); 45 | leaving = false; 46 | } 47 | if (shaking) { 48 | element.removeClass("shake"); 49 | shaking = false; 50 | } 51 | if (pushing) { 52 | element.removeClass("pushing"); 53 | pushing = false; 54 | } 55 | if (holder) { 56 | element.removeClass("holder"); 57 | holder = false; 58 | } 59 | 60 | tile = new_tile; 61 | 62 | element.addClass(tile.sprite_class) 63 | }, 64 | set_spotted: function(spotted) { 65 | if (spotted) { 66 | element.addClass("spotted"); 67 | } else { 68 | element.removeClass("spotted"); 69 | } 70 | }, 71 | change_moveable: function(new_moveable) { 72 | if (dir) { 73 | element.removeClass(dir.dir_name()); 74 | } 75 | 76 | this.change_tile(new_moveable.tile); 77 | 78 | if (new_moveable.dir) { 79 | dir = new_moveable.dir; 80 | element.addClass(dir.dir_name()); 81 | } 82 | if (new_moveable.say) { 83 | say = new_moveable.say; 84 | element.addClass("say").addClass(say); 85 | } 86 | if (new_moveable.forked) { 87 | forked = new_moveable.forked; 88 | element.addClass(forked); 89 | } 90 | if (new_moveable.floor == Tile.open_door) { 91 | leaving = true; 92 | element.addClass("under-door"); 93 | } 94 | if (new_moveable.shaking) { 95 | shaking = true; 96 | element.addClass("shake"); 97 | } 98 | if (new_moveable.pushing) { 99 | pushing = true; 100 | element.addClass("pushing"); 101 | } 102 | if (new_moveable.dying) { 103 | dying = new_moveable.dying; 104 | if (dying == 'dying') { 105 | element.transition({opacity: 0}, 4000); 106 | } else { 107 | element.transition({opacity: 1}, 0); 108 | } 109 | } 110 | if (new_moveable.tile.holder && new_moveable.tile !== Tile.forktopus_with_spork) { 111 | holder = true; 112 | element.addClass("holder"); 113 | } 114 | } 115 | }; 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /js/README.md: -------------------------------------------------------------------------------- 1 | # Design document 2 | 3 | This isn't a Waterfall-style design document: I don't have a fixed idea of 4 | where I want the project to go, or how I want it to be structured in the 5 | future. I just thought that it would be useful to have a high-level 6 | explanation of the way the code is *currently* structured, so you can have an 7 | idea of what is where. 8 | 9 | I arrived at this design gradually: I implement new features in the main 10 | octocarina.js file, then I refactor the resulting mess into small classes, 11 | renaming and repurposing the existing classes when appropriate. I expect the 12 | design to continue to change as we go along, we can just trash this document 13 | once it becomes too obsolete. For now, I just hope it's useful to you! 14 | 15 | ## Containment hierarchies 16 | 17 | So far, there are two parallel class hierarchies: 18 | - World contains Multirooms contains Rooms contains Tiles 19 | - Theatre contains Scenes contains Sprites 20 | 21 | 22 | ### Data hierarchy 23 | 24 | The first hierarchy is the data. The main mechanic of this game is to fork the 25 | timeline, so it's important to keep track of each copy; this is what Multiroom 26 | does. Currently, there is no timeline, just multiple copies of the room. 27 | Before the first timeline fork, the Multiroom has only one Room instance. 28 | 29 | Rooms are built out of tiles, which represent the contents of the room at a 30 | particular location. As blocks are pushed and the player moves around, those 31 | tiles change from Tile.empty to Tile.block to Tile.empty again. 32 | 33 | Individual tiles don't have identities; a Tile instance should be viewed as a 34 | value, the tile type at a particular location in the room. The only reason I 35 | use instances instead of, say, a number or a symbol, is so that I can use the 36 | OO notation tile.solid() instead of Tile.is_solid(tile). I plan to add another 37 | class, probably Entity, for level elements which can move around. 38 | 39 | 40 | Oh, and World is where we keep the level data, using a simple ASCII format. 41 | Feel free to add your own levels! The more, the merrier. 42 | 43 | 44 | ### View hierarchy 45 | 46 | In parallel to the data hierarchy is a view hierarchy which takes care of 47 | displaying all this data. Please don't add animation or css, or html data to 48 | the data classes; add them here instead. 49 | 50 | I chose not to call those classes boring names like "Room_View", because I 51 | like short names, and because I don't want this code to devolve into a 52 | Java-like mess of Room_Viewer_Factory_Worker_Singletons. It does require a bit 53 | of cleverness while coming up with new names, though. 54 | 55 | Each tile is represented by a Sprite. Unlike Tile, a Sprite instance does have 56 | an identity; it represents the individual DOM element at this particular 57 | location of the room. The Scene listen for the room's tile changes, and 58 | updates its sprites accordingly. There is also an uninteresting intermediate 59 | class, Layer, which deals with the fact that the floor and the obstacles are 60 | stacked on top of each other. 61 | 62 | In the same manner that a Multiroom contains many Rooms, a Theatre contains 63 | many Scenes. The role of the Theatre is to listen for Multiroom changes, and 64 | to display the many rooms accordingly. 65 | 66 | 67 | ## Events 68 | 69 | Change of plans. 70 | 71 | Instead of following the jQuery convention of registering and triggering 72 | callbacks, let's follow the advice of Rich Hickey and Evan Czaplicki and eschew 73 | callbacks. What shall we use instead? 74 | 75 | Lists. In the top-level keypress handler, Let's: 76 | 1) perform the action, accumulating events of a given type in a list 77 | (wrapped in an EventQueue object, for clarity) 78 | 2) ask each module to analyse the latest events 79 | 3) clear the lists. 80 | 4) loop. 81 | 82 | Hope you like it this way! 83 | -------------------------------------------------------------------------------- /js/Tracker.js: -------------------------------------------------------------------------------- 1 | // tracks moveables across the grid 2 | // 3 | // use the tracker's methods to add and move your moveables, 4 | // and you'll always know where they are. 5 | // each moveable can be identified both by its position and by its id. 6 | 7 | var Tracker = { 8 | from_id_table: function(id_table, moveables, next_id) { 9 | return { 10 | at: function(pos) { 11 | var id = id_table.at(pos, function() { 12 | return 0; 13 | }); 14 | 15 | return this.from_id(id); 16 | }, 17 | from_id: function(id) { 18 | return moveables[id]; 19 | }, 20 | 21 | // assigns a new pos and id to the entry, 22 | // removing any conflicting entry 23 | insert: function(pos, moveable) { 24 | var id = next_id; 25 | ++next_id; 26 | 27 | moveable.pos = pos; 28 | moveable.id = id; 29 | 30 | moveables[id] = moveable; 31 | id_table.change_at(pos, id); 32 | 33 | return id; 34 | }, 35 | remove: function(moveable) { 36 | id_table.change_at(moveable.pos, 0); 37 | }, 38 | 39 | // to move an entry, swap it with a null entry. 40 | // there is no method to explicitly change the position 41 | // of an existing entry, as that is prone to conflict: 42 | // what if there is already an entry at the new position? 43 | swap: function(pos1, pos2) { 44 | var id1 = id_table.at(pos1); 45 | var id2 = id_table.at(pos2); 46 | 47 | if (id1) moveables[id1].pos = pos2; 48 | if (id2) moveables[id2].pos = pos1; 49 | 50 | id_table.change_at(pos1, id2); 51 | id_table.change_at(pos2, id1); 52 | }, 53 | 54 | each: function(body) { 55 | for(var i=0; i max_id) { 112 | max_id = id; 113 | } 114 | 115 | moveables[id] = moveable; 116 | 117 | return id; 118 | } else { 119 | // zero is an invalid id, 120 | // representing an empty cell 121 | // containing no moveables. 122 | return 0; 123 | } 124 | }); 125 | 126 | return this.from_id_table(id_table, moveables, max_id + 1); 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /js/ActionQueue.js: -------------------------------------------------------------------------------- 1 | // run code in order despite asyncronous callbacks. 2 | // 3 | // this requires collaboration with all asynchronous calls, 4 | // as in the following example: 5 | // 6 | // var actionQueue = ActionQueue.create(); 7 | // actionQueue.enqueue(function() { 8 | // // this block executes first 9 | // }).then_async(function(resume) { 10 | // // then this animation. 11 | // $('body').transition({opacity: 0}, function() { 12 | // // this callback runs when the animation finishes. 13 | // // tell the actionQueue to continue 14 | // resume(); 15 | // }).then(function() { 16 | // // this block runs once the animation is over 17 | // }); 18 | // 19 | // this particular example could be achieved in a simpler way 20 | // by using the animation's callback directly; 21 | // explicit action queues are more useful when you need 22 | // more control over the execution, for instance to have 23 | // multiple interacting queues. 24 | 25 | var ActionQueue = { 26 | create: function() { 27 | var paused = false; 28 | var nested = false; 29 | var fast_forward = false; 30 | var queue = []; 31 | 32 | return { 33 | is_empty: function() { 34 | if (paused) { 35 | // an element is still running, even if its no longer on the queue. 36 | return false; 37 | } else { 38 | return (queue.length == 0); 39 | } 40 | }, 41 | run_queue: function() { 42 | if (nested) return; 43 | 44 | nested = true; 45 | while (queue.length > 0 && !paused) { 46 | var body = queue.shift(); 47 | if (body != 'dummy') { 48 | var remaining_events = queue; 49 | queue = ['dummy']; 50 | 51 | body(); 52 | 53 | // next, play the events enqueued by body(), 54 | // (which are already in the queue) 55 | // then play the remaining events 56 | for(var i=0; i fast_forward) delay = fast_forward; 119 | window.setTimeout(resume, delay); 120 | } else { 121 | other_queue.enqueue(resume); 122 | } 123 | }, 124 | then_wait_for: function(other_queue) { 125 | var self = this; 126 | self.enqueue(function() { 127 | self.wait_for(other_queue); 128 | }); 129 | 130 | return self; 131 | }, 132 | 133 | stop: function() { 134 | paused = true; 135 | }, 136 | resume: function() { 137 | if (paused) { 138 | paused = false; 139 | 140 | this.run_queue(); 141 | } 142 | }, 143 | 144 | fast_forward: function(forced_delay) { 145 | if (!this.is_empty()) { 146 | fast_forward = forced_delay; 147 | } 148 | }, 149 | clear: function() { 150 | queue = []; 151 | this.resume(); 152 | } 153 | }; 154 | } 155 | }; 156 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Push and Fork 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Push and Fork

16 | 17 | 18 |
19 |
20 | 21 |
22 | Fork me on GitHub 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 65 |
66 |
67 |

CREDITS

68 |

Lead Programmer

69 |
    70 |
  • Samuel Gélineau
  • 71 |
72 |

Programmers

73 |
    74 |
  • Matan Nassau
  • 75 |
  • Alexis Leroux-Chartré
  • 76 |
  • Jimmy Lahaie
  • 77 |
78 |

Advice

79 |
    80 |
  • Bob Quenneville
  • 81 |
82 |

Game Art

83 |
    84 |
  • Daniel Cook's free PlanetCute tileset
  • 85 |
  • Doctor-Gus's fork art (the monster)
  • 86 |
  • Nadezda Ershova
  • 87 |
  • Samuel Gélineau
  • 88 |
89 |

Quality Assurance

90 |
    91 |
  • Keegan George
  • 92 |
93 |

Beta Testers

94 |
    95 |
  • Bob Quenneville
  • 96 |
  • Catherine Villeneuve
  • 97 |
  • Jonathan Primeau
  • 98 |
  • Nadezda Ershova
  • 99 |
  • Stéphanie Simard
  • 100 |
  • Stéphane Beaudry
  • 101 |
  • Pascal Jetté
  • 102 |
103 |

Special Thanks

104 |
    105 |
  • Github for their inspiring contest rules
  • 106 |
107 |
108 |
109 |

Thanks for playing!

110 |
111 |
112 |
You have unlocked a new bonus level.
113 |
(Press any key to continue)
114 |
115 |
116 | 117 | 118 | -------------------------------------------------------------------------------- /js/Tile.js: -------------------------------------------------------------------------------- 1 | // an immutable representation of a tile type, 2 | // like Tile.wall or Tile.floor; 3 | // remember to always compare with "===". 4 | 5 | var Tile = { 6 | list: {}, 7 | from_symbol: function(symbol) { 8 | return this.list[symbol]; 9 | }, 10 | create: function(symbol, tile) { 11 | this.list[symbol] = tile; 12 | return tile; 13 | } 14 | }; 15 | 16 | Tile.empty = Tile.create('', { 17 | sprite_class : 'empty-tile' 18 | }); 19 | Tile.no_floor = Tile.create(' ', { 20 | sprite_class : 'empty-tile', 21 | solid : true, 22 | is_floor : true 23 | }); 24 | Tile.floor = Tile.create('.', { 25 | sprite_class : 'floor-tile', 26 | is_floor : true 27 | }); 28 | Tile.bad_floor = Tile.create(',', { 29 | sprite_class : 'floor-tile', 30 | is_floor : true 31 | }); 32 | Tile.future_hint = Tile.create('2', { 33 | sprite_class : 'floor-tile', 34 | is_floor : true 35 | }); 36 | Tile.wall = Tile.create('#', { 37 | sprite_class : 'wall-tile', 38 | solid : true 39 | }); 40 | Tile.invisible_wall = Tile.create(';', { 41 | sprite_class : 'floor-tile', 42 | solid : true, 43 | is_floor : true 44 | }); 45 | 46 | Tile.player = Tile.create('c', { 47 | sprite_class : 'player-sprite', 48 | player : true, 49 | solid : true, 50 | character : true, 51 | holder : true, 52 | moveable : true 53 | }); 54 | Tile.player_with_fork = Tile.create('C', { 55 | sprite_class : 'player-sprite', 56 | player : true, 57 | solid : true, 58 | forked : 'forked', 59 | character : true, 60 | holder : true, 61 | moveable : true 62 | }); 63 | Tile.player_with_spork = Tile.create('S', { 64 | sprite_class : 'player-sprite', 65 | player : true, 66 | solid : true, 67 | forked : 'sporked', 68 | character : true, 69 | holder : true, 70 | moveable : true 71 | }); 72 | Tile.lover = Tile.create('l', { 73 | sprite_class : 'lover-sprite', 74 | lover : true, 75 | solid : true, 76 | character : true, 77 | holder : true, 78 | moveable : true 79 | }); 80 | Tile.lover_with_fork = Tile.create('L', { 81 | sprite_class : 'lover-sprite', 82 | lover : true, 83 | solid : true, 84 | forked : 'forked', 85 | character : true, 86 | holder : true, 87 | moveable : true 88 | }); 89 | 90 | Tile.closed_door = Tile.create('D', { 91 | sprite_class : 'closed-door-tile', 92 | solid : true 93 | }); 94 | Tile.open_door = Tile.create('d', { 95 | sprite_class : 'open-door-tile' 96 | }); 97 | 98 | Tile.red_button = Tile.create('R', { 99 | sprite_class : 'red-button-tile', 100 | color : 'red', 101 | is_floor : true, 102 | button : true 103 | }); 104 | Tile.green_button = Tile.create('G', { 105 | sprite_class : 'green-button-tile', 106 | color : 'green', 107 | is_floor : true, 108 | button : true 109 | }); 110 | Tile.blue_button = Tile.create('B', { 111 | sprite_class : 'blue-button-tile', 112 | color : 'blue', 113 | is_floor : true, 114 | button : true 115 | }); 116 | Tile.orange_button = Tile.create('O', { 117 | sprite_class : 'orange-button-tile', 118 | color : 'orange', 119 | is_floor : true, 120 | button : true 121 | }); 122 | 123 | Tile.block = Tile.create('w', { 124 | sprite_class : 'wooden-block-sprite', 125 | solid : true, 126 | moveable : true 127 | }); 128 | Tile.block_with_hint = Tile.create('W', { 129 | sprite_class : 'hint-block-sprite', 130 | solid : true, 131 | moveable : true 132 | }); 133 | Tile.hint = Tile.create('?', { 134 | sprite_class : 'forked-block-hint-sprite', 135 | is_hint : true, 136 | hint : true 137 | }); 138 | Tile.hint_spot = Tile.create('', { 139 | sprite_class : 'hint-spot-sprite', 140 | is_hint : true 141 | }); 142 | Tile.block_with_spork = Tile.create('s', { 143 | sprite_class : 'green-block-sprite', 144 | solid : true, 145 | forked : 'sporked', 146 | moveable : true 147 | }); 148 | 149 | Tile.red_block = Tile.create('r', { 150 | sprite_class : 'red-block-sprite', 151 | color : 'red', 152 | solid : true, 153 | moveable : true 154 | }); 155 | Tile.green_block = Tile.create('g', { 156 | sprite_class : 'green-block-sprite', 157 | color : 'green', 158 | solid : true, 159 | moveable : true 160 | }); 161 | Tile.blue_block = Tile.create('b', { 162 | sprite_class : 'blue-block-sprite', 163 | color : 'blue', 164 | solid : true, 165 | moveable : true 166 | }); 167 | Tile.orange_block = Tile.create('o', { 168 | sprite_class : 'orange-block-sprite', 169 | color : 'orange', 170 | solid : true, 171 | moveable : true 172 | }); 173 | 174 | Tile.blood = Tile.create('', { 175 | sprite_class : 'blood-on-floor', 176 | is_floor : true 177 | }); 178 | Tile.fork = Tile.create('f', { 179 | sprite_class : 'fork-on-floor', 180 | is_floor : true 181 | }); 182 | Tile.dropped_fork = Tile.create('', { 183 | sprite_class : 'dropped-fork', 184 | is_floor : true 185 | }); 186 | Tile.forktopus = Tile.create('', { 187 | sprite_class : 'forktopus', 188 | forktopus : true, 189 | solid : true, 190 | character : true, 191 | moveable : true 192 | }); 193 | Tile.forktopus_with_spork = Tile.create('F', { 194 | sprite_class : 'forktopus', 195 | forktopus : true, 196 | solid : true, 197 | forked : 'sporked', 198 | character : true, 199 | holder : true, 200 | moveable : true 201 | }); 202 | 203 | Tile.block_shadow = Tile.create('', { 204 | sprite_class : 'block-shadow' 205 | }); 206 | Tile.gem_shadow = Tile.create('', { 207 | sprite_class : 'gem-shadow' 208 | }); 209 | Tile.player_shadow = Tile.create('', { 210 | sprite_class : 'player-shadow' 211 | }); 212 | Tile.lover_shadow = Tile.create('', { 213 | sprite_class : 'lover-shadow' 214 | }); 215 | Tile.forktopus_shadow = Tile.create('', { 216 | sprite_class : 'forktopus-shadow' 217 | }); 218 | -------------------------------------------------------------------------------- /js/Scene.js: -------------------------------------------------------------------------------- 1 | // a stack of Layers. 2 | // it watches a room change, and updates the sprites to match. 3 | 4 | var Scene = { 5 | queue: ActionQueue.create(), 6 | create: function(container, room) { 7 | var element = $('
'); 8 | 9 | // start invisible 10 | element.transition({opacity: 0}, 0); 11 | 12 | // add the floor 13 | var floor_tiles = this.create_floor(room); 14 | var floor_layer = Layer.create(element, floor_tiles, room); 15 | 16 | // add the actual obstacles 17 | var solid_tiles = this.extract_tiles(room); 18 | var solid_layer = Layer.create(element, solid_tiles, room); 19 | 20 | // honor each moveable's special visual features 21 | solid_tiles.each(function(pos, tile) { 22 | var moveable = room.moveable_at(pos); 23 | 24 | if (moveable) { 25 | var solid_sprite = solid_layer.sprite_at(pos); 26 | 27 | solid_sprite.change_moveable(moveable); 28 | } 29 | }); 30 | 31 | // add the hint layer 32 | var hint_tiles = this.create_hints(room); 33 | var hint_layer = Layer.create(element, hint_tiles, room, 'hint-layer'); 34 | 35 | // add color filters on the very top, to tint the entire scene 36 | var dark_filter = this.create_filter(element, 'dark'); 37 | var light_filter = this.create_filter(element, 'light'); 38 | 39 | container.prepend(element); 40 | 41 | var queue = ActionQueue.create(); 42 | var hints = EventQueue.create(); 43 | return { 44 | queue: queue, 45 | 46 | hide: function() { 47 | queue.enqueue_async(function(resume) { 48 | element.transition({opacity: 0}, resume); 49 | }); 50 | }, 51 | show: function(now) { 52 | queue.enqueue_async(function(resume) { 53 | element.transition({opacity: 1}, 'slow', resume); 54 | }); 55 | }, 56 | 57 | darken: function() { 58 | Scene.queue.enqueue_async(function(resume) { 59 | dark_filter.transition({opacity: 0.1}, 'slow', resume); 60 | }); 61 | }, 62 | undarken: function() { 63 | Scene.queue.enqueue_async(function(resume) { 64 | dark_filter.transition({opacity: 0}, 'slow', resume); 65 | }); 66 | }, 67 | lighten: function() { 68 | Scene.queue.enqueue_async(function(resume) { 69 | light_filter.transition({opacity: 0.5}, 0, function() { 70 | light_filter.transition({opacity: 0}, 'slow', resume); 71 | }); 72 | }); 73 | }, 74 | 75 | move_center: function () { 76 | queue.enqueue_async(function(resume) { 77 | element.transition({ scale: 1, opacity: 1, x: 0 }, resume); 78 | }); 79 | }, 80 | move_to: function(x) { 81 | queue.enqueue_async(function(resume) { 82 | // move, scale down, and make sure it's visible 83 | element.transition({scale: 0.5, opacity: 1, x: x}, resume); 84 | }); 85 | }, 86 | move_left: function() { 87 | this.move_to(-410); 88 | }, 89 | move_right: function() { 90 | this.move_to(410); 91 | }, 92 | 93 | add_hint_spot: function(pos) { 94 | hint_layer.sprite_at(pos).set_spotted(true); 95 | 96 | hints.add(pos); 97 | }, 98 | add_hint: function(pos, hint) { 99 | hint_layer.sprite_at(pos).change_tile(hint); 100 | 101 | hints.add(pos); 102 | }, 103 | clear_hints: function() { 104 | hints.each(function(pos) { 105 | var hint_sprite = hint_layer.sprite_at(pos); 106 | hint_sprite.change_tile(Tile.empty); 107 | hint_sprite.set_spotted(false); 108 | }); 109 | 110 | hints.clear(); 111 | }, 112 | process_events: function(room) { 113 | room.tile_changes.each(function(tile_change) { 114 | var pos = tile_change.pos; 115 | var tile = tile_change.new_tile; 116 | var solid_sprite = solid_layer.sprite_at(pos); 117 | var floor_sprite = floor_layer.sprite_at(pos); 118 | var hint_sprite = hint_layer.sprite_at(pos); 119 | var moveable = room.moveable_at(pos); 120 | 121 | if (moveable) { 122 | solid_sprite.change_moveable(moveable); 123 | if (moveable.floor.is_floor) { 124 | floor_sprite.change_tile(moveable.floor); 125 | } 126 | } else if (tile.is_floor) { 127 | solid_sprite.change_tile(Tile.empty); 128 | floor_sprite.change_tile(tile); 129 | } else if (tile.is_hint) { 130 | solid_sprite.change_tile(Tile.empty); 131 | hint_sprite.change_tile(tile); 132 | } else { 133 | solid_sprite.change_tile(tile); 134 | floor_sprite.change_tile(Tile.floor); 135 | } 136 | }); 137 | }, 138 | 139 | remove: function() { 140 | var self = this; 141 | 142 | queue.enqueue(function() { 143 | self.hide(); 144 | }).then(function() { 145 | element.remove(); 146 | }); 147 | } 148 | }; 149 | }, 150 | 151 | create_floor: function(room) { 152 | // we make a special case when the last row is all walls, 153 | // because the row of ground tiles look out of place in that case. 154 | var all_walls = true; 155 | { 156 | var y = room.h - 1; 157 | for(var x=0; x'); 205 | 206 | // start invisible 207 | filter.transition({opacity: 0}, 0); 208 | container.append(filter); 209 | 210 | return filter; 211 | } 212 | }; 213 | -------------------------------------------------------------------------------- /js/Room.js: -------------------------------------------------------------------------------- 1 | // a grid of Tiles. 2 | // tiles can change type, and you can listen for those changes. 3 | 4 | var Room = { 5 | create: function(tiles, moveables) { 6 | // find the player and other important moveables 7 | var player = null; 8 | var lover = null; 9 | var forktopus = null; 10 | moveables.each(function(moveable) { 11 | if (moveable.tile.player) { 12 | player = moveable; 13 | } else if (moveable.tile.lover) { 14 | lover = moveable; 15 | } else if (moveable.tile.forktopus) { 16 | forktopus = moveable; 17 | } 18 | }); 19 | 20 | var tile_changes = EventQueue.create(); 21 | var moves = EventQueue.create(); 22 | return { 23 | size: tiles.size, 24 | w: tiles.w, 25 | h: tiles.h, 26 | 27 | tile_at: function (pos) { 28 | return tiles.at(pos, function () { 29 | // prevent the player from falling off the map 30 | return Tile.wall; 31 | }); 32 | }, 33 | floor_tile_at: function(pos) { 34 | var moveable = moveables.at(pos); 35 | 36 | if (moveable) { 37 | return moveable.floor; 38 | } else { 39 | return this.tile_at(pos); 40 | } 41 | }, 42 | 43 | tile_changes: tile_changes, 44 | change_tile: function (pos, new_tile) { 45 | var old_tile = tiles.at(pos); 46 | 47 | tiles.change_at(pos, new_tile); 48 | 49 | // remember the change 50 | tile_changes.add({ 51 | pos: pos, 52 | old_tile: old_tile, 53 | new_tile: new_tile 54 | }); 55 | }, 56 | each_tile: function (body) { 57 | tiles.each(function (pos, tile) { 58 | body(pos, tile); 59 | }); 60 | }, 61 | each_door: function (body) { 62 | tiles.each(function (pos, tile) { 63 | if (tile === Tile.open_door || tile === Tile.closed_door) 64 | body(pos, tile); 65 | }); 66 | }, 67 | 68 | moveable_at: function (pos) { 69 | return moveables.at(pos); 70 | }, 71 | moveable_from_id: function (id) { 72 | return moveables.from_id(id); 73 | }, 74 | 75 | moves: moves, 76 | force_move: function (moveable, new_pos) { 77 | var old_pos = moveable.pos; 78 | var old_floor = moveable.floor; 79 | var new_floor = this.tile_at(new_pos); 80 | 81 | this.change_tile(old_pos, old_floor); 82 | this.change_tile(new_pos, moveable.tile); 83 | 84 | moveables.swap(old_pos, new_pos); 85 | 86 | if (old_pos !== new_pos) { 87 | moveable.floor = new_floor; 88 | } 89 | 90 | // remember the move 91 | moves.add({ 92 | moveable: moveable, 93 | dir: moveable.dir, 94 | old_pos: old_pos, 95 | new_pos: new_pos, 96 | old_floor: old_floor, 97 | new_floor: new_floor 98 | }); 99 | }, 100 | update_moveable: function (moveable) { 101 | this.force_move(moveable, moveable.pos); 102 | }, 103 | shake_moveable: function(moveable) { 104 | if (!moveable.shaking) { 105 | moveable.shaking = true; 106 | 107 | // remember the change 108 | moves.add({ 109 | start_shaking: true, 110 | moveable: moveable, 111 | dir: moveable.dir, 112 | old_pos: moveable.pos, 113 | new_pos: moveable.pos, 114 | old_floor: moveable.floor, 115 | new_floor: moveable.floor 116 | }); 117 | } 118 | }, 119 | insert_moveable: function (pos, moveable) { 120 | if (moveable.tile.player) player = this.player = moveable; 121 | if (moveable.tile.lover) lover = this.lover = moveable; 122 | if (moveable.tile.forktopus) forktopus = this.forktopus = moveable; 123 | 124 | moveables.insert(pos, moveable); 125 | moves.add({ 126 | insert: true, 127 | moveable: moveable, 128 | dir: moveable.dir, 129 | old_pos: moveable.pos, 130 | new_pos: moveable.pos, 131 | old_floor: moveable.floor, 132 | new_floor: moveable.floor 133 | }); 134 | this.update_moveable(moveable); 135 | }, 136 | remove_moveable: function (moveable) { 137 | moveables.remove(moveable); 138 | this.change_tile(moveable.pos, moveable.floor); 139 | }, 140 | move: function (moveable, dx, dy) { 141 | var old_pos = moveable.pos; 142 | var new_pos = old_pos.plus(dx, dy); 143 | var new_pos2 = old_pos.plus(2 * dx, 2 * dy); 144 | 145 | var target = this.tile_at(new_pos); 146 | var target2 = this.tile_at(new_pos2); 147 | 148 | if (target.moveable && !target2.solid && target2 !== Tile.open_door) { 149 | var block = this.moveable_at(new_pos); 150 | 151 | this.force_move(block, new_pos2); 152 | this.force_move(moveable, new_pos); 153 | } else if (!target.solid) { 154 | this.force_move(moveable, new_pos); 155 | } 156 | else{ 157 | this.force_move(moveable, moveable.pos); 158 | } 159 | 160 | }, 161 | 162 | player: player, 163 | player_pos: function () { 164 | return player.pos; 165 | }, 166 | move_player: function (dx, dy) { 167 | player.dir = Pos.create(dx, dy); 168 | this.move(player, dx, dy); 169 | }, 170 | 171 | lover: lover, 172 | lover_pos: function () { 173 | return lover.pos; 174 | }, 175 | move_lover: function (dx, dy) { 176 | lover.dir = Pos.create(dx, dy); 177 | this.move(lover, dx, dy); 178 | }, 179 | 180 | forktopus: forktopus, 181 | forktopus_pos: function () { 182 | return forktopus.pos; 183 | }, 184 | move_forktopus: function (dx, dy) { 185 | forktopus.dir = Pos.create(dx, dy); 186 | this.move(forktopus, dx, dy); 187 | }, 188 | 189 | copy: function () { 190 | return Room.create(tiles.copy(), moveables.copy()); 191 | }, 192 | 193 | clear_events: function () { 194 | tile_changes.clear(); 195 | moves.clear(); 196 | } 197 | }; 198 | }, 199 | from_tiles: function (tiles) { 200 | // find the moveables 201 | var player = null; 202 | var moveable_table = tiles.map(function (pos, tile) { 203 | if (tile.moveable) { 204 | var moveable = Moveable.create(tile); 205 | 206 | if (tile.forked) { 207 | moveable.forked = tile.forked; 208 | if (!tile.holder) moveable.shaking = true; 209 | } 210 | 211 | return moveable; 212 | } else { 213 | return null; 214 | } 215 | }); 216 | var moveables = Tracker.from_moveable_table(moveable_table); 217 | 218 | return this.create(tiles, moveables); 219 | }, 220 | from_data: function (data) { 221 | // convert the tile symbols into tile types 222 | var tiles = data.symbols.map(function (pos, symbol) { 223 | return Tile.from_symbol(symbol); 224 | }); 225 | 226 | return this.from_tiles(tiles); 227 | } 228 | }; 229 | -------------------------------------------------------------------------------- /js/jquery.transit.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Transit - CSS3 transitions and transformations 3 | * Copyright(c) 2011 Rico Sta. Cruz 4 | * MIT Licensed. 5 | * 6 | * http://ricostacruz.com/jquery.transit 7 | * http://github.com/rstacruz/jquery.transit 8 | * 9 | * Reviewed by Simon SER to work with jQuery 1.8.2. 10 | * 11 | */ 12 | 13 | (function($){"use strict";$.transit={version:"0.1.3",propertyMap:{marginLeft:'margin',marginRight:'margin',marginBottom:'margin',marginTop:'margin',paddingLeft:'padding',paddingRight:'padding',paddingBottom:'padding',paddingTop:'padding'},enabled:true,useTransitionEnd:false};var div=document.createElement('div');var support={};function getVendorPropertyName(prop){var prefixes=['Moz','Webkit','O','ms'];var prop_=prop.charAt(0).toUpperCase()+prop.substr(1);if(prop in div.style){return prop;} 14 | for(var i=0;i-1;support.transition=getVendorPropertyName('transition');support.transitionDelay=getVendorPropertyName('transitionDelay');support.transform=getVendorPropertyName('transform');support.transformOrigin=getVendorPropertyName('transformOrigin');support.transform3d=checkTransform3dSupport();$.extend($.support,support);var eventNames={'MozTransition':'transitionend','OTransition':'oTransitionEnd','WebkitTransition':'webkitTransitionEnd','msTransition':'MSTransitionEnd'};var transitionEnd=support.transitionEnd=eventNames[support.transition]||null;div=null;$.cssEase={'_default':'ease','in':'ease-in','out':'ease-out','in-out':'ease-in-out','snap':'cubic-bezier(0,1,.5,1)'};$.cssHooks.transform={get:function(elem){return $(elem).data('transform');},set:function(elem,v){var value=v;if(!(value instanceof Transform)){value=new Transform(value);} 17 | if(support.transform==='WebkitTransform'&&!isChrome){elem.style[support.transform]=value.toString(true);}else{elem.style[support.transform]=value.toString();} 18 | $(elem).data('transform',value);}};$.cssHooks.transformOrigin={get:function(elem){return elem.style[support.transformOrigin];},set:function(elem,value){elem.style[support.transformOrigin]=value;}};$.cssHooks.transition={get:function(elem){return elem.style[support.transition];},set:function(elem,value){elem.style[support.transition]=value;}};registerCssHook('scale');registerCssHook('translate');registerCssHook('rotate');registerCssHook('rotateX');registerCssHook('rotateY');registerCssHook('rotate3d');registerCssHook('perspective');registerCssHook('skewX');registerCssHook('skewY');registerCssHook('x',true);registerCssHook('y',true);function Transform(str){if(typeof str==='string'){this.parse(str);} 19 | return this;} 20 | Transform.prototype={setFromString:function(prop,val){var args=(typeof val==='string')?val.split(','):(val.constructor===Array)?val:[val];args.unshift(prop);Transform.prototype.set.apply(this,args);},set:function(prop){var args=Array.prototype.slice.apply(arguments,[1]);if(this.setter[prop]){this.setter[prop].apply(this,args);}else{this[prop]=args.join(',');}},get:function(prop){if(this.getter[prop]){return this.getter[prop].apply(this);}else{return this[prop]||0;}},setter:{rotate:function(theta){this.rotate=unit(theta,'deg');},rotateX:function(theta){this.rotateX=unit(theta,'deg');},rotateY:function(theta){this.rotateY=unit(theta,'deg');},scale:function(x,y){if(y===undefined){y=x;} 21 | this.scale=x+","+y;},skewX:function(x){this.skewX=unit(x,'deg');},skewY:function(y){this.skewY=unit(y,'deg');},perspective:function(dist){this.perspective=unit(dist,'px');},x:function(x){this.set('translate',x,null);},y:function(y){this.set('translate',null,y);},translate:function(x,y){if(this._translateX===undefined){this._translateX=0;} 22 | if(this._translateY===undefined){this._translateY=0;} 23 | if(x!==null){this._translateX=unit(x,'px');} 24 | if(y!==null){this._translateY=unit(y,'px');} 25 | this.translate=this._translateX+","+this._translateY;}},getter:{x:function(){return this._translateX||0;},y:function(){return this._translateY||0;},scale:function(){var s=(this.scale||"1,1").split(',');if(s[0]){s[0]=parseFloat(s[0]);} 26 | if(s[1]){s[1]=parseFloat(s[1]);} 27 | return(s[0]===s[1])?s[0]:s;},rotate3d:function(){var s=(this.rotate3d||"0,0,0,0deg").split(',');for(var i=0;i<=3;++i){if(s[i]){s[i]=parseFloat(s[i]);}} 28 | if(s[3]){s[3]=unit(s[3],'deg');} 29 | return s;}},parse:function(str){var self=this;str.replace(/([a-zA-Z0-9]+)\((.*?)\)/g,function(x,prop,val){self.setFromString(prop,val);});},toString:function(use3d){var re=[];for(var i in this){if(this.hasOwnProperty(i)){if((!support.transform3d)&&((i==='rotateX')||(i==='rotateY')||(i==='perspective')||(i==='transformOrigin'))){continue;} 30 | if(i[0]!=='_'){if(use3d&&(i==='scale')){re.push(i+"3d("+this[i]+",1)");}else if(use3d&&(i==='translate')){re.push(i+"3d("+this[i]+",0)");}else{re.push(i+"("+this[i]+")");}}}} 31 | return re.join(" ");}};function callOrQueue(self,queue,fn){if(queue===true){self.queue(fn);}else if(queue){self.queue(queue,fn);}else{fn();}} 32 | function getProperties(props){var re=[];$.each(props,function(key){key=$.camelCase(key);key=$.transit.propertyMap[key]||key;key=uncamel(key);if($.inArray(key,re)===-1){re.push(key);}});return re;} 33 | function getTransition(properties,duration,easing,delay){var props=getProperties(properties);if($.cssEase[easing]){easing=$.cssEase[easing];} 34 | var attribs=''+toMS(duration)+' '+easing;if(parseInt(delay,10)>0){attribs+=' '+toMS(delay);} 35 | var transitions=[];$.each(props,function(i,name){transitions.push(name+' '+attribs);});return transitions.join(', ');} 36 | $.fn.transition=$.fn.transit=function(properties,duration,easing,callback){var self=this;var delay=0;var queue=true;if(typeof duration==='function'){callback=duration;duration=undefined;} 37 | if(typeof easing==='function'){callback=easing;easing=undefined;} 38 | if(typeof properties.easing!=='undefined'){easing=properties.easing;delete properties.easing;} 39 | if(typeof properties.duration!=='undefined'){duration=properties.duration;delete properties.duration;} 40 | if(typeof properties.complete!=='undefined'){callback=properties.complete;delete properties.complete;} 41 | if(typeof properties.queue!=='undefined'){queue=properties.queue;delete properties.queue;} 42 | if(typeof properties.delay!=='undefined'){delay=properties.delay;delete properties.delay;} 43 | if(typeof duration==='undefined'){duration=$.fx.speeds._default;} 44 | if(typeof easing==='undefined'){easing=$.cssEase._default;} 45 | duration=toMS(duration);var transitionValue=getTransition(properties,duration,easing,delay);var work=$.transit.enabled&&support.transition;var i=work?(parseInt(duration,10)+parseInt(delay,10)):0;if(i===0){var fn=function(next){self.css(properties);if(callback){callback.apply(self);} 46 | if(next){next();}};callOrQueue(self,queue,fn);return self;} 47 | var oldTransitions={};var run=function(nextCall){var bound=false;var cb=function(){if(bound){self.unbind(transitionEnd,cb);} 48 | if(i>0){self.each(function(){this.style[support.transition]=(oldTransitions[this]||null);});} 49 | if(typeof callback==='function'){callback.apply(self);} 50 | if(typeof nextCall==='function'){nextCall();}};if((i>0)&&(transitionEnd)&&($.transit.useTransitionEnd)){bound=true;self.bind(transitionEnd,cb);}else{window.setTimeout(cb,i);} 51 | self.each(function(){if(i>0){this.style[support.transition]=transitionValue;} 52 | $(this).css(properties);});};var deferredRun=function(next){var i=0;if((support.transition==='MozTransition')&&(i<25)){i=25;} 53 | window.setTimeout(function(){run(next);},i);};callOrQueue(self,queue,deferredRun);return this;};function registerCssHook(prop,isPixels){if(!isPixels){$.cssNumber[prop]=true;} 54 | $.transit.propertyMap[prop]=support.transform;$.cssHooks[prop]={get:function(elem){var t=$(elem).css('transform')||new Transform();if(t=="none")t=new Transform();return t.get(prop);},set:function(elem,value){var t=$(elem).css('transform')||new Transform();if(t=="none")t=new Transform();t.setFromString(prop,value);$(elem).css({transform:t});}};} 55 | function uncamel(str){return str.replace(/([A-Z])/g,function(letter){return'-'+letter.toLowerCase();});} 56 | function unit(i,units){if((typeof i==="string")&&(!i.match(/^[\-0-9\.]+$/))){return i;}else{return""+i+units;}} 57 | function toMS(duration){var i=duration;if($.fx.speeds[i]){i=$.fx.speeds[i];} 58 | return unit(i,'ms');} 59 | $.transit.getTransitionValue=getTransition;})(jQuery); 60 | -------------------------------------------------------------------------------- /css/bootstrap.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v2.1.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | .clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;} 11 | .clearfix:after{clear:both;} 12 | .hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} 13 | .input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} 14 | .btn{display:inline-block;*display:inline;*zoom:1;padding:4px 14px;margin-bottom:0;font-size:14px;line-height:20px;*line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #bbbbbb;*border:0;border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;} 15 | .btn:active,.btn.active{background-color:#cccccc \9;} 16 | .btn:first-child{*margin-left:0;} 17 | .btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} 18 | .btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} 19 | .btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);} 20 | .btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 21 | .btn-large{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} 22 | .btn-large [class^="icon-"]{margin-top:2px;} 23 | .btn-small{padding:3px 9px;font-size:12px;line-height:18px;} 24 | .btn-small [class^="icon-"]{margin-top:0;} 25 | .btn-mini{padding:2px 6px;font-size:11px;line-height:17px;} 26 | .btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} 27 | .btn-block+.btn-block{margin-top:5px;} 28 | input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;} 29 | .btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} 30 | .btn{border-color:#c5c5c5;border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);} 31 | .btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #0088cc, #0044cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));background-image:-webkit-linear-gradient(top, #0088cc, #0044cc);background-image:-o-linear-gradient(top, #0088cc, #0044cc);background-image:linear-gradient(to bottom, #0088cc, #0044cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);border-color:#0044cc #0044cc #002a80;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#0044cc;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#0044cc;*background-color:#003bb3;} 32 | .btn-primary:active,.btn-primary.active{background-color:#003399 \9;} 33 | .btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#f89406;*background-color:#df8505;} 34 | .btn-warning:active,.btn-warning.active{background-color:#c67605 \9;} 35 | .btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;} 36 | .btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} 37 | .btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;} 38 | .btn-success:active,.btn-success.active{background-color:#408140 \9;} 39 | .btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;} 40 | .btn-info:active,.btn-info.active{background-color:#24748c \9;} 41 | .btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#363636;background-image:-moz-linear-gradient(top, #444444, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));background-image:-webkit-linear-gradient(top, #444444, #222222);background-image:-o-linear-gradient(top, #444444, #222222);background-image:linear-gradient(to bottom, #444444, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#222222;*background-color:#151515;} 42 | .btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} 43 | button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} 44 | button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} 45 | button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} 46 | button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} 47 | .btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 48 | .btn-link{border-color:transparent;cursor:pointer;color:#0088cc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} 49 | .btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent;} 50 | .btn-link[disabled]:hover{color:#333333;text-decoration:none;} 51 | .well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} 52 | .well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} 53 | .well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 54 | -------------------------------------------------------------------------------- /css/octocarina.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-align: center; 3 | color: #57a250; 4 | } 5 | #content { 6 | width: 808px; 7 | height: 650px; 8 | overflow: hidden; 9 | margin: auto; 10 | } 11 | 12 | #splash { 13 | text-align: center; 14 | } 15 | .splash-img { 16 | display: block; 17 | margin: auto; 18 | margin-bottom: 1em; 19 | } 20 | 21 | .btn { 22 | margin-left: 5px; 23 | margin-right: 5px; 24 | } 25 | 26 | .scene { 27 | width: 808px; 28 | height: 650px; 29 | overflow: hidden; 30 | display: inline-block; 31 | position: relative; 32 | margin-right: -808px; 33 | } 34 | .filter { 35 | width: 100%; 36 | height: 100%; 37 | overflow: visible; 38 | position: absolute; 39 | left: 0px; 40 | right: 0px; 41 | } 42 | .dark { 43 | background-color: black; 44 | } 45 | .light { 46 | background-color: white; 47 | } 48 | 49 | /* Each sprite has a width of 100px, plus a 1px border on the right which 50 | * overlaps with the next tile. A sprite also has a depth of 80px and a height 51 | * of up to 90px, for a total of 170px plus an overlapping bottom border. 52 | * 53 | * All blocks have the same height: 40px. This allows them to stack nicely. 54 | */ 55 | .layer { 56 | font-size: 0px; /* no gap between rows */ 57 | height: 480px; /* 6*80px depth */ 58 | margin-bottom: -520px; /* 6*80px depth + 40px stacking height */ 59 | position: relative; 60 | top: 40px; /* 1*40px stacking height */ 61 | } 62 | .hint-layer { 63 | margin-bottom: -480px; /* 6*80px depth */ 64 | top: 80px; 65 | } 66 | .layer-line 67 | { 68 | width: 3000px; 69 | height: 80px; 70 | margin: 0 auto; 71 | } 72 | .sprite { 73 | width: 101px; /* 100px width + 1px border */ 74 | height: 171px; /* 80px depth + 90px height + 1px border */ 75 | background-image: url('../img/sprites.png'); 76 | background-repeat: no-repeat; 77 | display: inline-block; 78 | margin-right: -1px; /* 1px border */ 79 | margin-bottom: -91px; /* 90px height + 1px border */ 80 | 81 | /* http://stackoverflow.com/questions/6953497/webkit-transform-overwrites-z-index-ordering-in-chrome-13 */ 82 | -webkit-transform: translate3d(0px, 0px, 0px); 83 | -moz-transform: translate3d(0px, 0px, 0px); 84 | } 85 | .preloader { 86 | display: none; 87 | width: 1px; 88 | height: 1px; 89 | } 90 | .empty-tile { 91 | background-position: 101px; 92 | } 93 | .floor-tile { 94 | background-position: 0px; 95 | } 96 | .wall-tile { 97 | background-position: -101px; 98 | } 99 | .closed-door-tile { 100 | background-position: -707px; 101 | } 102 | .open-door-tile { 103 | background-position: -808px; 104 | } 105 | .wooden-block-sprite { 106 | background-position: -202px; 107 | } 108 | .red-button-tile { 109 | background-position: -909px; 110 | } 111 | .pressed-red-button-tile { 112 | background-position: -1010px; 113 | } 114 | .red-block-sprite { 115 | background-position: -3030px; 116 | } 117 | .green-button-tile { 118 | background-position: -1111px; 119 | } 120 | .pressed-green-button-tile { 121 | background-position: -1212px; 122 | } 123 | .green-block-sprite { 124 | background-position: -1313px; 125 | } 126 | .blue-button-tile { 127 | background-position: -1414px; 128 | } 129 | .pressed-blue-button-tile { 130 | background-position: -1515px; 131 | } 132 | .blue-block-sprite { 133 | background-position: -1616px; 134 | } 135 | .orange-button-tile { 136 | background-position: -1717px; 137 | } 138 | .pressed-orange-button-tile { 139 | background-position: -1818px; 140 | } 141 | .orange-block-sprite { 142 | background-position: -1919px; 143 | } 144 | .blood-on-floor { 145 | background-position: -3939px; 146 | } 147 | .fork-on-floor { 148 | background-position: -4040px; 149 | } 150 | .dropped-fork { 151 | background-position: -4141px; 152 | } 153 | .forktopus { 154 | background-position: -4545px; 155 | } 156 | .forktopus.left { 157 | background-position: -4646px; 158 | } 159 | .forktopus.up { 160 | background-position: -6262px; 161 | } 162 | .forked-block-hint-sprite { 163 | background-position: -4848px; 164 | } 165 | .hint-block-sprite { 166 | background-position: -5555px; 167 | } 168 | .block-shadow { 169 | background-position: -5757px; 170 | } 171 | .gem-shadow { 172 | background-position: -5858px; 173 | } 174 | .player-shadow { 175 | background-position: -5959px; 176 | } 177 | .lover-shadow { 178 | background-position: -6060px; 179 | } 180 | .forktopus-shadow { 181 | background-position: -6161px; 182 | } 183 | 184 | /* based on http://www.cssreset.com/css3-webkit-animation-shake-links/ */ 185 | @-webkit-keyframes spaceboots { 186 | 0% { -webkit-transform: translate(2px, 1px) rotate(0deg); } 187 | 10% { -webkit-transform: translate(-1px, -2px) rotate(-1deg); } 188 | 20% { -webkit-transform: translate(-3px, 0px) rotate(1deg); } 189 | 30% { -webkit-transform: translate(0px, 2px) rotate(0deg); } 190 | 40% { -webkit-transform: translate(1px, -1px) rotate(1deg); } 191 | 50% { -webkit-transform: translate(-1px, 2px) rotate(-1deg); } 192 | 60% { -webkit-transform: translate(-3px, 1px) rotate(0deg); } 193 | 70% { -webkit-transform: translate(2px, 1px) rotate(-1deg); } 194 | 80% { -webkit-transform: translate(-1px, -1px) rotate(1deg); } 195 | 90% { -webkit-transform: translate(2px, 2px) rotate(0deg); } 196 | 100% { -webkit-transform: translate(1px, -2px) rotate(-1deg); } 197 | } 198 | @-moz-keyframes spaceboots { 199 | 0% { -moz-transform: translate(2px, 1px) rotate(0deg); } 200 | 10% { -moz-transform: translate(-1px, -2px) rotate(-1deg); } 201 | 20% { -moz-transform: translate(-3px, 0px) rotate(1deg); } 202 | 30% { -moz-transform: translate(0px, 2px) rotate(0deg); } 203 | 40% { -moz-transform: translate(1px, -1px) rotate(1deg); } 204 | 50% { -moz-transform: translate(-1px, 2px) rotate(-1deg); } 205 | 60% { -moz-transform: translate(-3px, 1px) rotate(0deg); } 206 | 70% { -moz-transform: translate(2px, 1px) rotate(-1deg); } 207 | 80% { -moz-transform: translate(-1px, -1px) rotate(1deg); } 208 | 90% { -moz-transform: translate(2px, 2px) rotate(0deg); } 209 | 100% { -moz-transform: translate(1px, -2px) rotate(-1deg); } 210 | } 211 | .shake { 212 | -webkit-animation-name: spaceboots; 213 | -webkit-animation-duration: 0.8s; 214 | -webkit-transform-origin:50% 50%; 215 | -webkit-animation-iteration-count: infinite; 216 | -webkit-animation-timing-function: linear; 217 | 218 | -moz-animation-name: spaceboots; 219 | -moz-animation-duration: 0.8s; 220 | -moz-transform-origin:50% 50%; 221 | -moz-animation-iteration-count: infinite; 222 | -moz-animation-timing-function: linear; 223 | } 224 | 225 | 226 | .holder:before { 227 | content: ""; 228 | width: 101px; /* 100px width + 1px border */ 229 | height: 171px; /* 80px depth + 90px height + 1px border */ 230 | background-image: url('../img/sprites.png'); 231 | background-repeat: no-repeat; 232 | display: inline-block; 233 | margin-right: -101px; /* 100px width + 1px border */ 234 | margin-bottom: -91px; /* 90px height + 1px border */ 235 | } 236 | .forked:before { 237 | content: ""; 238 | width: 101px; /* 100px width + 1px border */ 239 | height: 171px; /* 80px depth + 90px height + 1px border */ 240 | background-image: url('../img/sprites.png'); 241 | background-repeat: no-repeat; 242 | display: inline-block; 243 | margin-right: -101px; /* 100px width + 1px border */ 244 | margin-bottom: -91px; /* 90px height + 1px border */ 245 | background-position: -2020px; 246 | } 247 | .sporked:before { 248 | content: ""; 249 | width: 101px; /* 100px width + 1px border */ 250 | height: 171px; /* 80px depth + 90px height + 1px border */ 251 | background-image: url('../img/sprites.png'); 252 | background-repeat: no-repeat; 253 | display: inline-block; 254 | margin-right: -101px; /* 100px width + 1px border */ 255 | margin-bottom: -91px; /* 90px height + 1px border */ 256 | background-position: -4444px; 257 | } 258 | .pushing:after { 259 | content: ""; 260 | width: 101px; /* 100px width + 1px border */ 261 | height: 171px; /* 80px depth + 90px height + 1px border */ 262 | background-image: url('../img/sprites.png'); 263 | background-repeat: no-repeat; 264 | display: inline-block; 265 | margin-right: -1px; /* 1px border */ 266 | margin-bottom: -91px; /* 90px height + 1px border */ 267 | } 268 | .spotted:after { 269 | content: ""; 270 | width: 101px; /* 100px width + 1px border */ 271 | height: 171px; /* 80px depth + 90px height + 1px border */ 272 | background-image: url('../img/sprites.png'); 273 | background-repeat: no-repeat; 274 | display: inline-block; 275 | margin-right: -1px; /* 1px border */ 276 | margin-bottom: -91px; /* 90px height + 1px border */ 277 | background-position: -5656px; 278 | } 279 | 280 | 281 | /* :before and :after elements show up on *top* of the main element; 282 | * to display the fork under the player sprite, we display the fork 283 | * in the main element and the player sprite in the pseudo-element. */ 284 | .player-sprite.right:before { 285 | background-position: -303px; 286 | } 287 | .player-sprite.down:before { /* down, and default */ 288 | background-position: -404px; 289 | } 290 | .player-sprite.up { 291 | background-position: -505px; 292 | } 293 | .player-sprite.left:before { 294 | background-position: -606px; 295 | } 296 | 297 | .lover-sprite.right:before { 298 | background-position: -2424px; 299 | } 300 | .lover-sprite.down:before { 301 | background-position: -2525px; 302 | } 303 | .lover-sprite.up { 304 | background-position: -2626px; 305 | } 306 | .lover-sprite.left:before { 307 | background-position: -2727px; 308 | } 309 | 310 | .holder.right { 311 | background-position: -5454px; 312 | } 313 | .holder.down { 314 | background-position: -5353px; 315 | } 316 | .holder.left { 317 | background-position: -5353px; 318 | } 319 | .holder.up:before { 320 | background-position: -5353px; 321 | } 322 | 323 | .holder.forked.right { 324 | background-position: -2222px; 325 | } 326 | .holder.forked.down { 327 | background-position: -2121px; 328 | } 329 | .holder.forked.left { 330 | background-position: -2121px; 331 | } 332 | .holder.forked.up:before { 333 | background-position: -2121px; 334 | } 335 | 336 | .holder.sporked.right { 337 | background-position: -4343px; 338 | } 339 | .holder.sporked.down { 340 | background-position: -4242px; 341 | } 342 | .holder.sporked.left { 343 | background-position: -4242px; 344 | } 345 | .holder.sporked.up:before { 346 | background-position: -4242px; 347 | } 348 | 349 | .forktopus.sporked { 350 | background-position: -6363px; 351 | } 352 | .forktopus.sporked:before { 353 | background-position: 101px; 354 | } 355 | 356 | .pushing.right:after { 357 | background-position: -4949px; 358 | } 359 | .pushing.down:after { 360 | background-position: -5050px; 361 | } 362 | .pushing.up:after { 363 | background-position: -5151px; 364 | } 365 | .pushing.left:after { 366 | background-position: -5252px; 367 | } 368 | 369 | .under-door { 370 | background-position: -2323px !important; 371 | } 372 | .under-door:before { 373 | background-position: -2323px !important; 374 | } 375 | 376 | .say:after { 377 | content: ""; 378 | width: 101px; /* 100px width + 1px border */ 379 | height: 171px; /* 80px depth + 90px height + 1px border */ 380 | background-image: url('../img/sprites.png'); 381 | background-repeat: no-repeat; 382 | display: inline-block; 383 | margin-right: -1px; /* 1px border */ 384 | margin-bottom: -91px; /* 90px height + 1px border */ 385 | position: relative; 386 | left: -70px; 387 | top: -15px; 388 | } 389 | .say.heart:after { 390 | background-position: -2828px; 391 | } 392 | .say.press-z:after { 393 | background-position: -2929px; 394 | } 395 | .say.door-question:after { 396 | background-position: -3131px; 397 | } 398 | .say.door-exclam:after { 399 | background-position: -3232px; 400 | } 401 | .say.question:after { 402 | background-position: -3333px; 403 | } 404 | .say.exclam:after { 405 | background-position: -3434px; 406 | } 407 | .say.spork-question:after { 408 | background-position: -3535px; 409 | } 410 | .say.r-question:after { 411 | background-position: -3636px; 412 | } 413 | .say.fork-question:after { 414 | background-position: -3737px; 415 | } 416 | .say.fork-exclam:after { 417 | background-position: -3838px; 418 | } 419 | .say.spork-exclam:after { 420 | background-position: -4747px; 421 | } 422 | 423 | 424 | .level-name { 425 | font-size: 24px; 426 | text-align: right; 427 | margin-top: -1em; 428 | } 429 | 430 | 431 | #hidden { 432 | display: none; 433 | } 434 | 435 | #credits { 436 | color: white; 437 | text-align: center; 438 | } 439 | #credits h3 { 440 | font-size: 24px; 441 | margin-top: 0px; 442 | margin-bottom: 0px; 443 | padding-top: 0px; 444 | padding-bottom: 20px; 445 | } 446 | #credits h4 { 447 | font-size: 22px; 448 | margin-top: 20px; 449 | margin-bottom: 0px; 450 | padding-top: 0px; 451 | padding-bottom: 5px; 452 | } 453 | #credits ul { 454 | list-style-type: none; 455 | margin-top: 0px; 456 | margin-bottom: 0px; 457 | margin-left: 0px; 458 | margin-right: 0px; 459 | padding-top: 0px; 460 | padding-bottom: 0px; 461 | padding-left: 0px; 462 | padding-right: 0px; 463 | } 464 | #credits img { 465 | height: 300px; 466 | padding: 20px; 467 | } 468 | #credits a { 469 | color: white; 470 | } 471 | #credits a:visited { 472 | color: white; 473 | } 474 | #credits .spacing { 475 | height: 280px; 476 | } 477 | 478 | #thanks { 479 | color: white; 480 | text-align: center; 481 | margin-top: 300px; 482 | } 483 | #congratulations { 484 | color: white; 485 | text-align: center; 486 | } 487 | 488 | #footer { 489 | position: absolute; 490 | bottom: 0px; 491 | height: 2em; 492 | color: #aaa; 493 | z-index: -1; 494 | } 495 | #footer a:link {color: #888;} 496 | #footer a:visited {color: #888;} 497 | #footer a:hover {color: #ccc;} 498 | #footer a:active {color: #ccc;} 499 | -------------------------------------------------------------------------------- /css/bootstrap.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v2.1.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | .clearfix { 11 | *zoom: 1; 12 | } 13 | .clearfix:before, 14 | .clearfix:after { 15 | display: table; 16 | content: ""; 17 | line-height: 0; 18 | } 19 | .clearfix:after { 20 | clear: both; 21 | } 22 | .hide-text { 23 | font: 0/0 a; 24 | color: transparent; 25 | text-shadow: none; 26 | background-color: transparent; 27 | border: 0; 28 | } 29 | .input-block-level { 30 | display: block; 31 | width: 100%; 32 | min-height: 30px; 33 | -webkit-box-sizing: border-box; 34 | -moz-box-sizing: border-box; 35 | box-sizing: border-box; 36 | } 37 | .btn { 38 | display: inline-block; 39 | *display: inline; 40 | /* IE7 inline-block hack */ 41 | 42 | *zoom: 1; 43 | padding: 4px 14px; 44 | margin-bottom: 0; 45 | font-size: 14px; 46 | line-height: 20px; 47 | *line-height: 20px; 48 | text-align: center; 49 | vertical-align: middle; 50 | cursor: pointer; 51 | color: #333333; 52 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); 53 | background-color: #f5f5f5; 54 | background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); 55 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); 56 | background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); 57 | background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); 58 | background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); 59 | background-repeat: repeat-x; 60 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); 61 | border-color: #e6e6e6 #e6e6e6 #bfbfbf; 62 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 63 | *background-color: #e6e6e6; 64 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 65 | 66 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 67 | border: 1px solid #bbbbbb; 68 | *border: 0; 69 | border-bottom-color: #a2a2a2; 70 | -webkit-border-radius: 4px; 71 | -moz-border-radius: 4px; 72 | border-radius: 4px; 73 | *margin-left: .3em; 74 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 75 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 76 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 77 | } 78 | .btn:hover, 79 | .btn:active, 80 | .btn.active, 81 | .btn.disabled, 82 | .btn[disabled] { 83 | color: #333333; 84 | background-color: #e6e6e6; 85 | *background-color: #d9d9d9; 86 | } 87 | .btn:active, 88 | .btn.active { 89 | background-color: #cccccc \9; 90 | } 91 | .btn:first-child { 92 | *margin-left: 0; 93 | } 94 | .btn:hover { 95 | color: #333333; 96 | text-decoration: none; 97 | background-color: #e6e6e6; 98 | *background-color: #d9d9d9; 99 | /* Buttons in IE7 don't get borders, so darken on hover */ 100 | 101 | background-position: 0 -15px; 102 | -webkit-transition: background-position 0.1s linear; 103 | -moz-transition: background-position 0.1s linear; 104 | -o-transition: background-position 0.1s linear; 105 | transition: background-position 0.1s linear; 106 | } 107 | .btn:focus { 108 | outline: thin dotted #333; 109 | outline: 5px auto -webkit-focus-ring-color; 110 | outline-offset: -2px; 111 | } 112 | .btn.active, 113 | .btn:active { 114 | background-color: #e6e6e6; 115 | background-color: #d9d9d9 \9; 116 | background-image: none; 117 | outline: 0; 118 | -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 119 | -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 120 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 121 | } 122 | .btn.disabled, 123 | .btn[disabled] { 124 | cursor: default; 125 | background-color: #e6e6e6; 126 | background-image: none; 127 | opacity: 0.65; 128 | filter: alpha(opacity=65); 129 | -webkit-box-shadow: none; 130 | -moz-box-shadow: none; 131 | box-shadow: none; 132 | } 133 | .btn-large { 134 | padding: 9px 14px; 135 | font-size: 16px; 136 | line-height: normal; 137 | -webkit-border-radius: 5px; 138 | -moz-border-radius: 5px; 139 | border-radius: 5px; 140 | } 141 | .btn-large [class^="icon-"] { 142 | margin-top: 2px; 143 | } 144 | .btn-small { 145 | padding: 3px 9px; 146 | font-size: 12px; 147 | line-height: 18px; 148 | } 149 | .btn-small [class^="icon-"] { 150 | margin-top: 0; 151 | } 152 | .btn-mini { 153 | padding: 2px 6px; 154 | font-size: 11px; 155 | line-height: 17px; 156 | } 157 | .btn-block { 158 | display: block; 159 | width: 100%; 160 | padding-left: 0; 161 | padding-right: 0; 162 | -webkit-box-sizing: border-box; 163 | -moz-box-sizing: border-box; 164 | box-sizing: border-box; 165 | } 166 | .btn-block + .btn-block { 167 | margin-top: 5px; 168 | } 169 | input[type="submit"].btn-block, 170 | input[type="reset"].btn-block, 171 | input[type="button"].btn-block { 172 | width: 100%; 173 | } 174 | .btn-primary.active, 175 | .btn-warning.active, 176 | .btn-danger.active, 177 | .btn-success.active, 178 | .btn-info.active, 179 | .btn-inverse.active { 180 | color: rgba(255, 255, 255, 0.75); 181 | } 182 | .btn { 183 | border-color: #c5c5c5; 184 | border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25); 185 | } 186 | .btn-primary { 187 | color: #ffffff; 188 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 189 | background-color: #006dcc; 190 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 191 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 192 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 193 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 194 | background-image: linear-gradient(to bottom, #0088cc, #0044cc); 195 | background-repeat: repeat-x; 196 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); 197 | border-color: #0044cc #0044cc #002a80; 198 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 199 | *background-color: #0044cc; 200 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 201 | 202 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 203 | } 204 | .btn-primary:hover, 205 | .btn-primary:active, 206 | .btn-primary.active, 207 | .btn-primary.disabled, 208 | .btn-primary[disabled] { 209 | color: #ffffff; 210 | background-color: #0044cc; 211 | *background-color: #003bb3; 212 | } 213 | .btn-primary:active, 214 | .btn-primary.active { 215 | background-color: #003399 \9; 216 | } 217 | .btn-warning { 218 | color: #ffffff; 219 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 220 | background-color: #faa732; 221 | background-image: -moz-linear-gradient(top, #fbb450, #f89406); 222 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); 223 | background-image: -webkit-linear-gradient(top, #fbb450, #f89406); 224 | background-image: -o-linear-gradient(top, #fbb450, #f89406); 225 | background-image: linear-gradient(to bottom, #fbb450, #f89406); 226 | background-repeat: repeat-x; 227 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); 228 | border-color: #f89406 #f89406 #ad6704; 229 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 230 | *background-color: #f89406; 231 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 232 | 233 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 234 | } 235 | .btn-warning:hover, 236 | .btn-warning:active, 237 | .btn-warning.active, 238 | .btn-warning.disabled, 239 | .btn-warning[disabled] { 240 | color: #ffffff; 241 | background-color: #f89406; 242 | *background-color: #df8505; 243 | } 244 | .btn-warning:active, 245 | .btn-warning.active { 246 | background-color: #c67605 \9; 247 | } 248 | .btn-danger { 249 | color: #ffffff; 250 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 251 | background-color: #da4f49; 252 | background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); 253 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); 254 | background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); 255 | background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); 256 | background-image: linear-gradient(to bottom, #ee5f5b, #bd362f); 257 | background-repeat: repeat-x; 258 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0); 259 | border-color: #bd362f #bd362f #802420; 260 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 261 | *background-color: #bd362f; 262 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 263 | 264 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 265 | } 266 | .btn-danger:hover, 267 | .btn-danger:active, 268 | .btn-danger.active, 269 | .btn-danger.disabled, 270 | .btn-danger[disabled] { 271 | color: #ffffff; 272 | background-color: #bd362f; 273 | *background-color: #a9302a; 274 | } 275 | .btn-danger:active, 276 | .btn-danger.active { 277 | background-color: #942a25 \9; 278 | } 279 | .btn-success { 280 | color: #ffffff; 281 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 282 | background-color: #5bb75b; 283 | background-image: -moz-linear-gradient(top, #62c462, #51a351); 284 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); 285 | background-image: -webkit-linear-gradient(top, #62c462, #51a351); 286 | background-image: -o-linear-gradient(top, #62c462, #51a351); 287 | background-image: linear-gradient(to bottom, #62c462, #51a351); 288 | background-repeat: repeat-x; 289 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0); 290 | border-color: #51a351 #51a351 #387038; 291 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 292 | *background-color: #51a351; 293 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 294 | 295 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 296 | } 297 | .btn-success:hover, 298 | .btn-success:active, 299 | .btn-success.active, 300 | .btn-success.disabled, 301 | .btn-success[disabled] { 302 | color: #ffffff; 303 | background-color: #51a351; 304 | *background-color: #499249; 305 | } 306 | .btn-success:active, 307 | .btn-success.active { 308 | background-color: #408140 \9; 309 | } 310 | .btn-info { 311 | color: #ffffff; 312 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 313 | background-color: #49afcd; 314 | background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); 315 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); 316 | background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); 317 | background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); 318 | background-image: linear-gradient(to bottom, #5bc0de, #2f96b4); 319 | background-repeat: repeat-x; 320 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0); 321 | border-color: #2f96b4 #2f96b4 #1f6377; 322 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 323 | *background-color: #2f96b4; 324 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 325 | 326 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 327 | } 328 | .btn-info:hover, 329 | .btn-info:active, 330 | .btn-info.active, 331 | .btn-info.disabled, 332 | .btn-info[disabled] { 333 | color: #ffffff; 334 | background-color: #2f96b4; 335 | *background-color: #2a85a0; 336 | } 337 | .btn-info:active, 338 | .btn-info.active { 339 | background-color: #24748c \9; 340 | } 341 | .btn-inverse { 342 | color: #ffffff; 343 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 344 | background-color: #363636; 345 | background-image: -moz-linear-gradient(top, #444444, #222222); 346 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222)); 347 | background-image: -webkit-linear-gradient(top, #444444, #222222); 348 | background-image: -o-linear-gradient(top, #444444, #222222); 349 | background-image: linear-gradient(to bottom, #444444, #222222); 350 | background-repeat: repeat-x; 351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0); 352 | border-color: #222222 #222222 #000000; 353 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 354 | *background-color: #222222; 355 | /* Darken IE7 buttons by default so they stand out more given they won't have borders */ 356 | 357 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 358 | } 359 | .btn-inverse:hover, 360 | .btn-inverse:active, 361 | .btn-inverse.active, 362 | .btn-inverse.disabled, 363 | .btn-inverse[disabled] { 364 | color: #ffffff; 365 | background-color: #222222; 366 | *background-color: #151515; 367 | } 368 | .btn-inverse:active, 369 | .btn-inverse.active { 370 | background-color: #080808 \9; 371 | } 372 | button.btn, 373 | input[type="submit"].btn { 374 | *padding-top: 3px; 375 | *padding-bottom: 3px; 376 | } 377 | button.btn::-moz-focus-inner, 378 | input[type="submit"].btn::-moz-focus-inner { 379 | padding: 0; 380 | border: 0; 381 | } 382 | button.btn.btn-large, 383 | input[type="submit"].btn.btn-large { 384 | *padding-top: 7px; 385 | *padding-bottom: 7px; 386 | } 387 | button.btn.btn-small, 388 | input[type="submit"].btn.btn-small { 389 | *padding-top: 3px; 390 | *padding-bottom: 3px; 391 | } 392 | button.btn.btn-mini, 393 | input[type="submit"].btn.btn-mini { 394 | *padding-top: 1px; 395 | *padding-bottom: 1px; 396 | } 397 | .btn-link, 398 | .btn-link:active, 399 | .btn-link[disabled] { 400 | background-color: transparent; 401 | background-image: none; 402 | -webkit-box-shadow: none; 403 | -moz-box-shadow: none; 404 | box-shadow: none; 405 | } 406 | .btn-link { 407 | border-color: transparent; 408 | cursor: pointer; 409 | color: #0088cc; 410 | -webkit-border-radius: 0; 411 | -moz-border-radius: 0; 412 | border-radius: 0; 413 | } 414 | .btn-link:hover { 415 | color: #005580; 416 | text-decoration: underline; 417 | background-color: transparent; 418 | } 419 | .btn-link[disabled]:hover { 420 | color: #333333; 421 | text-decoration: none; 422 | } 423 | .well { 424 | min-height: 20px; 425 | padding: 19px; 426 | margin-bottom: 20px; 427 | background-color: #f5f5f5; 428 | border: 1px solid #e3e3e3; 429 | -webkit-border-radius: 4px; 430 | -moz-border-radius: 4px; 431 | border-radius: 4px; 432 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 433 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 434 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 435 | } 436 | .well blockquote { 437 | border-color: #ddd; 438 | border-color: rgba(0, 0, 0, 0.15); 439 | } 440 | .well-large { 441 | padding: 24px; 442 | -webkit-border-radius: 6px; 443 | -moz-border-radius: 6px; 444 | border-radius: 6px; 445 | } 446 | .well-small { 447 | padding: 9px; 448 | -webkit-border-radius: 3px; 449 | -moz-border-radius: 3px; 450 | border-radius: 3px; 451 | } 452 | -------------------------------------------------------------------------------- /js/World.js: -------------------------------------------------------------------------------- 1 | // all the rooms in the world. here for you. 2 | 3 | var World = { 4 | levels: [ 5 | { // Level 0 6 | name: "Move using the arrow keys", 7 | ascii: ["#####d##", 8 | "#......#", 9 | "#......#", 10 | "#.c....#", 11 | "#......#", 12 | "########"] 13 | } 14 | , 15 | { // Level 1 16 | ascii: ["#D##.###", 17 | ".c.#.###", 18 | "...#.#d#", 19 | "w#w#....", 20 | "...w....", 21 | "...w.Lw."], 22 | on_start: [ 23 | 600, 'face_left', '!', 24 | 'left', 200, 'up', 200, 'up', 200, 'up', 200, 'up', 200, 25 | 'face_left', '<3', 0, 'player_face_right', 'player_<3', 1000, 26 | 27 | 'face_down', 300, 'down', 300, 'down', 300, 'down', 300, 28 | 'down', 300, 'right', 29 | 30 | 'face_left', '!', 31 | 32 | 'face_right', 'Z', 0, 'fork', 33 | 34 | 'face_up', 200, 'up', 200, 'up', 200, 'right', 200, 35 | 'face_down', 36 | 'fork!' ], 37 | position_animations: { 38 | '5,5': [0, 'reminder'], 39 | '6,4': [0, 'reminder'] 40 | }, 41 | on_unfork: [ 'face_left', '<3' ] 42 | } 43 | , 44 | { // Level 2 45 | ascii: ["#D######", 46 | "#cLw,,,#", 47 | "#,....,#", 48 | "#,....,#", 49 | "#,2..W,#", 50 | "#,,,,,,#"], 51 | on_kiss: [ 0, 'ask-fork', 'give' ], 52 | position_animations: { 53 | '5,3': [0, 'reminder'], 54 | '4,4': [0, 'reminder'] 55 | }, 56 | on_fork: [ '<3', 57 | 'down', 300, 'right', 300, 'right', 300, 'up', 300, 58 | 'face_left', 'left', 'left', 'face_down', 59 | 60 | 'player_face_left', 'player_?', '!', 61 | 'hint' ], 62 | on_solved: [ 'Z' ], 63 | on_unfork: [ 'face_down', 'R?' ], 64 | on_unfork_solved: [ 65 | '<3', 66 | 'left', 300, 'face_up', 'open', 67 | 'face_right', 300, 'right', 300, 'face_down', 68 | 'door?'] 69 | } 70 | , 71 | { // Level 3 72 | ascii: ["####D###", 73 | "###.....", 74 | "###...#.", 75 | ".cL..#..", 76 | "....#...", 77 | "....#,gG"], 78 | on_start: [ 1000, 'face_left', 'kiss', 'face_down', '<3' ], 79 | on_kiss: [ 0, 'ask-fork', 'give' ] 80 | } 81 | , 82 | { // Level 4 83 | name: "1 / 25", 84 | ascii: [" #d#", 85 | " #.#", 86 | " #.#", 87 | "######.#", 88 | "Lc.w....", 89 | "########"], 90 | on_kiss: [ 0, 'ask-fork', 'give' ] 91 | } 92 | , 93 | { // Level 5 -- lover makes a mistake 94 | name: "2 / 25", 95 | ascii: ["###d###", 96 | "###.###", 97 | "##.w.##", 98 | "#.w.w.#", 99 | ".w.w.w.", 100 | "wcwLw.w"], 101 | on_start: [ 102 | 0, 'face_up', 300, 103 | 'up', 'left', 'up', 1000, 104 | 'face_right', 300, 'face_up', 'face_right', 1000, 105 | 'face_down', 'R?'], 106 | on_reset: [ 'skip' ], 107 | on_kiss: [ 0, 'R?' ] 108 | } 109 | , 110 | { // Level 6 111 | name: "2 / 25", 112 | ascii: ["###d###", 113 | "###.###", 114 | "##.w.##", 115 | "#.w.w.#", 116 | ".w.w.w.", 117 | "wcwLw.w"], 118 | on_kiss: [ 0, 'ask-fork', 'give' ] 119 | } 120 | , 121 | { // Level 7 122 | name: "3 / 25", 123 | ascii: ["###d###", 124 | "###.###", 125 | "##.w.##", 126 | "##w.W##", 127 | "#Lc...#", 128 | "#...2.#"], 129 | on_kiss: [ 0, 'ask-fork', 'give' ], 130 | on_fork: [ 'hint' ], 131 | on_solved: [ 'fork!' ] 132 | } 133 | , 134 | { // Level 8 135 | name: "4 / 25", 136 | ascii: [" #####", 137 | " #..#D#", 138 | "###..o,#", 139 | "....##.#", 140 | ".L.w....", 141 | ".c.O####"], 142 | on_kiss: [ 0, 'ask-fork', 'give' ] 143 | } 144 | , 145 | { // Level 9 146 | name: "5 / 25", 147 | ascii: ["########", 148 | ".L.w.#d#", 149 | ".c.w...#", 150 | "...w.###", 151 | "########"], 152 | on_kiss: [ 0, 'ask-fork', 'give' ] 153 | } 154 | , 155 | { // Level 10 156 | name: "6 / 25", 157 | ascii: ["#d######", 158 | ".....,..", 159 | "####www#", 160 | ".L.w..,w", 161 | ".c.w...w"], 162 | on_kiss: [ 0, 'ask-fork', 'give' ] 163 | } 164 | , 165 | { // Level 11 166 | name: "7 / 25", 167 | ascii: ["#.w.####", 168 | "c.w.####", 169 | "LgwG##D#", 170 | "..w.#...", 171 | "#.w....."], 172 | on_start: [ 173 | 'down', 300, 'right', 300, 'down', 300, 'face_right', 174 | 'right', 'right', 'right', 'right', 175 | 'face_up', 300, 'up', 300, 'right', 300, 'face_up', 1500, 176 | 'face_left', 'door?', 'level_up' ], 177 | on_solved: [ 178 | 'face_up', 1000, 179 | 'face_left', 'door!', 180 | 'face_up', 300, 'up', 300, 'leave'], 181 | on_kiss: [ 0, 'ask-fork', 'give' ] 182 | } 183 | , 184 | { // Level 12 185 | name: "7 / 25", 186 | ascii: ["#.w.####", 187 | "c.w.####", 188 | ".gwG##D#", 189 | "..w.#.L.", 190 | "#.....w."], 191 | on_solved: [ 192 | 'face_up', 1000, 193 | 'face_left', 'door!', 194 | 'face_up', 300, 'up', 300, 'leave'], 195 | on_kiss: [ 0, 'ask-fork', 'give' ] 196 | } 197 | , 198 | { // Level 13 199 | name: "8 / 25", 200 | ascii: ["#######d", 201 | ".w.w.w..", 202 | ".w.w.wL.", 203 | "cw.w.w.#", 204 | ".w.w.w.#", 205 | ".w.w.w.#"], 206 | on_start: [ 0, 'face_right', 207 | 'face_right', 300, 'right', 300, 'up', 300, 'up', 300, 'leave' ] 208 | } 209 | , 210 | { // Level 14 -- Foreshadowing 211 | ascii: [" #...#", 212 | " ..g..", 213 | "## .....", 214 | ".. . ", 215 | "...o...o", 216 | "........", 217 | "........", 218 | "........", 219 | "........", 220 | ".....F.."], 221 | on_start: [ 222 | 'octo_up', 800, 'octo_up', 800, 'octo_up', 800, 'octo_up', 800, 223 | 'octo_up', 800, 'octo_up', 800, 'octo_up', 800, 224 | 'octo_spork', 800, 'skip' ] 225 | } 226 | , 227 | { // Level 15 228 | name: "9 / 25", 229 | ascii: ["#####D#", 230 | "c..#...", 231 | ".#.#...", 232 | "Ggo..O.", 233 | "...#..."] 234 | } 235 | , 236 | { // Level 16 237 | name: "10 / 25", 238 | ascii: ["##D##", 239 | ".....", 240 | ".w.w.", 241 | "wgwGw", 242 | ".w.w.", 243 | "..c.."] 244 | } 245 | , 246 | { // Level 17 -- something terrible has happened 247 | name: "", 248 | ascii: ["###D######", 249 | "..#.#...;;", 250 | "........;;", 251 | "c.......;;", 252 | "...fw...;;"], 253 | position_animations: { 254 | "3,4": ['face_down', 'pick', 'open'] 255 | } 256 | } 257 | , 258 | { // Level 18 259 | name: "11 / 25", 260 | ascii: ["#d###", 261 | ".....", 262 | "###.#", 263 | "?.ww.", 264 | ".....", 265 | "..C.."] 266 | } 267 | , 268 | { // Level 19 269 | name: "12 / 25", 270 | ascii: ["#D###..", 271 | "..GGgg.", 272 | "....C.."] 273 | } 274 | , 275 | { // Level 20 276 | name: "13 / 25", 277 | ascii: ["#####D#", 278 | "C..w..#", 279 | ".Gg..##"] 280 | } 281 | , 282 | { // Level 21 283 | name: "14 / 25", 284 | ascii: ["#####D", 285 | "C..#..", 286 | ".#.#..", 287 | "Ggo..O", 288 | "...#.."] 289 | } 290 | , 291 | { // Level 22 292 | name: "15 / 25", 293 | ascii: ["###D#", 294 | "#CB.#", 295 | "#.b.#", 296 | "#wow#", 297 | "#.O.#"] 298 | } 299 | , 300 | { // Level 23 301 | name: "16 / 25", 302 | ascii: ["#####D#", 303 | "..w.w.#", 304 | "C.Gg..#", 305 | "..w.w.#", 306 | "#######"] 307 | } 308 | , 309 | { // Level 24 310 | name: "17 / 25", 311 | ascii: ["####D#", 312 | "#....#", 313 | "#CgwG#", 314 | "#..#.#", 315 | "#.##.#", 316 | "#....#"] 317 | } 318 | , 319 | { // Level 25 320 | name: "18 / 25", 321 | ascii: ["...####", 322 | ".C.####", 323 | ".b.#B#D", 324 | ".w...w.", 325 | ".wwwww.", 326 | "......."] 327 | } 328 | , 329 | { // Level 26 330 | name: "19 / 25", 331 | ascii: ["#D####", 332 | "#.C.##", 333 | "#.ogOG", 334 | "###.##"] 335 | } 336 | , 337 | { // Level 27 338 | name: "20 / 25", 339 | ascii: ["###D#", 340 | "#...#", 341 | "#.g.#", 342 | "#www#", 343 | "#...#", 344 | "#CG.#"] 345 | } 346 | , 347 | { // Level 28 348 | name: "21 / 25", 349 | ascii: [" ###D#", 350 | " Gg..#", 351 | "CGg..#", 352 | " Gg..#"] 353 | } 354 | , 355 | { // Level 29 356 | name: "22 / 25", 357 | ascii: ["#.w.###", 358 | "..w.###", 359 | "CgwG###", 360 | "..w.#D#", 361 | "#.w...#"] 362 | } 363 | , 364 | { // Level 30 365 | name: "23 / 25", 366 | ascii: ["###D###", 367 | "..w.w..", 368 | ".wgwGw.", 369 | "..w.w..", 370 | "...C..."] 371 | } 372 | , 373 | { // Level 31 374 | name: "24 / 25", 375 | ascii: ["###D##", 376 | "..Rr..", 377 | "C#..#.", 378 | "..gG.."] 379 | } 380 | , 381 | { // Level 32 382 | name: "25 / 25", 383 | ascii: ["###D###", 384 | "###.###", 385 | "#..G..#", 386 | "#wwwww#", 387 | "#..g..#", 388 | "##.C.##"] 389 | } 390 | , 391 | { // Level 33 -- The lair 392 | ascii: [" #...#", 393 | " ..g..", 394 | "## .....", 395 | ".. . ", 396 | "...o...o", 397 | "........", 398 | "........", 399 | "........", 400 | ".c......", 401 | ".....F.."], 402 | last_level: true, 403 | on_start: [ 404 | 0, 'hidden', 405 | 0, 'octo_up', 0, 'octo_up', 0, 'octo_up', 0, 'octo_up', 406 | 0, 'octo_up', 0, 'octo_up', 0, 'octo_up', 407 | 0, 'octo_spork', 408 | 0, 'octo_down', 0, 'octo_down', 0, 'octo_down', 0, 'octo_down', 409 | 0, 'octo_down', 0, 'octo_down', 0, 'octo_down', 0, 'octo_down', 410 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 411 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 412 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 413 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 414 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 415 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 416 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 417 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 418 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 419 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 420 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 421 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 422 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 423 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 424 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 425 | 0, 'octo_left', 0, 'octo_right', 0, 'octo_left', 0, 'octo_right', 426 | 0, 'unhidden', 427 | 0, 'update_forked_block', 428 | 'player_up', 'player_up'], 429 | on_spork: [ 430 | 0, 'fork', 431 | 'octo_face_left', 'octo_face_right', 'spork!', 432 | 'octo_escape', 600, 'octo_escape', 600, 433 | 'octo_escape', 600, 'octo_escape', 600, 434 | 'octo_escape', 600, 'octo_escape', 600, 435 | 'octo_down', 'check_trap', 436 | 'skip' 437 | ], 438 | on_trapped: [ 439 | 'octo_face_left', 'octo_face_right', '!' 440 | ] 441 | } 442 | , 443 | { // Level 34 -- second visit 444 | name: "9 / 25", 445 | long_ending: true, 446 | ascii: ["#####D#", 447 | "S..#...", 448 | ".#.#...", 449 | "Ggo..O.", 450 | "...#..."] 451 | } 452 | , 453 | { // Level 35 -- second visit 454 | name: "10 / 25", 455 | ascii: ["##d##", 456 | "w.w..", 457 | ".wLw.", 458 | "...gw", 459 | ".w.w.", 460 | "..S.."], 461 | on_start: [ 462 | 0, 'face_up', 463 | 'face_left', 500, 'left', 500, 'face_up', 300, 'up', 300, 464 | 'face_right', 'right', 500, 'face_up', 300, 'up', 300, 'leave'] 465 | } 466 | , 467 | { // Level 36 -- something terrible, explained 468 | ascii: ["###D######", 469 | "..#.#.....", 470 | "..........", 471 | "L.........", 472 | "....w....."], 473 | bad_ending: true, 474 | on_start: [ 475 | 0, 'face_right', 'right', 'right', 'right', 'face_up', 476 | 'up', 500, 'up', 1500, 'door?', 477 | 'face_down', 500, 'down', 500, 'down', 500, 478 | 'open', 300, 'octo_appear', 300, 'close', 0, 479 | 'face_up', 'face_left', 300, 'face_right', 300, 'face_up', '!', 480 | 'down', 0, 'face_up', 1200, 481 | 'octo_down', 600, 'octo_down', 600, 482 | 'lover_dies', 483 | 'octo_up', 600, 'octo_up', 600, 484 | 'open', 300, 'octo_disappear', 300, 'close', 300, 485 | 'credits', 486 | 'c_appear', 487 | 'show', 488 | 'player_right', 'player_right', 'player_down', 'player_right', 489 | 'pick', 'open', 'face_up', 1500, 490 | 'player_up', 'player_up', 'player_up', 'player_up', 'leave', 491 | 'the_end', 'unlock1' 492 | ] 493 | } 494 | , 495 | { // Level 37 -- something terrible, avoided 496 | ascii: ["###D######", 497 | "..#.#.....", 498 | "..........", 499 | "L.........", 500 | "....w....."], 501 | good_ending: true, 502 | on_start: [ 503 | 0, 'face_right', 'right', 'right', 'right', 'face_up', 504 | 'up', 500, 'up', 1500, 'door?', 505 | 'face_down', 500, 'down', 500, 'down', 500, 506 | 'down', 507 | 'face_right', '?', 'fork', 508 | 'open', 300, 'octo_appear', 300, 'close', 0, 509 | 'face_up', 'face_left', 300, 'face_right', 300, 'face_up', 600, 510 | 'octo_down', 1200, '!', 0, 'octo_down', 300, 511 | 'face_right', 300, 'fork', 512 | 'face_down', '!', 513 | 'face_up', 200, 'up', 200, 'right', 200, 'right', 200, 'up', 200, 'up', 200, 'face_down', 514 | 'open', 'octo_appear', 1200, 515 | 'octo_down', 1200, 'octo_down', 1200, 'octo_down', 600, 516 | 'down', 150, 'left', 150, 'left', 150, 'down', 150, 'stab', 517 | 'octo_dies', 518 | '!', 'up', 'up', 'up', 300, 'leave', 300, 'close', 0, 519 | 'credits', 520 | 'c_appear', 521 | 'show', 522 | 'player_right', 'player_right', 'player_down', 'player_right', 523 | 'pick', 'open', 'player_face_up', 1500, 524 | 'player_up', 'player_up', 'player_up', 'player_up', 'leave', 525 | 'the_end', 'unlock2' 526 | ] 527 | } 528 | , 529 | { // Level 38 -- nothing terrible has ever happened 530 | ascii: ["###D######", 531 | "..#.#.....", 532 | "..........", 533 | "L.........", 534 | "....w....."], 535 | best_ending: true, 536 | on_start: [ 537 | 0, 'face_right', 'right', 'right', 'right', 'face_up', 538 | 'up', 500, 'up', 1500, 'door?', 539 | 'face_down', 500, 'down', 500, 'down', 500, 540 | 'S_appear', 0, 'player_face_right', 541 | 'face_left', '!', 542 | 'left', 0, 'player_right', 543 | 'saved', 0, '<3', 0, 'saved_end', 1000, 544 | 'player_face_down', 0, 'face_down', 0, 545 | 'credits', 546 | 'show', 547 | 'face_left', 'spork?', 548 | 'the_end', 'unlock3' 549 | ] 550 | } 551 | , 552 | { // Level 39 553 | name: "Bonus Level — 1 / 3", 554 | first_bonus: true, 555 | ascii: ["##D##", 556 | "R...G", 557 | ".rwg.", 558 | ".w.w.", 559 | ".bwo.", 560 | "B.S.O"] 561 | } 562 | , 563 | { // Level 40 564 | name: "Bonus Level — 2 / 3", 565 | ascii: ["##D##", 566 | "G...R", 567 | ".rwg.", 568 | ".w.w.", 569 | ".bwo.", 570 | "O.S.B"] 571 | } 572 | , 573 | { // Level 41 574 | name: "Bonus Level — 3 / 3", 575 | ascii: ["#####", 576 | "O...B", 577 | ".wwg.", 578 | ".w.w.", 579 | ".bwo.", 580 | "G.S.."], 581 | on_solved: [ 'credits', 'the_end', 'unlock4' ] 582 | } 583 | ], 584 | 585 | index_of_special_level: function(feature) { 586 | if (!this[feature]) { 587 | for(var i=0; i 5) { 103 | room().move_forktopus(-1, 0); 104 | } else { 105 | room().move_forktopus(0, 1); 106 | } 107 | process_events(); 108 | }, 109 | 'octo_spork': function() { 110 | var f = room().forktopus; 111 | f.forked = 'sporked'; 112 | use_fork(f); 113 | }, 114 | 'octo_face_left': function() { 115 | var f = room().forktopus; 116 | f.dir = Pos.create(-1, 0); 117 | update_moveable(f); 118 | }, 119 | 'octo_face_right': function() { 120 | var f = room().forktopus; 121 | f.dir = Pos.create(1, 0); 122 | update_moveable(f); 123 | }, 124 | 'octo_appear': function() { 125 | var forktopus = Moveable.create(Tile.forktopus); 126 | forktopus.dir = Pos.create(0, 1); 127 | room().insert_moveable(Pos.create(3, 1), forktopus); 128 | process_events(); 129 | }, 130 | 'octo_disappear': function() { 131 | var r = room(); 132 | r.remove_moveable(r.forktopus); 133 | process_events(); 134 | }, 135 | 'lover_disappear': function() { 136 | room().remove_moveable(lover()); 137 | process_events(); 138 | }, 139 | 'S_appear': function() { 140 | var p = Moveable.create(Tile.player_with_spork); 141 | p.dir = Pos.create(1, 0); 142 | p.forked = 'sporked'; 143 | room().insert_moveable(Pos.create(0, 3), p); 144 | update_moveable(p); 145 | }, 146 | 'c_appear': function() { 147 | var p = Moveable.create(Tile.player); 148 | p.dir = Pos.create(1, 0); 149 | p.dying = 'not_dead'; 150 | room().insert_moveable(Pos.create(0, 3), p); 151 | update_moveable(p); 152 | }, 153 | 154 | 'face_left': function() { 155 | change_character_dir(-1, 0); 156 | }, 157 | 'face_right': function() { 158 | change_character_dir(1, 0); 159 | }, 160 | 'face_up': function() { 161 | change_character_dir(0, -1); 162 | }, 163 | 'face_down': function() { 164 | change_character_dir(0, 1); 165 | }, 166 | 167 | 'player_face_left': function() { 168 | change_player_dir(-1, 0); 169 | }, 170 | 'player_face_right': function() { 171 | change_player_dir(1, 0); 172 | }, 173 | 'player_face_up': function() { 174 | change_player_dir(0, -1); 175 | }, 176 | 'player_face_down': function() { 177 | change_player_dir(0, 1); 178 | }, 179 | 'player_flip': function() { 180 | var dir = player().dir; 181 | change_player_dir(-dir.x, -dir.y); 182 | }, 183 | 184 | 'leave': function() { 185 | if (player() && player().floor === Tile.open_door) { 186 | room().remove_moveable(player()); 187 | } else if (lover().floor === Tile.open_door) { 188 | room().remove_moveable(lover()); 189 | } 190 | 191 | process_events(); 192 | }, 193 | 'open': function() { 194 | var r = room(); 195 | r.each_door(function(pos, tile) { 196 | r.change_tile(pos, Tile.open_door); 197 | }); 198 | 199 | process_events(); 200 | }, 201 | 'close': function() { 202 | var r = room(); 203 | r.each_door(function(pos, tile) { 204 | r.change_tile(pos, Tile.closed_door); 205 | }); 206 | 207 | process_events(); 208 | }, 209 | 'hint': function() { 210 | var r = room(); 211 | r.each_tile(function(pos, tile) { 212 | if (tile === Tile.future_hint) { 213 | r.change_tile(pos, Tile.hint); 214 | process_events(); 215 | } 216 | }); 217 | }, 218 | 219 | 'player_<3': function() { 220 | player_says('heart'); 221 | }, 222 | 'ask-door': function() { 223 | player_says('door-exclam'); 224 | }, 225 | 'ask-fork': function() { 226 | player_says('fork-question'); 227 | }, 228 | 'give': function() { 229 | var r = room(); 230 | var p = player(); 231 | var l = lover(); 232 | 233 | var forked1 = p.forked; 234 | var forked2 = l.forked; 235 | p.forked = forked2; 236 | l.forked = forked1; 237 | 238 | r.update_moveable(p); 239 | r.update_moveable(l); 240 | 241 | process_events(); 242 | }, 243 | 244 | 'kiss': function() { 245 | kiss(lover(), player()); 246 | }, 247 | '<3': function() { 248 | lover_says('heart'); 249 | }, 250 | 'Z': function() { 251 | lover_says('press-z'); 252 | }, 253 | 'reminder': function() { 254 | if (!has_forked) { 255 | window.setTimeout(function() { 256 | if (!has_forked) { 257 | lover_says('press-z'); 258 | } 259 | }, 1000); 260 | } 261 | }, 262 | 'door?': function() { 263 | lover_says('door-question'); 264 | }, 265 | 'door!': function() { 266 | lover_says('door-exclam'); 267 | }, 268 | '?': function() { 269 | lover_says('question'); 270 | }, 271 | '!': function() { 272 | moveable_says(room().lover || room().forktopus, 'exclam'); 273 | }, 274 | 'R?': function() { 275 | lover_says('r-question'); 276 | }, 277 | 'fork?': function() { 278 | lover_says('fork-question'); 279 | }, 280 | 'fork!': function() { 281 | lover_says('fork-exclam'); 282 | }, 283 | 'spork?': function() { 284 | lover_says('spork-question'); 285 | }, 286 | 'spork!': function() { 287 | moveable_says(room().forktopus, 'spork-exclam'); 288 | }, 289 | 290 | 'fork': function() { 291 | use_fork(lover() || player()); 292 | }, 293 | 294 | 'skip': function() { 295 | next_level(); 296 | }, 297 | 'level_up': function() { 298 | ++level; 299 | }, 300 | 'check_trap': function() { 301 | var y = room().forktopus.pos.y; 302 | if (y == 9) { 303 | if (unlocked_puzzles) { 304 | level = World.index_of_bad_ending(); 305 | } else { 306 | level = World.index_of_long_ending(); 307 | } 308 | } else if (y < 4) { 309 | animate('trapped', World.load_on_trapped(level)); 310 | level = World.index_of_best_ending(); 311 | } else { 312 | level = World.index_of_good_ending(); 313 | } 314 | 315 | // so that skip goes to the correct level 316 | --level; 317 | }, 318 | 319 | 'pick': function() { 320 | var p = player(); 321 | 322 | p.dir = Pos.create(0, 1); 323 | p.forked = 'forked'; 324 | p.floor = Tile.blood; 325 | 326 | update_moveable(p); 327 | }, 328 | 'drop': function() { 329 | var p = player(); 330 | var dir = p.dir; 331 | var pos = player().pos.plus(dir.x, dir.y); 332 | 333 | room().change_tile(pos, Tile.dropped_fork); 334 | p.forked = null; 335 | update_moveable(p); 336 | }, 337 | 'stab': function() { 338 | var r = room(); 339 | var p = r.lover; 340 | var f = r.forktopus; 341 | 342 | var forked1 = p.forked; 343 | var forked2 = f.forked; 344 | p.forked = forked2; 345 | f.forked = forked1; 346 | 347 | r.update_moveable(p); 348 | r.update_moveable(f); 349 | 350 | f.floor = Tile.blood; 351 | process_events(); 352 | }, 353 | 'octo_dies': function() { 354 | var f = room().forktopus; 355 | for(var i=0; i<5; ++i) { 356 | foreground_animations.enqueue(function() { 357 | f.tile = Tile.empty; 358 | update_moveable(f); 359 | }).then_wait_for(100).enqueue(function() { 360 | f.tile = Tile.forktopus; 361 | update_moveable(f); 362 | }).then_wait_for(100); 363 | } 364 | foreground_animations.enqueue(function() { 365 | f.dying = 'dying'; 366 | update_moveable(f); 367 | }).then_wait_for(3000).enqueue(function() { 368 | f.floor = Tile.fork; 369 | update_moveable(f); 370 | }).then_wait_for(1000).enqueue(function() { 371 | room().remove_moveable(f); 372 | }); 373 | }, 374 | 'lover_dies': function() { 375 | var l = lover(); 376 | for(var i=0; i<5; ++i) { 377 | foreground_animations.enqueue(function() { 378 | l.tile = Tile.empty; 379 | l.forked = false; 380 | update_moveable(l); 381 | }).then_wait_for(100).enqueue(function() { 382 | l.tile = Tile.lover; 383 | l.forked = true; 384 | update_moveable(l); 385 | }).then_wait_for(100); 386 | } 387 | foreground_animations.enqueue(function() { 388 | l.dying = 'dying'; 389 | update_moveable(l); 390 | }).then_wait_for(3000).enqueue(function() { 391 | l.floor = Tile.fork; 392 | update_moveable(l); 393 | }).then_wait_for(1000).enqueue(function() { 394 | room().remove_moveable(l); 395 | }); 396 | }, 397 | 'saved': function() { 398 | var p = player(); 399 | p.say = 'heart'; 400 | update_moveable(p); 401 | }, 402 | 'saved_end': function() { 403 | var p = player(); 404 | p.say = null; 405 | update_moveable(p); 406 | }, 407 | 'both_right': function() { 408 | move_lover(1, 0); 409 | move_player(1, 0); 410 | process_events(); 411 | }, 412 | 'credits': function() { 413 | roll_credit(); 414 | }, 415 | 'show': function() { 416 | show_room(); 417 | }, 418 | 'the_end': function() { 419 | hide_room(); 420 | show_thanks(); 421 | }, 422 | 'unlock1': function() { 423 | if (unlocked_puzzles < 1) { 424 | unlocked_puzzles = 1; 425 | if (window.localStorage) 426 | { 427 | window.localStorage['unlocked_puzzles'] = unlocked_puzzles; 428 | } 429 | } 430 | show_congratulations("You have unlocked a bonus level. To unlock more, try to slow down the monster somehow."); 431 | }, 432 | 'unlock2': function() { 433 | if (unlocked_puzzles < 2) { 434 | unlocked_puzzles = 2; 435 | if (window.localStorage) 436 | { 437 | window.localStorage['unlocked_puzzles'] = unlocked_puzzles; 438 | } 439 | } 440 | show_congratulations("You have unlocked another bonus level. For the last ending, do you think you could trap the monster somehow?"); 441 | }, 442 | 'unlock3': function() { 443 | if (unlocked_puzzles < 3) { 444 | unlocked_puzzles = 3; 445 | if (window.localStorage) 446 | { 447 | window.localStorage['unlocked_puzzles'] = unlocked_puzzles; 448 | } 449 | } 450 | show_congratulations("You have unlocked the last bonus level. Good luck!"); 451 | }, 452 | 'unlock4': function() { 453 | if (unlocked_puzzles < 4) { 454 | unlocked_puzzles = 4; 455 | if (window.localStorage) 456 | { 457 | window.localStorage['unlocked_puzzles'] = unlocked_puzzles; 458 | } 459 | } 460 | show_congratulations("You really are a master of the spork."); 461 | }, 462 | 463 | 'dummy': null 464 | }; 465 | 466 | function animate(animation_key, animation_plan) { 467 | if (animation_plan.length > 0 && !completed_animations[animation_key]) { 468 | completed_animations[animation_key] = true; 469 | 470 | for(var i=0; i