jeu.js (12853B)
1 function loadSprites(fileNames, callback) { 2 var sprites = {}; 3 var loaders = []; 4 5 $.each(fileNames, function(name, src) { 6 var deferred = $.Deferred(); 7 var img = new Image(); 8 img.onload = function() { deferred.resolve(); }; 9 img.src = src; 10 sprites[name] = img; 11 loaders.push(deferred.promise()); 12 }); 13 14 $.when.apply($, loaders).done(function() { callback(sprites); }); 15 } 16 17 // Type System 18 19 var Type = { 20 // Primitive types 21 Void: function() { return { primitive: "Void" }; }, 22 Unit: function() { return { primitive: "Unit" }; }, 23 Int: function() { return { primitive: "Int" }; }, 24 Struct: function(fields) { // Type.Struct({ a: Type.Int(), b: Type.Unit(), c: ... }) 25 return { 26 primitive: "Struct", 27 fields: fields, 28 }; 29 }, 30 Function: function(parameterTypes, returnType) { // Type.Function([Type.Int(), Type.Int()], Type.Int()) 31 return { 32 primitive: "Function", 33 parameterTypes: parameterTypes, 34 returnType: returnType, 35 }; 36 }, 37 Either: function(taggedUnion) { // Type.Either({ something: Type.SomeType(...), nothing: Type.Unit() }); 38 return { 39 primitive: "Either", 40 taggedUnion: taggedUnion, 41 }; 42 }, 43 Map: function(fromName, fromType, toName, toType) { 44 return { 45 primitive: "Map", 46 fromName: fromName, 47 fromType: fromType, 48 toName: toName, 49 toType: toType, 50 }; 51 }, 52 // User-defined types 53 Maybe: function(type) { 54 return Type.Either({ 55 something: type, 56 nothing: Type.Unit() 57 }); 58 }, 59 Enum: function(symbols) { 60 var assoc = {} 61 for (var i = 0; i < symbols.length; i++) { 62 assoc[i] = Type.Unit(); 63 } 64 return Pattern.Either(assoc); 65 }, 66 Boolean: function() { 67 return Type.Enum([ 68 'true', 69 'false' 70 ]); 71 } 72 }; 73 74 var Pattern = { 75 Any: function() { return function(value) { return true; }; }, 76 Void: function() { return function(value) { return false; }; }, // Void can never have a value. 77 Predicate: function(predicate) { return predicate; }, 78 Unit: function() { return function(value) { return value.primitive == "Unit"; }; }, 79 AnyInt: function() { return function(value) { return value.primitive == "Int"; }; }, 80 Int: function(intValue) { return function(value) { return value.primitive == "Int" && value.value == intValue; }; }, 81 Struct: function(fields) { // Pattern.Struct({ a: Pattern.Int(), b: Pattern.Unit(), c: ... }) 82 return function(value) { 83 if (value.primitive != "Struct") { return false; } 84 for (f in fields) { if (!fields[f](value.fields[f])) { return false; } } 85 for (f in value.fields) { if (!fields[f]) { return false; } } // Check `value` doesn't have extra fields. 86 return true; 87 }; 88 }, 89 Function: function(parameterTypes, returnType) { // Pattern.Function([Pattern.Int(), Pattern.Int()], Pattern.Int()) 90 return function(value) { 91 if (value.primitive != "Function") { return false; } 92 if (!returnType(value.returnType)) { return false; } 93 if (parameterTypes.length != value.parameterTypes.length) { return false; } 94 for (var i = 0; i < parameterTypes; i++) { 95 if (!parameterTypes[i](value.parameterTypes[i])) { return false; } 96 } 97 return true; 98 }; 99 }, 100 Either: function(taggedUnion) { // Pattern.Either({ something: Pattern.SomeType(...), nothing: Pattern.Unit()}); 101 return function(value) { 102 if (value.primitive != "Either") { return false; } 103 if (!taggedUnion[value.tag]) { return false; } 104 return taggedUnion[value.tag](value.value); 105 }; 106 }, 107 OneOf: function(untaggedUnion) { 108 return function(value) { 109 for (i = 0; i < untaggedUnion.length; i++) { 110 if (untaggedUnion[i](value)) { 111 return true; 112 } 113 } 114 return false; 115 } 116 }, 117 // User-defined patterns 118 Maybe: function(pattern) { 119 return Pattern.OneOf([ 120 pattern, 121 Pattern.Unit() 122 ]); 123 }, 124 }; 125 126 var Value = { 127 // Void can't ever have a value. 128 Unit: function() { return { primitive: "Unit" }; }, 129 Int: function(value) { return { primitive: "Int", value: value }; }, 130 Struct: function(fields) { // Value.Struct({ a: Value.Int(42), b: Value.Unit(), c: ... }) 131 return { 132 primitive: "Struct", 133 fields: fields, 134 }; 135 }, 136 Function: function(parameterTypes, returnType, body) { // Value.Function([Type.Int(), Type.Int()], Type.Int(), body) 137 return { 138 primitive: "Function", 139 parameterTypes: parameterTypes, 140 returnType: returnType, 141 body: body, 142 }; 143 }, 144 Either: function(tag, value) { // Value.Either("something", Value.SomeType(...)); 145 return { 146 primitive: "Either", 147 tag: tag, 148 value: value, 149 }; 150 }, 151 }; 152 153 // Test 154 (function() { 155 var maybeCellType = Type.Maybe(Type.Int()); 156 var maybeCellPattern = Pattern.Maybe(Pattern.AnyInt()); 157 var cellValue = Value.Int(42); 158 var eitherCellPattern = Pattern.Either({ cell: Pattern.AnyInt(), foobar: Pattern.AnyInt() }); 159 var eitherCellValue = Value.Either("cell", cellValue); 160 if (console) { 161 console.log(true, maybeCellPattern(cellValue)); 162 console.log(false, maybeCellPattern(eitherCellValue)); 163 console.log(true, eitherCellPattern(eitherCellValue)); 164 } 165 })(); 166 167 // DONE : 168 // Type system: Types, Pattern matching and Values 169 // Grid cells with {floor: new Floor(), actor: new Actor()} 170 // where Floor has 4 "push" input/output directions, 4 input directions and 4 output directions. 171 // 172 // TODO : 173 // Type system: 174 // creating patterns from types, 175 // verifying if a value is of the given type, 176 // verifying if a pattern is matches against values of the given type. 177 // Type system: 178 // Maybe, Either and OrElse have slightly different meanings in types/patterns/values. 179 // Display types, values and patterns. 180 // Grid pattern matching: 181 // using relative up/right/down/left grid positions, and absolute coordinates 182 // Then, using the i/o paths that the floor tiles construct 183 // TODO: the i/o paths we currently have do not allow for teleports. 184 185 var GameType = {}; 186 187 GameType.Direction = Type.Enum([ 188 'up', 189 'down', 190 'left', 191 'right', 192 ]); 193 194 GameType.Position = Type.Struct({ 195 x: Type.Int(), 196 y: Type.Int(), 197 }); 198 199 GameType.FloorTile = Type.Enum([ 200 'floor', 201 'grass', 202 'hole', 203 'sand', 204 'wall', 205 'filledhole', 206 ]); 207 208 GameType.Floor = Type.Struct({ 209 tile: GameType.FloorTile, 210 push: Type.Map('in', GameType.Direction, 'out', GameType.Direction), 211 allowedIn: Type.Map('in', GameType.Direction, 'allowed', Type.Boolean()), 212 allowedOut: Type.Map('out', GameType.Direction, 'allowed', Type.Boolean()), 213 }); 214 215 GameType.TriggerTile = Type.Enum([ 216 'end', 217 ]); 218 219 GameType.ActorTile = Type.Enum([ 220 'player', 221 'block', 222 ]); 223 224 GameType.Cell = Type.Struct({ 225 floor: GameType.Floor, 226 trigger: Type.Maybe(GameType.Trigger), 227 actor: Type.Maybe(GameType.Actor), 228 }); 229 230 GamePattern = {}; 231 232 GamePattern.AnchoredCell = function(cellPattern, positionPattern) { 233 }; 234 235 GamePattern.Subgrid = function(subgrid) { // subgrid = Array(Array(Either(GamePattern.Cell, GamePattern.AnchoredCell))) 236 237 }; 238 239 function Position(x, y) { 240 this.x = x; 241 this.y = y; 242 this.offsetX = function(offsetX) { return new Position(this.x + offsetX, this.y); } 243 this.offsetY = function(offsetY) { return new Position(this.x, this.y + offsetY); } 244 this.offset = function(a, b) { 245 if (arguments.length == 1) { // Position 246 return new Position(this.x + a.x, this.y + a.y); 247 } else { // x, y 248 return new Position(this.x + a, this.y + b); 249 } 250 } 251 // this.toString = function() { return 'Position(' + x + ', ' + y + ')'; }; 252 } 253 254 function Rule(event, condition, action) { 255 this.event = event; 256 this.condition = condition; 257 this.action = action; 258 } 259 260 function Grid(width, height, initializer) { 261 for (var x = 0; x < width; x++) { 262 this[x] = []; 263 for (var y = 0; y < height; y++) { 264 this[x][y] = initializer(x, y); 265 } 266 } 267 268 this.width = width; 269 this.height = height; 270 271 this.get = function(a, b) { 272 if (b) { 273 return this[x][y]; // x, y 274 } else { 275 return this[a.x][a.y]; // Position 276 } 277 } 278 279 // toString() 280 var that = this; 281 this.toString = function() { 282 str='['; 283 for (var y = 0; y < that.height; y++) { 284 str += '['; 285 for (var x = 0; x < that.width; x++) { 286 str += '[' + that[x][y].join(',') + ']'; 287 } 288 str += '],\n'; 289 } 290 str += ']'; 291 return str; 292 }; 293 } 294 295 function Game(sprites) { 296 var game = this; 297 this.grid = new Grid(38, 25, function(x, y) { return ['h']; }); 298 this.player = new Position(0, 0); 299 this.events = { 300 // TODO : make rules instead. 301 left: function() { game.player.x--; }, 302 up: function() { game.player.y--; }, 303 right: function() { 304 var current = game.grid.get(game.player); 305 var next1 = game.grid.get(game.player.offsetX(1)); 306 var next2 = game.grid.get(game.player.offsetX(2)); 307 for (var r = 0; r < game.rules.length; r++) { 308 if (game.rules[r].condition(game, current, next1, next2)) { 309 console.log("rule", r); 310 game.rules[r].action(game, current, next1, next2); 311 break; 312 } 313 } 314 }, 315 down: function() { game.player.y++; }, 316 } 317 this.rules = [ 318 // TODO : find a non-awkward way to express rules. 319 new Rule('moveToEnd', function(game, current, next1, next2) { 320 console.log(next1[1]); 321 return (current[1] == 'p') // [?,p] 322 && (next1 != null) && (next1[1] == 'e'); // [e,?] 323 }, function(game, current, next1, next2) { 324 //game.player.position = next1.position; 325 var p = current[1]; 326 var e = next1[1]; 327 current.splice(1, 1);// delete starting at 1 a single (1) element; 328 next1[1] = p; 329 game.player = game.player.offsetX(1); // HACK! 330 alert("you win!"); 331 }), 332 333 new Rule('pushInHole', function(game, current, next1, next2) { 334 return (current[1] == 'p') // [?,p] 335 && (next1 != null) && (next1[1] == 'b') // [?,b] 336 && (next2 != null) && (next2[0] == 'h'); // [h,?] 337 }, function(game, current, next1, next2) { 338 //game.player.position = next1.position; 339 var p = current[1]; 340 var b = next1[1]; 341 current.splice(1, 1);// delete starting at 1 a single (1) element; 342 next1[1] = p; 343 next2[0] = b; 344 game.player = game.player.offsetX(1); // HACK! 345 }), 346 347 new Rule('push', function(game, current, next1, next2) { 348 return (current[1] == 'p') // [?,p] 349 && (next1 != null) && (next1[1] == 'b') // [?,b] 350 && (next2 != null) && (next2[1] === undefined); // [?,undefined] 351 }, function(game, current, next1, next2) { 352 //game.player.position = next1.position; 353 var p = current[1]; 354 var b = next1[1]; 355 current.splice(1, 1);// delete starting at 1 a single (1) element; 356 next1[1] = p; 357 next2[1] = b; 358 game.player = game.player.offsetX(1); // HACK! 359 }), 360 361 new Rule('move', function(game, current, next1, next2) { 362 return (current[1] == 'p') // [?,p] 363 && (next1 != null) && (next1[1] === undefined); // [?,undefined] 364 }, function(game, current, next1, next2) { 365 //game.player.position = next1.position; 366 var p = current[1]; 367 current.splice(1, 1);// delete starting at 1 a single (1) element; 368 next1[1] = p; 369 game.player = game.player.offsetX(1); // HACK! 370 }), 371 ]; 372 this.event = function(name) { 373 this.events[name](this); 374 } 375 } 376 377 $(function() { 378 var canvas = document.getElementById('canvas'); 379 var c = canvas.getContext('2d'); 380 c.fillStyle = 'black'; 381 c.fillRect(0,0,16,16); 382 383 level = [ 384 "hhhhhhhhhhwwwww", 385 "wwwwwwwwwwwfffw", 386 "wpfbfhffffffefw", 387 "wwwwwwwwwwwfffw", 388 "hhhhhhhhhhwwwww", 389 "fghsweb"// + 'p' 390 ]; 391 392 loadSprites({ 393 p: "img/player.png", 394 f: "img/floor.png", 395 g: "img/grass.png", 396 h: "img/hole.png", 397 s: "img/sand.png", 398 w: "img/wall.png", 399 b: "img/block.png", 400 e: "img/end.png", 401 }, function(sprites) { 402 var game = new Game(sprites); 403 window.game = game; // For debugging purposes. 404 405 // TODO : remove this and provide a GUI to manage cell's contents. 406 for (var y = 0; y < level.length; y++) { 407 for (var x = 0; x < level[y].length; x++) { 408 if (level[y][x] == 'p') { 409 game.player = new Position(x, y); 410 } 411 412 if ('peb'.indexOf(level[y][x]) != -1) { 413 game.grid[x][y] = [ 'f', level[y][x], ]; 414 } else { 415 game.grid[x][y] = [ level[y][x], ]; 416 } 417 } 418 } 419 420 game.redraw = function() { 421 for (var x = 0; x < game.grid.width; x++) { 422 for (var y = 0; y < game.grid.height; y++) { 423 c.fillStyle = 'black'; 424 c.fillRect(x*16, y*16, (x+1)*16, (y+1)*16); 425 var cell = game.grid[x][y]; 426 for (o = 0; o < cell.length; o++) { 427 c.drawImage(sprites[cell[o]], x*16, y*16); 428 } 429 } 430 } 431 }; 432 433 game.redraw(); 434 435 // Keyboard presses 436 $("body").keydown(function(e) { 437 switch(e.which) { 438 case 37: // left 439 game.event('left'); 440 break; 441 case 38: // up 442 game.event('up'); 443 break; 444 case 39: // right 445 game.event('right'); 446 break; 447 case 40: // down 448 game.event('down'); 449 break; 450 } 451 game.redraw(); 452 }); 453 }); 454 }); 455 456 // Concepts: 457 // Actor ? 458 // Object(s) in grid cell (= grid cell "state") 459 // Sprite (grid cell + neighborhood are used to choose a cell, for example draw a "border" when the sprite's neighbor's state is different). 460 461 // Concepts2: 462 // Grid (2d-array of stuff), later: grid+paths/zones along which objects can move. Paths are drawn between the centers of two grid cells. 463 // Tile (image) 464 // Rule (includes a pattern, and a substitute(?))