commit ba20bac6b888b55ac036285852f7b5e5a4e9550d Author: schrom01 Date: Thu Nov 17 09:58:26 2022 +0100 initial commit diff --git a/connect4.html b/connect4.html new file mode 100644 index 0000000..71d50cd --- /dev/null +++ b/connect4.html @@ -0,0 +1,131 @@ + + + + + Vier gewinnt + + + + + + + + + +
+ + + + + + + diff --git a/game-canvas/code/chapter/16_game.js b/game-canvas/code/chapter/16_game.js new file mode 100644 index 0000000..8b0a2d7 --- /dev/null +++ b/game-canvas/code/chapter/16_game.js @@ -0,0 +1,359 @@ +var simpleLevelPlan = ` +...................... +..#................#.. +..#..............=.#.. +..#.........o.o....#.. +..#.@......#####...#.. +..#####............#.. +......#++++++++++++#.. +......##############.. +......................`; + +var Level = class Level { + constructor(plan) { + let rows = plan.trim().split("\n").map(l => [...l]); + this.height = rows.length; + this.width = rows[0].length; + this.startActors = []; + + this.rows = rows.map((row, y) => { + return row.map((ch, x) => { + let type = levelChars[ch]; + if (typeof type == "string") return type; + this.startActors.push( + type.create(new Vec(x, y), ch)); + return "empty"; + }); + }); + } +} + +var State = class State { + constructor(level, actors, status) { + this.level = level; + this.actors = actors; + this.status = status; + } + + static start(level) { + return new State(level, level.startActors, "playing"); + } + + get player() { + return this.actors.find(a => a.type == "player"); + } +} + +var Vec = class Vec { + constructor(x, y) { + this.x = x; this.y = y; + } + plus(other) { + return new Vec(this.x + other.x, this.y + other.y); + } + times(factor) { + return new Vec(this.x * factor, this.y * factor); + } +} + +var Player = class Player { + constructor(pos, speed) { + this.pos = pos; + this.speed = speed; + } + + get type() { return "player"; } + + static create(pos) { + return new Player(pos.plus(new Vec(0, -0.5)), + new Vec(0, 0)); + } +} + +Player.prototype.size = new Vec(0.8, 1.5); + +var Lava = class Lava { + constructor(pos, speed, reset) { + this.pos = pos; + this.speed = speed; + this.reset = reset; + } + + get type() { return "lava"; } + + static create(pos, ch) { + if (ch == "=") { + return new Lava(pos, new Vec(2, 0)); + } else if (ch == "|") { + return new Lava(pos, new Vec(0, 2)); + } else if (ch == "v") { + return new Lava(pos, new Vec(0, 3), pos); + } + } +} + +Lava.prototype.size = new Vec(1, 1); + +var Coin = class Coin { + constructor(pos, basePos, wobble) { + this.pos = pos; + this.basePos = basePos; + this.wobble = wobble; + } + + get type() { return "coin"; } + + static create(pos) { + let basePos = pos.plus(new Vec(0.2, 0.1)); + return new Coin(basePos, basePos, + Math.random() * Math.PI * 2); + } +} + +Coin.prototype.size = new Vec(0.6, 0.6); + +var levelChars = { + ".": "empty", "#": "wall", "+": "lava", + "@": Player, "o": Coin, + "=": Lava, "|": Lava, "v": Lava +}; + +var simpleLevel = new Level(simpleLevelPlan); + +function elt(name, attrs, ...children) { + let dom = document.createElement(name); + for (let attr of Object.keys(attrs)) { + dom.setAttribute(attr, attrs[attr]); + } + for (let child of children) { + dom.appendChild(child); + } + return dom; +} + +var DOMDisplay = class DOMDisplay { + constructor(parent, level) { + this.dom = elt("div", {class: "game"}, drawGrid(level)); + this.actorLayer = null; + parent.appendChild(this.dom); + } + + clear() { this.dom.remove(); } +} + +var scale = 20; + +function drawGrid(level) { + return elt("table", { + class: "background", + style: `width: ${level.width * scale}px` + }, ...level.rows.map(row => + elt("tr", {style: `height: ${scale}px`}, + ...row.map(type => elt("td", {class: type}))) + )); +} + +function drawActors(actors) { + return elt("div", {}, ...actors.map(actor => { + let rect = elt("div", {class: `actor ${actor.type}`}); + rect.style.width = `${actor.size.x * scale}px`; + rect.style.height = `${actor.size.y * scale}px`; + rect.style.left = `${actor.pos.x * scale}px`; + rect.style.top = `${actor.pos.y * scale}px`; + return rect; + })); +} + +DOMDisplay.prototype.syncState = function(state) { + if (this.actorLayer) this.actorLayer.remove(); + this.actorLayer = drawActors(state.actors); + this.dom.appendChild(this.actorLayer); + this.dom.className = `game ${state.status}`; + this.scrollPlayerIntoView(state); +}; + +DOMDisplay.prototype.scrollPlayerIntoView = function(state) { + let width = this.dom.clientWidth; + let height = this.dom.clientHeight; + let margin = width / 3; + + // The viewport + let left = this.dom.scrollLeft, right = left + width; + let top = this.dom.scrollTop, bottom = top + height; + + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)) + .times(scale); + + if (center.x < left + margin) { + this.dom.scrollLeft = center.x - margin; + } else if (center.x > right - margin) { + this.dom.scrollLeft = center.x + margin - width; + } + if (center.y < top + margin) { + this.dom.scrollTop = center.y - margin; + } else if (center.y > bottom - margin) { + this.dom.scrollTop = center.y + margin - height; + } +}; + +Level.prototype.touches = function(pos, size, type) { + var xStart = Math.floor(pos.x); + var xEnd = Math.ceil(pos.x + size.x); + var yStart = Math.floor(pos.y); + var yEnd = Math.ceil(pos.y + size.y); + + for (var y = yStart; y < yEnd; y++) { + for (var x = xStart; x < xEnd; x++) { + let isOutside = x < 0 || x >= this.width || + y < 0 || y >= this.height; + let here = isOutside ? "wall" : this.rows[y][x]; + if (here == type) return true; + } + } + return false; +}; + +State.prototype.update = function(time, keys) { + let actors = this.actors + .map(actor => actor.update(time, this, keys)); + let newState = new State(this.level, actors, this.status); + + if (newState.status != "playing") return newState; + + let player = newState.player; + if (this.level.touches(player.pos, player.size, "lava")) { + return new State(this.level, actors, "lost"); + } + + for (let actor of actors) { + if (actor != player && overlap(actor, player)) { + newState = actor.collide(newState); + } + } + return newState; +}; + +function overlap(actor1, actor2) { + return actor1.pos.x + actor1.size.x > actor2.pos.x && + actor1.pos.x < actor2.pos.x + actor2.size.x && + actor1.pos.y + actor1.size.y > actor2.pos.y && + actor1.pos.y < actor2.pos.y + actor2.size.y; +} + +Lava.prototype.collide = function(state) { + return new State(state.level, state.actors, "lost"); +}; + +Coin.prototype.collide = function(state) { + let filtered = state.actors.filter(a => a != this); + let status = state.status; + if (!filtered.some(a => a.type == "coin")) status = "won"; + return new State(state.level, filtered, status); +}; + +Lava.prototype.update = function(time, state) { + let newPos = this.pos.plus(this.speed.times(time)); + if (!state.level.touches(newPos, this.size, "wall")) { + return new Lava(newPos, this.speed, this.reset); + } else if (this.reset) { + return new Lava(this.reset, this.speed, this.reset); + } else { + return new Lava(this.pos, this.speed.times(-1)); + } +}; + +var wobbleSpeed = 8, wobbleDist = 0.07; + +Coin.prototype.update = function(time) { + let wobble = this.wobble + time * wobbleSpeed; + let wobblePos = Math.sin(wobble) * wobbleDist; + return new Coin(this.basePos.plus(new Vec(0, wobblePos)), + this.basePos, wobble); +}; + +var playerXSpeed = 7; +var gravity = 30; +var jumpSpeed = 17; + +Player.prototype.update = function(time, state, keys) { + let xSpeed = 0; + if (keys.ArrowLeft) xSpeed -= playerXSpeed; + if (keys.ArrowRight) xSpeed += playerXSpeed; + let pos = this.pos; + let movedX = pos.plus(new Vec(xSpeed * time, 0)); + if (!state.level.touches(movedX, this.size, "wall")) { + pos = movedX; + } + + let ySpeed = this.speed.y + time * gravity; + let movedY = pos.plus(new Vec(0, ySpeed * time)); + if (!state.level.touches(movedY, this.size, "wall")) { + pos = movedY; + } else if (keys.ArrowUp && ySpeed > 0) { + ySpeed = -jumpSpeed; + } else { + ySpeed = 0; + } + return new Player(pos, new Vec(xSpeed, ySpeed)); +}; + +function trackKeys(keys) { + let down = Object.create(null); + function track(event) { + if (keys.includes(event.key)) { + down[event.key] = event.type == "keydown"; + event.preventDefault(); + } + } + window.addEventListener("keydown", track); + window.addEventListener("keyup", track); + return down; +} + +var arrowKeys = + trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]); + +function runAnimation(frameFunc) { + let lastTime = null; + function frame(time) { + if (lastTime != null) { + let timeStep = Math.min(time - lastTime, 100) / 1000; + if (frameFunc(timeStep) === false) return; + } + lastTime = time; + requestAnimationFrame(frame); + } + requestAnimationFrame(frame); +} + +function runLevel(level, Display) { + let display = new Display(document.body, level); + let state = State.start(level); + let ending = 1; + return new Promise(resolve => { + runAnimation(time => { + state = state.update(time, arrowKeys); + display.syncState(state); + if (state.status == "playing") { + return true; + } else if (ending > 0) { + ending -= time; + return true; + } else { + display.clear(); + resolve(state.status); + return false; + } + }); + }); +} + +async function runGame(plans, Display) { + for (let level = 0; level < plans.length;) { + let status = await runLevel(new Level(plans[level]), + Display); + if (status == "won") level++; + } + console.log("You've won!"); +} diff --git a/game-canvas/code/chapter/17_canvas.js b/game-canvas/code/chapter/17_canvas.js new file mode 100644 index 0000000..dd6ee70 --- /dev/null +++ b/game-canvas/code/chapter/17_canvas.js @@ -0,0 +1,143 @@ +var results = [ + {name: "Satisfied", count: 1043, color: "lightblue"}, + {name: "Neutral", count: 563, color: "lightgreen"}, + {name: "Unsatisfied", count: 510, color: "pink"}, + {name: "No comment", count: 175, color: "silver"} +]; + +function flipHorizontally(context, around) { + context.translate(around, 0); + context.scale(-1, 1); + context.translate(-around, 0); +} + +var CanvasDisplay = class CanvasDisplay { + constructor(parent, level) { + this.canvas = document.createElement("canvas"); + this.canvas.width = Math.min(600, level.width * scale); + this.canvas.height = Math.min(450, level.height * scale); + parent.appendChild(this.canvas); + this.cx = this.canvas.getContext("2d"); + + this.flipPlayer = false; + + this.viewport = { + left: 0, + top: 0, + width: this.canvas.width / scale, + height: this.canvas.height / scale + }; + } + + clear() { + this.canvas.remove(); + } +} + +CanvasDisplay.prototype.syncState = function(state) { + this.updateViewport(state); + this.clearDisplay(state.status); + this.drawBackground(state.level); + this.drawActors(state.actors); +}; + +CanvasDisplay.prototype.updateViewport = function(state) { + let view = this.viewport, margin = view.width / 3; + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)); + + if (center.x < view.left + margin) { + view.left = Math.max(center.x - margin, 0); + } else if (center.x > view.left + view.width - margin) { + view.left = Math.min(center.x + margin - view.width, + state.level.width - view.width); + } + if (center.y < view.top + margin) { + view.top = Math.max(center.y - margin, 0); + } else if (center.y > view.top + view.height - margin) { + view.top = Math.min(center.y + margin - view.height, + state.level.height - view.height); + } +}; + +CanvasDisplay.prototype.clearDisplay = function(status) { + if (status == "won") { + this.cx.fillStyle = "rgb(68, 191, 255)"; + } else if (status == "lost") { + this.cx.fillStyle = "rgb(44, 136, 214)"; + } else { + this.cx.fillStyle = "rgb(52, 166, 251)"; + } + this.cx.fillRect(0, 0, + this.canvas.width, this.canvas.height); +}; + +var otherSprites = document.createElement("img"); +otherSprites.src = "img/sprites.png"; + +CanvasDisplay.prototype.drawBackground = function(level) { + let {left, top, width, height} = this.viewport; + let xStart = Math.floor(left); + let xEnd = Math.ceil(left + width); + let yStart = Math.floor(top); + let yEnd = Math.ceil(top + height); + + for (let y = yStart; y < yEnd; y++) { + for (let x = xStart; x < xEnd; x++) { + let tile = level.rows[y][x]; + if (tile == "empty") continue; + let screenX = (x - left) * scale; + let screenY = (y - top) * scale; + let tileX = tile == "lava" ? scale : 0; + this.cx.drawImage(otherSprites, + tileX, 0, scale, scale, + screenX, screenY, scale, scale); + } + } +}; + +var playerSprites = document.createElement("img"); +playerSprites.src = "img/player.png"; +var playerXOverlap = 4; + +CanvasDisplay.prototype.drawPlayer = function(player, x, y, + width, height){ + width += playerXOverlap * 2; + x -= playerXOverlap; + if (player.speed.x != 0) { + this.flipPlayer = player.speed.x < 0; + } + + let tile = 8; + if (player.speed.y != 0) { + tile = 9; + } else if (player.speed.x != 0) { + tile = Math.floor(Date.now() / 60) % 8; + } + + this.cx.save(); + if (this.flipPlayer) { + flipHorizontally(this.cx, x + width / 2); + } + let tileX = tile * width; + this.cx.drawImage(playerSprites, tileX, 0, width, height, + x, y, width, height); + this.cx.restore(); +}; + +CanvasDisplay.prototype.drawActors = function(actors) { + for (let actor of actors) { + let width = actor.size.x * scale; + let height = actor.size.y * scale; + let x = (actor.pos.x - this.viewport.left) * scale; + let y = (actor.pos.y - this.viewport.top) * scale; + if (actor.type == "player") { + this.drawPlayer(actor, x, y, width, height); + } else { + let tileX = (actor.type == "coin" ? 2 : 1) * scale; + this.cx.drawImage(otherSprites, + tileX, 0, width, height, + x, y, width, height); + } + } +}; diff --git a/game-canvas/code/levels.js b/game-canvas/code/levels.js new file mode 100644 index 0000000..9ff491f --- /dev/null +++ b/game-canvas/code/levels.js @@ -0,0 +1,178 @@ +var GAME_LEVELS = [` +................................................................................ +................................................................................ +................................................................................ +................................................................................ +................................................................................ +................................................................................ +..................................................................###........... +...................................................##......##....##+##.......... +....................................o.o......##..................#+++#.......... +.................................................................##+##.......... +...................................#####..........................#v#........... +............................................................................##.. +..##......................................o.o................................#.. +..#.....................o....................................................#.. +..#......................................#####.............................o.#.. +..#..........####.......o....................................................#.. +..#..@.......#..#................................................#####.......#.. +..############..###############...####################.....#######...#########.. +..............................#...#..................#.....#.................... +..............................#+++#..................#+++++#.................... +..............................#+++#..................#+++++#.................... +..............................#####..................#######.................... +................................................................................ +................................................................................ +`,` +................................................................................ +................................................................................ +....###############################............................................. +...##.............................##########################################.... +...#.......................................................................##... +...#....o...................................................................#... +...#................................................=.......................#... +...#.o........################...................o..o...........|........o..#... +...#.........................#..............................................#... +...#....o....................##########.....###################....##########... +...#..................................#+++++#.................#....#............ +...###############....oo......=o.o.o..#######.###############.#....#............ +.....#...............o..o.............#.......#......#........#....#............ +.....#....................#############..######.####.#.########....########..... +.....#.............########..............#...........#.#..................#..... +.....#..........####......####...#####################.#..................#..... +.....#........###............###.......................########....########..... +.....#.......##................#########################......#....#............ +.....#.......#................................................#....#............ +.....###......................................................#....#............ +.......#...............o...........................................#............ +.......#...............................................o...........#............ +.......#########......###.....############.........................##........... +.............#..................#........#####....#######.o.........########.... +.............#++++++++++++++++++#............#....#.....#..................#.... +.............#++++++++++++++++++#..........###....###...####.o.............#.... +.............####################..........#........#......#.....|.........#.... +...........................................#++++++++#......####............#.... +...........................................#++++++++#.........#........@...#.... +...........................................#++++++++#.........##############.... +...........................................##########........................... +................................................................................ +`,` +......................................#++#........................#######....................................#+#.. +......................................#++#.....................####.....####.................................#+#.. +......................................#++##########...........##...........##................................#+#.. +......................................##++++++++++##.........##.............##...............................#+#.. +.......................................##########++#.........#....................................o...o...o..#+#.. +................................................##+#.........#.....o...o....................................##+#.. +.................................................#+#.........#................................###############++#.. +.................................................#v#.........#.....#...#........................++++++++++++++##.. +.............................................................##..|...|...|..##............#####################... +..............................................................##+++++++++++##............v........................ +...............................................................####+++++####...................................... +...............................................#.....#............#######........###.........###.................. +...............................................#.....#...........................#.#.........#.#.................. +...............................................#.....#.............................#.........#.................... +...............................................#.....#.............................##........#.................... +...............................................##....#.............................#.........#.................... +...............................................#.....#......o..o.....#...#.........#.........#.................... +...............#######........###...###........#.....#...............#...#.........#.........#.................... +..............##.....##.........#...#..........#.....#.....######....#...#...#########.......#.................... +.............##.......##........#.o.#..........#....##...............#...#...#...............#.................... +.....@.......#.........#........#...#..........#.....#...............#...#...#...............#.................... +....###......#.........#........#...#..........#.....#...............#...#####...######......#.................... +....#.#......#.........#.......##.o.##.........#.....#...............#.....o.....#.#.........#.................... +++++#.#++++++#.........#++++++##.....##++++++++##....#++++++++++.....#.....=.....#.#.........#.................... +++++#.#++++++#.........#+++++##.......##########.....#+++++++##+.....#############.##..o.o..##.................... +++++#.#++++++#.........#+++++#....o.................##++++++##.+....................##.....##..................... +++++#.#++++++#.........#+++++#.....................##++++++##..+.....................#######...................... +++++#.#++++++#.........#+++++##.......##############++++++##...+.................................................. +++++#.#++++++#.........#++++++#########++++++++++++++++++##....+.................................................. +++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....+.................................................. +`,` +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +........................................o..................................................................... +.............................................................................................................. +........................................#..................................................................... +........................................#..................................................................... +........................................#..................................................................... +........................................#..................................................................... +.......................................###.................................................................... +.......................................#.#.................+++........+++..###................................ +.......................................#.#.................+#+........+#+..................................... +.....................................###.###................#..........#...................................... +......................................#...#.................#...oooo...#.......###............................ +......................................#...#.................#..........#......#+++#........................... +......................................#...#.................############.......###............................ +.....................................##...##......#...#......#................................................ +......................................#...#########...########..............#.#............................... +......................................#...#...........#....................#+++#.............................. +......................................#...#...........#.....................###............................... +.....................................##...##..........#....................................................... +......................................#...#=.=.=.=....#............###........................................ +......................................#...#...........#...........#+++#....................................... +......................................#...#....=.=.=.=#.....o......###.......###.............................. +.....................................##...##..........#.....................#+++#............................. +..............................o...o...#...#...........#.....#................##v........###................... +......................................#...#...........#..............#.................#+++#.................. +.............................###.###.###.###.....o.o..#++++++++++++++#...................v#................... +.............................#.###.#.#.###.#..........#++++++++++++++#........................................ +.............................#.............#...#######################........................................ +.............................##...........##.........................................###...................... +..###.........................#.....#.....#.........................................#+++#................###.. +..#.#.........................#....###....#..........................................###.................#.#.. +..#...........................#....###....#######........................#####.............................#.. +..#...........................#...........#..............................#...#.............................#.. +..#...........................##..........#..............................#.#.#.............................#.. +..#.......................................#.......|####|....|####|.....###.###.............................#.. +..#................###.............o.o....#..............................#.........###.....................#.. +..#...............#####.......##..........#.............................###.......#+++#..........#.........#.. +..#...............o###o.......#....###....#.............................#.#........###..........###........#.. +..#................###........#############..#.oo.#....#.oo.#....#.oo..##.##....................###........#.. +..#......@..........#.........#...........#++#....#++++#....#++++#....##...##....................#.........#.. +..#############################...........#############################.....################################.. +.............................................................................................................. +.............................................................................................................. +`,` +..................................................................................................###.#....... +......................................................................................................#....... +..................................................................................................#####....... +..................................................................................................#........... +..................................................................................................#.###....... +..........................o.......................................................................#.#.#....... +.............................................................................................o.o.o###.#....... +...................###................................................................................#....... +.......+..o..+................................................#####.#####.#####.#####.#####.#####.#####....... +.......#.....#................................................#...#.#...#.#...#.#...#.#...#.#...#.#........... +.......#=.o..#............#...................................###.#.###.#.###.#.###.#.###.#.###.#.#####....... +.......#.....#..................................................#.#...#.#...#.#...#.#...#.#...#.#.....#....... +.......+..o..+............o..................................####.#####.#####.#####.#####.#####.#######....... +.............................................................................................................. +..........o..............###..............................##.................................................. +.............................................................................................................. +.............................................................................................................. +......................................................##...................................................... +...................###.........###............................................................................ +.............................................................................................................. +..........................o.....................................................#......#...................... +..........................................................##.....##........................................... +.............###.........###.........###.................................#..................#................. +.............................................................................................................. +.................................................................||........................................... +..###########................................................................................................. +..#.........#.o.#########.o.#########.o.##................................................#................... +..#.........#...#.......#...#.......#...#.................||..................#.....#......................... +..#..@......#####...o...#####...o...#####..................................................................... +..#######.....................................#####.......##.....##.....###................................... +........#=..................=................=#...#.....................###................................... +........#######################################...#+++++++++++++++++++++###+++++++++++++++++++++++++++++++++++ +..................................................############################################################ +.............................................................................................................. +`]; + +if (typeof module != "undefined" && module.exports && (typeof window == "undefined" || window.exports != exports)) + module.exports = GAME_LEVELS; +if (typeof global != "undefined" && !global.GAME_LEVELS) + global.GAME_LEVELS = GAME_LEVELS; diff --git a/game-canvas/img/player.png b/game-canvas/img/player.png new file mode 100644 index 0000000..1244769 Binary files /dev/null and b/game-canvas/img/player.png differ diff --git a/game-canvas/img/sprites.png b/game-canvas/img/sprites.png new file mode 100644 index 0000000..2a77ab9 Binary files /dev/null and b/game-canvas/img/sprites.png differ diff --git a/game-canvas/index.html b/game-canvas/index.html new file mode 100644 index 0000000..fd24e8c --- /dev/null +++ b/game-canvas/index.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/game-canvas/readme.txt b/game-canvas/readme.txt new file mode 100644 index 0000000..d436230 --- /dev/null +++ b/game-canvas/readme.txt @@ -0,0 +1,15 @@ +This example is part of: + +Marijn Haverbeke +Eloquent JavaScript +3rd edition (2018) + +https://eloquentjavascript.net + + +Chapter 16: Project: A Platform Game +https://eloquentjavascript.net/16_game.html + +Chapter 17: Drawing on Canvas +https://eloquentjavascript.net/17_canvas.html + diff --git a/game-dom/code/chapter/16_game.js b/game-dom/code/chapter/16_game.js new file mode 100644 index 0000000..8b0a2d7 --- /dev/null +++ b/game-dom/code/chapter/16_game.js @@ -0,0 +1,359 @@ +var simpleLevelPlan = ` +...................... +..#................#.. +..#..............=.#.. +..#.........o.o....#.. +..#.@......#####...#.. +..#####............#.. +......#++++++++++++#.. +......##############.. +......................`; + +var Level = class Level { + constructor(plan) { + let rows = plan.trim().split("\n").map(l => [...l]); + this.height = rows.length; + this.width = rows[0].length; + this.startActors = []; + + this.rows = rows.map((row, y) => { + return row.map((ch, x) => { + let type = levelChars[ch]; + if (typeof type == "string") return type; + this.startActors.push( + type.create(new Vec(x, y), ch)); + return "empty"; + }); + }); + } +} + +var State = class State { + constructor(level, actors, status) { + this.level = level; + this.actors = actors; + this.status = status; + } + + static start(level) { + return new State(level, level.startActors, "playing"); + } + + get player() { + return this.actors.find(a => a.type == "player"); + } +} + +var Vec = class Vec { + constructor(x, y) { + this.x = x; this.y = y; + } + plus(other) { + return new Vec(this.x + other.x, this.y + other.y); + } + times(factor) { + return new Vec(this.x * factor, this.y * factor); + } +} + +var Player = class Player { + constructor(pos, speed) { + this.pos = pos; + this.speed = speed; + } + + get type() { return "player"; } + + static create(pos) { + return new Player(pos.plus(new Vec(0, -0.5)), + new Vec(0, 0)); + } +} + +Player.prototype.size = new Vec(0.8, 1.5); + +var Lava = class Lava { + constructor(pos, speed, reset) { + this.pos = pos; + this.speed = speed; + this.reset = reset; + } + + get type() { return "lava"; } + + static create(pos, ch) { + if (ch == "=") { + return new Lava(pos, new Vec(2, 0)); + } else if (ch == "|") { + return new Lava(pos, new Vec(0, 2)); + } else if (ch == "v") { + return new Lava(pos, new Vec(0, 3), pos); + } + } +} + +Lava.prototype.size = new Vec(1, 1); + +var Coin = class Coin { + constructor(pos, basePos, wobble) { + this.pos = pos; + this.basePos = basePos; + this.wobble = wobble; + } + + get type() { return "coin"; } + + static create(pos) { + let basePos = pos.plus(new Vec(0.2, 0.1)); + return new Coin(basePos, basePos, + Math.random() * Math.PI * 2); + } +} + +Coin.prototype.size = new Vec(0.6, 0.6); + +var levelChars = { + ".": "empty", "#": "wall", "+": "lava", + "@": Player, "o": Coin, + "=": Lava, "|": Lava, "v": Lava +}; + +var simpleLevel = new Level(simpleLevelPlan); + +function elt(name, attrs, ...children) { + let dom = document.createElement(name); + for (let attr of Object.keys(attrs)) { + dom.setAttribute(attr, attrs[attr]); + } + for (let child of children) { + dom.appendChild(child); + } + return dom; +} + +var DOMDisplay = class DOMDisplay { + constructor(parent, level) { + this.dom = elt("div", {class: "game"}, drawGrid(level)); + this.actorLayer = null; + parent.appendChild(this.dom); + } + + clear() { this.dom.remove(); } +} + +var scale = 20; + +function drawGrid(level) { + return elt("table", { + class: "background", + style: `width: ${level.width * scale}px` + }, ...level.rows.map(row => + elt("tr", {style: `height: ${scale}px`}, + ...row.map(type => elt("td", {class: type}))) + )); +} + +function drawActors(actors) { + return elt("div", {}, ...actors.map(actor => { + let rect = elt("div", {class: `actor ${actor.type}`}); + rect.style.width = `${actor.size.x * scale}px`; + rect.style.height = `${actor.size.y * scale}px`; + rect.style.left = `${actor.pos.x * scale}px`; + rect.style.top = `${actor.pos.y * scale}px`; + return rect; + })); +} + +DOMDisplay.prototype.syncState = function(state) { + if (this.actorLayer) this.actorLayer.remove(); + this.actorLayer = drawActors(state.actors); + this.dom.appendChild(this.actorLayer); + this.dom.className = `game ${state.status}`; + this.scrollPlayerIntoView(state); +}; + +DOMDisplay.prototype.scrollPlayerIntoView = function(state) { + let width = this.dom.clientWidth; + let height = this.dom.clientHeight; + let margin = width / 3; + + // The viewport + let left = this.dom.scrollLeft, right = left + width; + let top = this.dom.scrollTop, bottom = top + height; + + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)) + .times(scale); + + if (center.x < left + margin) { + this.dom.scrollLeft = center.x - margin; + } else if (center.x > right - margin) { + this.dom.scrollLeft = center.x + margin - width; + } + if (center.y < top + margin) { + this.dom.scrollTop = center.y - margin; + } else if (center.y > bottom - margin) { + this.dom.scrollTop = center.y + margin - height; + } +}; + +Level.prototype.touches = function(pos, size, type) { + var xStart = Math.floor(pos.x); + var xEnd = Math.ceil(pos.x + size.x); + var yStart = Math.floor(pos.y); + var yEnd = Math.ceil(pos.y + size.y); + + for (var y = yStart; y < yEnd; y++) { + for (var x = xStart; x < xEnd; x++) { + let isOutside = x < 0 || x >= this.width || + y < 0 || y >= this.height; + let here = isOutside ? "wall" : this.rows[y][x]; + if (here == type) return true; + } + } + return false; +}; + +State.prototype.update = function(time, keys) { + let actors = this.actors + .map(actor => actor.update(time, this, keys)); + let newState = new State(this.level, actors, this.status); + + if (newState.status != "playing") return newState; + + let player = newState.player; + if (this.level.touches(player.pos, player.size, "lava")) { + return new State(this.level, actors, "lost"); + } + + for (let actor of actors) { + if (actor != player && overlap(actor, player)) { + newState = actor.collide(newState); + } + } + return newState; +}; + +function overlap(actor1, actor2) { + return actor1.pos.x + actor1.size.x > actor2.pos.x && + actor1.pos.x < actor2.pos.x + actor2.size.x && + actor1.pos.y + actor1.size.y > actor2.pos.y && + actor1.pos.y < actor2.pos.y + actor2.size.y; +} + +Lava.prototype.collide = function(state) { + return new State(state.level, state.actors, "lost"); +}; + +Coin.prototype.collide = function(state) { + let filtered = state.actors.filter(a => a != this); + let status = state.status; + if (!filtered.some(a => a.type == "coin")) status = "won"; + return new State(state.level, filtered, status); +}; + +Lava.prototype.update = function(time, state) { + let newPos = this.pos.plus(this.speed.times(time)); + if (!state.level.touches(newPos, this.size, "wall")) { + return new Lava(newPos, this.speed, this.reset); + } else if (this.reset) { + return new Lava(this.reset, this.speed, this.reset); + } else { + return new Lava(this.pos, this.speed.times(-1)); + } +}; + +var wobbleSpeed = 8, wobbleDist = 0.07; + +Coin.prototype.update = function(time) { + let wobble = this.wobble + time * wobbleSpeed; + let wobblePos = Math.sin(wobble) * wobbleDist; + return new Coin(this.basePos.plus(new Vec(0, wobblePos)), + this.basePos, wobble); +}; + +var playerXSpeed = 7; +var gravity = 30; +var jumpSpeed = 17; + +Player.prototype.update = function(time, state, keys) { + let xSpeed = 0; + if (keys.ArrowLeft) xSpeed -= playerXSpeed; + if (keys.ArrowRight) xSpeed += playerXSpeed; + let pos = this.pos; + let movedX = pos.plus(new Vec(xSpeed * time, 0)); + if (!state.level.touches(movedX, this.size, "wall")) { + pos = movedX; + } + + let ySpeed = this.speed.y + time * gravity; + let movedY = pos.plus(new Vec(0, ySpeed * time)); + if (!state.level.touches(movedY, this.size, "wall")) { + pos = movedY; + } else if (keys.ArrowUp && ySpeed > 0) { + ySpeed = -jumpSpeed; + } else { + ySpeed = 0; + } + return new Player(pos, new Vec(xSpeed, ySpeed)); +}; + +function trackKeys(keys) { + let down = Object.create(null); + function track(event) { + if (keys.includes(event.key)) { + down[event.key] = event.type == "keydown"; + event.preventDefault(); + } + } + window.addEventListener("keydown", track); + window.addEventListener("keyup", track); + return down; +} + +var arrowKeys = + trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]); + +function runAnimation(frameFunc) { + let lastTime = null; + function frame(time) { + if (lastTime != null) { + let timeStep = Math.min(time - lastTime, 100) / 1000; + if (frameFunc(timeStep) === false) return; + } + lastTime = time; + requestAnimationFrame(frame); + } + requestAnimationFrame(frame); +} + +function runLevel(level, Display) { + let display = new Display(document.body, level); + let state = State.start(level); + let ending = 1; + return new Promise(resolve => { + runAnimation(time => { + state = state.update(time, arrowKeys); + display.syncState(state); + if (state.status == "playing") { + return true; + } else if (ending > 0) { + ending -= time; + return true; + } else { + display.clear(); + resolve(state.status); + return false; + } + }); + }); +} + +async function runGame(plans, Display) { + for (let level = 0; level < plans.length;) { + let status = await runLevel(new Level(plans[level]), + Display); + if (status == "won") level++; + } + console.log("You've won!"); +} diff --git a/game-dom/code/levels.js b/game-dom/code/levels.js new file mode 100644 index 0000000..9ff491f --- /dev/null +++ b/game-dom/code/levels.js @@ -0,0 +1,178 @@ +var GAME_LEVELS = [` +................................................................................ +................................................................................ +................................................................................ +................................................................................ +................................................................................ +................................................................................ +..................................................................###........... +...................................................##......##....##+##.......... +....................................o.o......##..................#+++#.......... +.................................................................##+##.......... +...................................#####..........................#v#........... +............................................................................##.. +..##......................................o.o................................#.. +..#.....................o....................................................#.. +..#......................................#####.............................o.#.. +..#..........####.......o....................................................#.. +..#..@.......#..#................................................#####.......#.. +..############..###############...####################.....#######...#########.. +..............................#...#..................#.....#.................... +..............................#+++#..................#+++++#.................... +..............................#+++#..................#+++++#.................... +..............................#####..................#######.................... +................................................................................ +................................................................................ +`,` +................................................................................ +................................................................................ +....###############################............................................. +...##.............................##########################################.... +...#.......................................................................##... +...#....o...................................................................#... +...#................................................=.......................#... +...#.o........################...................o..o...........|........o..#... +...#.........................#..............................................#... +...#....o....................##########.....###################....##########... +...#..................................#+++++#.................#....#............ +...###############....oo......=o.o.o..#######.###############.#....#............ +.....#...............o..o.............#.......#......#........#....#............ +.....#....................#############..######.####.#.########....########..... +.....#.............########..............#...........#.#..................#..... +.....#..........####......####...#####################.#..................#..... +.....#........###............###.......................########....########..... +.....#.......##................#########################......#....#............ +.....#.......#................................................#....#............ +.....###......................................................#....#............ +.......#...............o...........................................#............ +.......#...............................................o...........#............ +.......#########......###.....############.........................##........... +.............#..................#........#####....#######.o.........########.... +.............#++++++++++++++++++#............#....#.....#..................#.... +.............#++++++++++++++++++#..........###....###...####.o.............#.... +.............####################..........#........#......#.....|.........#.... +...........................................#++++++++#......####............#.... +...........................................#++++++++#.........#........@...#.... +...........................................#++++++++#.........##############.... +...........................................##########........................... +................................................................................ +`,` +......................................#++#........................#######....................................#+#.. +......................................#++#.....................####.....####.................................#+#.. +......................................#++##########...........##...........##................................#+#.. +......................................##++++++++++##.........##.............##...............................#+#.. +.......................................##########++#.........#....................................o...o...o..#+#.. +................................................##+#.........#.....o...o....................................##+#.. +.................................................#+#.........#................................###############++#.. +.................................................#v#.........#.....#...#........................++++++++++++++##.. +.............................................................##..|...|...|..##............#####################... +..............................................................##+++++++++++##............v........................ +...............................................................####+++++####...................................... +...............................................#.....#............#######........###.........###.................. +...............................................#.....#...........................#.#.........#.#.................. +...............................................#.....#.............................#.........#.................... +...............................................#.....#.............................##........#.................... +...............................................##....#.............................#.........#.................... +...............................................#.....#......o..o.....#...#.........#.........#.................... +...............#######........###...###........#.....#...............#...#.........#.........#.................... +..............##.....##.........#...#..........#.....#.....######....#...#...#########.......#.................... +.............##.......##........#.o.#..........#....##...............#...#...#...............#.................... +.....@.......#.........#........#...#..........#.....#...............#...#...#...............#.................... +....###......#.........#........#...#..........#.....#...............#...#####...######......#.................... +....#.#......#.........#.......##.o.##.........#.....#...............#.....o.....#.#.........#.................... +++++#.#++++++#.........#++++++##.....##++++++++##....#++++++++++.....#.....=.....#.#.........#.................... +++++#.#++++++#.........#+++++##.......##########.....#+++++++##+.....#############.##..o.o..##.................... +++++#.#++++++#.........#+++++#....o.................##++++++##.+....................##.....##..................... +++++#.#++++++#.........#+++++#.....................##++++++##..+.....................#######...................... +++++#.#++++++#.........#+++++##.......##############++++++##...+.................................................. +++++#.#++++++#.........#++++++#########++++++++++++++++++##....+.................................................. +++++#.#++++++#.........#++++++++++++++++++++++++++++++++##.....+.................................................. +`,` +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +.............................................................................................................. +........................................o..................................................................... +.............................................................................................................. +........................................#..................................................................... +........................................#..................................................................... +........................................#..................................................................... +........................................#..................................................................... +.......................................###.................................................................... +.......................................#.#.................+++........+++..###................................ +.......................................#.#.................+#+........+#+..................................... +.....................................###.###................#..........#...................................... +......................................#...#.................#...oooo...#.......###............................ +......................................#...#.................#..........#......#+++#........................... +......................................#...#.................############.......###............................ +.....................................##...##......#...#......#................................................ +......................................#...#########...########..............#.#............................... +......................................#...#...........#....................#+++#.............................. +......................................#...#...........#.....................###............................... +.....................................##...##..........#....................................................... +......................................#...#=.=.=.=....#............###........................................ +......................................#...#...........#...........#+++#....................................... +......................................#...#....=.=.=.=#.....o......###.......###.............................. +.....................................##...##..........#.....................#+++#............................. +..............................o...o...#...#...........#.....#................##v........###................... +......................................#...#...........#..............#.................#+++#.................. +.............................###.###.###.###.....o.o..#++++++++++++++#...................v#................... +.............................#.###.#.#.###.#..........#++++++++++++++#........................................ +.............................#.............#...#######################........................................ +.............................##...........##.........................................###...................... +..###.........................#.....#.....#.........................................#+++#................###.. +..#.#.........................#....###....#..........................................###.................#.#.. +..#...........................#....###....#######........................#####.............................#.. +..#...........................#...........#..............................#...#.............................#.. +..#...........................##..........#..............................#.#.#.............................#.. +..#.......................................#.......|####|....|####|.....###.###.............................#.. +..#................###.............o.o....#..............................#.........###.....................#.. +..#...............#####.......##..........#.............................###.......#+++#..........#.........#.. +..#...............o###o.......#....###....#.............................#.#........###..........###........#.. +..#................###........#############..#.oo.#....#.oo.#....#.oo..##.##....................###........#.. +..#......@..........#.........#...........#++#....#++++#....#++++#....##...##....................#.........#.. +..#############################...........#############################.....################################.. +.............................................................................................................. +.............................................................................................................. +`,` +..................................................................................................###.#....... +......................................................................................................#....... +..................................................................................................#####....... +..................................................................................................#........... +..................................................................................................#.###....... +..........................o.......................................................................#.#.#....... +.............................................................................................o.o.o###.#....... +...................###................................................................................#....... +.......+..o..+................................................#####.#####.#####.#####.#####.#####.#####....... +.......#.....#................................................#...#.#...#.#...#.#...#.#...#.#...#.#........... +.......#=.o..#............#...................................###.#.###.#.###.#.###.#.###.#.###.#.#####....... +.......#.....#..................................................#.#...#.#...#.#...#.#...#.#...#.#.....#....... +.......+..o..+............o..................................####.#####.#####.#####.#####.#####.#######....... +.............................................................................................................. +..........o..............###..............................##.................................................. +.............................................................................................................. +.............................................................................................................. +......................................................##...................................................... +...................###.........###............................................................................ +.............................................................................................................. +..........................o.....................................................#......#...................... +..........................................................##.....##........................................... +.............###.........###.........###.................................#..................#................. +.............................................................................................................. +.................................................................||........................................... +..###########................................................................................................. +..#.........#.o.#########.o.#########.o.##................................................#................... +..#.........#...#.......#...#.......#...#.................||..................#.....#......................... +..#..@......#####...o...#####...o...#####..................................................................... +..#######.....................................#####.......##.....##.....###................................... +........#=..................=................=#...#.....................###................................... +........#######################################...#+++++++++++++++++++++###+++++++++++++++++++++++++++++++++++ +..................................................############################################################ +.............................................................................................................. +`]; + +if (typeof module != "undefined" && module.exports && (typeof window == "undefined" || window.exports != exports)) + module.exports = GAME_LEVELS; +if (typeof global != "undefined" && !global.GAME_LEVELS) + global.GAME_LEVELS = GAME_LEVELS; diff --git a/game-dom/css/game.css b/game-dom/css/game.css new file mode 100644 index 0000000..bb77778 --- /dev/null +++ b/game-dom/css/game.css @@ -0,0 +1,24 @@ +.background { background: rgb(52, 166, 251); + table-layout: fixed; + border-spacing: 0; } +.background td { padding: 0; } +.lava { background: rgb(255, 100, 100); } +.wall { background: white; } + +.actor { position: absolute; } +.coin { background: rgb(241, 229, 89); } +.player { background: rgb(64, 64, 64); } + +.game { + overflow: hidden; + max-width: 600px; + max-height: 450px; + position: relative; +} + +.lost .player { + background: rgb(160, 64, 64); +} +.won .player { + box-shadow: -4px -7px 8px white, 4px -7px 8px white; +} diff --git a/game-dom/index.html b/game-dom/index.html new file mode 100644 index 0000000..d2a6f66 --- /dev/null +++ b/game-dom/index.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/game-dom/readme.txt b/game-dom/readme.txt new file mode 100644 index 0000000..fbf09fc --- /dev/null +++ b/game-dom/readme.txt @@ -0,0 +1,12 @@ +This example is part of: + +Marijn Haverbeke +Eloquent JavaScript +3rd edition (2018) + +https://eloquentjavascript.net + + +Chapter 16: Project: A Platform Game +https://eloquentjavascript.net/16_game.html + diff --git a/memo.js b/memo.js new file mode 100644 index 0000000..c713ab6 --- /dev/null +++ b/memo.js @@ -0,0 +1,12 @@ +function memo (func) { + let cache = new Map() + + return (...args) => { + let argStr = args.toString(), result + if ((result = cache.get(argStr)) === undefined) { + result = func(...args) + cache.set(argStr, result) + } + return result + } +}