added Maze game

This commit is contained in:
Ward Truyen 2024-08-14 00:43:08 +02:00
parent 60d815f71c
commit b6f9e1e9f8
15 changed files with 756 additions and 1 deletions

View File

@ -6,3 +6,4 @@
- Tetris - Tetris
- Maze

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title>Game: Maze</title>
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<link rel="stylesheet" href="css/game.css" />
<link rel="stylesheet" href="css/main.css" />
<script src="term/wterminal.js"></script>
<script src="js/game-maze-context2d.js"></script>
</head>
<body>
<header>
<button title="Dark theme on/off" onclick="toggleTheme();">&#9728;</button>
<a href="index.html">Willem Games</a>
</header>
<main>
<h1>Game: Maze</h1>
<div class="game-area" id="game-area">Loading ...</div>
<h2>How to:</h2>
<p>Use the arrow keys up, down, left and right to move, or [W], [S], [A] and [D].<br>
Press spacebar or enter, to select.</p>
<button onclick="startMaze('game-area', 480, 320, 1);">start normal</button>
<button onclick="startMaze('game-area', 320 , 480, 1);">start portrait</button>
<button onclick="startMaze('game-area', 800 , 640, 1);">start large</button>
<script>
WTerminal.instalDropdownTerminal();
startMaze('game-area', 800, 640, 1); // startGame(element-id, width, height, zoom)
</script>
</main>
<footer>
<p class="float-right">Author: Ward Truyen</p>
</footer>
<script src="js/theme.js"></script>
<script src="term/wterminal-autoextend.js"></script>
</body>
</html>

View File

@ -40,6 +40,7 @@
<script src="js/theme.js"></script> <script src="js/theme.js"></script>
<script src="js/game-pong-context2d.js"></script> <script src="js/game-pong-context2d.js"></script>
<script src="js/game-tetris-context2d.js"></script> <script src="js/game-tetris-context2d.js"></script>
<script src="js/game-maze-context2d.js"></script>
</body> </body>
</html> </html>

View File

@ -25,6 +25,7 @@
<ul> <ul>
<li> <a href="game-pong-context2d.html">Ping pong (context2d)</a></li> <li> <a href="game-pong-context2d.html">Ping pong (context2d)</a></li>
<li> <a href="game-tetris-context2d.html">Tetris (context2d)</a></li> <li> <a href="game-tetris-context2d.html">Tetris (context2d)</a></li>
<li> <a href="game-maze-context2d.html">Maze game(context2d)</a></li>
<li> <a href="test-collision.html">Collision test(context2d)</a> &#9874;WIP&#9874;</li> <li> <a href="test-collision.html">Collision test(context2d)</a> &#9874;WIP&#9874;</li>
</ul> </ul>

View File

@ -0,0 +1,692 @@
// this uses the Backtracking Algorithm to generate a maze
class Maze_CTX2D {
static get DEBUG_PARENT_OBJECT() { return true; };
static get AUTO_CONTINUE_ON_FOCUS() { return false; };
static get SHOW_FPS_INTERVAL() { return 1000 / 4; }; // four times per second
static get KEY_ARROW_UP() { return 'ArrowUp'; };
static get KEY_ARROW_DOWN() { return 'ArrowDown'; };
static get KEY_ARROW_LEFT() { return 'ArrowLeft'; };
static get KEY_ARROW_RIGHT() { return 'ArrowRight'; };
static get KEY_W() { return 'w'; };
static get KEY_S() { return 's'; };
static get KEY_A() { return 'a'; };
static get KEY_D() { return 'd'; };
static get KEY_ENTER() { return 'Enter'; };
static get KEY_SPACEBAR() { return ' '; };
static get KEY_ESCAPE() { return 'Escape'; };
static get STATE_MENU() { return 0; };
static get STATE_MAP_GEN_ANIM() { return 1; };
static get STATE_PLAYING() { return 2; };
static get STATE_ENDED() { return 3; };
static get ANIMATION_DELAY() { return 10; };
animationRequestId = 0;
running = false;
constructor(divId, width, height, difficulty = 1, showFps = false) {
this.createCanvas(divId, width, height);
this.canvasEl.title = "Playing: Maze";
this.ctx = this.canvasEl.getContext("2d");
this.ctx.textAlign = "center";
this.audioCtx = new AudioContext();
this.sounds = [];
this.sounds[0] = new Audio('./snd/short-success.mp3');
this.audioCtx.createMediaElementSource(this.sounds[0]).connect(this.audioCtx.destination);
this.sounds[1] = new Audio('./snd/footstep.mp3');
this.audioCtx.createMediaElementSource(this.sounds[1]).connect(this.audioCtx.destination);
this.sounds[2] = new Audio('./snd/glass-knock.mp3');
this.audioCtx.createMediaElementSource(this.sounds[2]).connect(this.audioCtx.destination);
this.sounds[3] = new Audio('./snd/flash.mp3');
this.audioCtx.createMediaElementSource(this.sounds[3]).connect(this.audioCtx.destination);
this.sounds[4] = new Audio('./snd/metronome.mp3');
this.audioCtx.createMediaElementSource(this.sounds[4]).connect(this.audioCtx.destination);
this.showFps = showFps;
if (showFps) {
// add framecounter and fps variables and html
this.frameCounter = 0;
this.initTime = performance.now();
const el = document.createElement('p');
this.frameLabel = document.createElement('span');
const floatRight = document.createElement('span');
floatRight.style = "float: right;";
this.fpsCounter = 0;
this.fpsLabel = document.createElement('span');
el.appendChild(document.createTextNode('fps: '));
el.appendChild(this.fpsLabel);
floatRight.appendChild(document.createTextNode('frame: '));
floatRight.appendChild(this.frameLabel);
el.appendChild(floatRight);
this.divEl.appendChild(el);
}
this.setDifficulty(difficulty);
this.gameState = Maze_CTX2D.STATE_MENU;
this.canvasEl.addEventListener("keydown", (e) => this.onKeyDown(e));
this.canvasEl.addEventListener("keyup", (e) => this.onKeyUp(e));
this.canvasEl.addEventListener("blur", (e) => this.onBlur(e));
this.canvasEl.addEventListener("focus", (e) => this.onFocus(e));
if (Maze_CTX2D.DEBUG_PARENT_OBJECT) window.game = this;
if (typeof WTerminal === "function") {
WTerminal.terminalAddCommand("restartgame", (t) => this.terminalRestartGame(t));
WTerminal.terminalAddCommand("printgame", (t) => t.printVar(this, "maze"));
WTerminal.printLn("new Maze: @", divId, ' ', width, 'x', height, ' difficulty=', difficulty, ' showFps=', showFps);
}
this.drawCanvas();
}
newGame(gridWidth, gridHeight) {
gridWidth -= gridWidth % 2; gridWidth++;
gridHeight -= gridHeight % 2; gridHeight++;
this.gridWidth = gridWidth;
this.gridHeight = gridHeight;
this.gridCellWidth = this.width / gridWidth;
this.gridCellHeight = this.height / gridHeight;
this.gridCellSize = Math.floor(Math.min(this.gridCellWidth, this.gridCellHeight));
this.mapWidth = gridWidth * this.gridCellSize;
this.mapHeight = gridHeight * this.gridCellSize;
this.mapOffsetX = (this.width - this.mapWidth) / 2;
this.mapOffsetY = (this.height - this.mapHeight) / 2;
var result = this.generateBacktrackingMaze(gridWidth, gridHeight);
this.maze = result.maze;
this.animation = result.animation;
this.animationPos = 0;
this.animationStepTime = 0;
this.animationStepDelay = Maze_CTX2D.ANIMATION_DELAY / this.animation.length;
this.player = { x: 1, y: 0 };
// console.log("animation", this.animation);
}
playSound(index) {
try {
const snd = this.sounds[index];
snd.currentTime = 0;
snd.play();
} catch (e) {
console.log(`Failed to play sound '${index}': ${e}}`)
}
}
createCanvas(divId, width = 0, height = 0, zoom = 1) {
this.divEl = document.getElementById(divId);
if (this.divEl === null) throw new Error("elementId not found: " + divId);
while (this.divEl.firstChild) {
this.divEl.removeChild(this.divEl.lastChild);
}
this.canvasEl = this.divEl.appendChild(document.createElement("canvas"));
const c = this.canvasEl;
this.width = width;
this.height = height;
if (width > 0 && height > 0) {
c.style.width = width * zoom + 'px';
c.style.height = height * zoom + 'px';
c.width = width;
c.height = height;
}
c.tabIndex = 0; // improtant for keyboard focus!
// c.style.imageRendering = 'pixelated';
}
drawCanvas() {
if (this.showFps) {
this.frameCounter++;
this.fpsCounter++;
const now = performance.now();
const diff = now - this.initTime;
if (Maze_CTX2D.SHOW_FPS_INTERVAL < diff || !this.running) {
this.initTime = now;
const seconds = diff / 1000;
const fps = this.fpsCounter / seconds;
this.fpsCounter = 0;
if (this.frameLabel) this.frameLabel.innerHTML = this.frameCounter;
if (this.fpsLabel) {
this.fpsLabel.innerHTML = Math.round((fps + Number.EPSILON) * 100) / 100;
if (!this.running) this.fpsLabel.innerHTML += " (not running)";
}
}
}
const ctx = this.ctx;
ctx.clearRect(0, 0, this.width, this.height);
if (this.gameState == Maze_CTX2D.STATE_MENU) {
ctx.strokeStyle = 'black';
ctx.fillStyle = "#4080ff";
const FONT_SIZE = this.width > 440 ? 24 : 16;
const x = this.width / 2;
const y = this.height / 2;
let text = "Menu";
ctx.font = "bold " + (2 * FONT_SIZE) + "px serif";
ctx.fillText(text, x, y - 3 * FONT_SIZE);
ctx.strokeText(text, x, y - 3 * FONT_SIZE);
ctx.font = "bold " + FONT_SIZE + "px mono";
ctx.fillStyle = "#b0b0b0";
text = `Difficulty: ${this.difficulty}`;
ctx.fillText(text, x, y + 0 * FONT_SIZE);
ctx.strokeText(text, x, y + 0 * FONT_SIZE);
text = `Size: ${this.gridWidth} x ${this.gridHeight}`;
ctx.fillText(text, x, y + 2 * FONT_SIZE);
ctx.strokeText(text, x, y + 2 * FONT_SIZE);
if (this.isFocused) return;
this.drawBanner("Click here to continue. (unfocused)");
} else if (this.gameState == Maze_CTX2D.STATE_MAP_GEN_ANIM) {
ctx.fillStyle = "#404040";
ctx.fillRect(this.mapOffsetX, this.mapOffsetY, this.mapWidth, this.mapHeight);
ctx.fillStyle = "blue";
for (let i = 0; i < this.animationPos; i++) {
const animationStep = this.animation[i];
if (animationStep.state == 0) {
//clearRect
ctx.clearRect(this.mapOffsetX + animationStep.x * this.gridCellSize,
this.mapOffsetY + animationStep.y * this.gridCellSize, this.gridCellSize, this.gridCellSize);
} else {
//fillRect
// ctx.fillRect(this.mapOffsetX + animationStep.x * this.gridCellSize,
// this.mapOffsetY + animationStep.y * this.gridCellSize, this.gridCellSize, this.gridCellSize);
ctx.beginPath();
ctx.arc(this.mapOffsetX + (0.5 + animationStep.x) * this.gridCellSize,
this.mapOffsetY + (0.5 + animationStep.y) * this.gridCellSize,
this.gridCellSize / 2, 0, Math.PI * 2);
ctx.fill();
}
}
if (!this.running) {
if (this.isFocused)
this.drawBanner("press space to continue. (paused)");
else
this.drawBanner("Click here to continue. (unfocused)");
}
} else {// Maze_CTX2D.STATE_ENDED & Maze_CTX2D.STATE_PLAYING
ctx.fillStyle = "#505050";
for (let i = 0; i < this.maze.length; i++) {
for (let j = 0; j < this.maze[i].length; j++) {
if (this.maze[i][j] != 0) {
ctx.fillRect(this.mapOffsetX + j * this.gridCellSize,
this.mapOffsetY + i * this.gridCellSize, this.gridCellSize, this.gridCellSize);
}
}
}
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(this.mapOffsetX + (0.5 + this.player.x) * this.gridCellSize,
this.mapOffsetY + (0.5 + this.player.y) * this.gridCellSize,
this.gridCellSize / 2, 0, Math.PI * 2);
ctx.fill();
if (this.gameState == Maze_CTX2D.STATE_ENDED) {
if (this.isFocused)
this.drawBanner("Press space or enter to continue", "Victory!");
ctx.lineWidth = 3;
for (let p of this.fireworks) {
ctx.strokeStyle = p.color;
if (p.type >= 1) {
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);
ctx.stroke();
} else {
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.x + p.vx * 0.1, p.y + p.vy * 0.1);
ctx.stroke();
}
}
ctx.lineWidth = 1;
if (!this.isFocused)
this.drawBanner("Click here to continue. (unfocused)", "Paused");
}
if (this.gameState == Maze_CTX2D.STATE_PLAYING) {
if (!this.isFocused)
this.drawBanner("Click here to continue. (unfocused)", "Paused");
}
}
}
drawBanner(innerText, titleText) {
const ctx = this.ctx;
const FONT_SIZE = this.width > 440 ? 24 : 16;
const x = this.width / 2;
const y = this.height / 2;
const y2 = y - FONT_SIZE / 2;
ctx.strokeStyle = 'black';
const g = ctx.createLinearGradient(0, 0, this.width, 0);
g.addColorStop(0, '#404040a0');
g.addColorStop(0.2, '#404040df');
g.addColorStop(0.8, '#404040df');
g.addColorStop(1, '#404040a0');
ctx.fillStyle = g;
ctx.fillRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
ctx.strokeRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
if (typeof innerText === "string") {
ctx.fillStyle = 'goldenrod';//this.isFocused ? 'goldenrod' : 'lightgray';
ctx.font = FONT_SIZE + "px serif";
ctx.strokeText(innerText, x, y2 + FONT_SIZE);
ctx.fillText(innerText, x, y2 + FONT_SIZE);
}
if (typeof titleText === "string") {
ctx.fillStyle = this.isFocused ? 'red' : 'gray';
ctx.font = 4 * FONT_SIZE + "px serif";
ctx.fillText(titleText, x, y - 2 * FONT_SIZE);
ctx.strokeText(titleText, x, y - 2 * FONT_SIZE);
}
}
updateCanvas() {
const now = performance.now();
const timeDelta = (now - this.prevNow) / 1000; //timeDelta = (milli - milli) / toSeconds
this.prevNow = now;
//#state switch
if (this.gameState == Maze_CTX2D.STATE_MAP_GEN_ANIM) {
this.animationStepTime += timeDelta;
while (this.animationStepTime >= this.animationStepDelay) {
this.animationStepTime -= this.animationStepDelay;
if (this.animationPos < this.animation.length) {
this.animationPos++;
} else {
this.gameState = Maze_CTX2D.STATE_PLAYING;
this.running = false;
this.playSound(3);
}
}
} else if (this.gameState == Maze_CTX2D.STATE_ENDED) {
this.fireworksStepTime += timeDelta;
if (this.fireworksStepTime > 3) {
this.boom();
this.fireworksStepTime = 0 + Math.random() * .5;
}
for (let p of this.fireworks) {
p.time += timeDelta;
if (p.time > 2) {
p.time = 0;
p.type += 1;
}
if (p.type == 0) {
p.x += p.vx * timeDelta;
p.y += p.vy * timeDelta;
} else if (p.type == 1) {
p.size += 10 * timeDelta;
} else {
p.size += timeDelta;
}
}
while (this.fireworks.length > 0 && this.fireworks[0].type == 3) this.fireworks.shift();
}
this.drawCanvas();
if (this.running) { // loop
this.animationRequestId = requestAnimationFrame(() => this.updateCanvas());
} else { // not looping
this.animationRequestId = 0;
}
}
startRunning(fn) {
if (this.animationRequestId != 0) cancelAnimationFrame(this.animationRequestId);
this.running = true;
this.prevNow = performance.now();
if (this.showFps) {
this.initTime = performance.now();
}
this.animationRequestId = requestAnimationFrame(fn);
}
close() {
if (this.animationRequestId != 0) {
cancelAnimationFrame(this.animationRequestId);
this.animationRequestId = 0;
}
this.running = false;
}
pausePlayGame() {
this.running = !this.running;
if (this.running) {
this.startRunning(() => this.updateCanvas());
}
}
terminalPrintGame(term) {
term.printVar(this, "pong");
}
terminalRestartGame(term) {
term.terminalClose();
this.restartGame();
// this.canvasEl.focus();
setTimeout(() => { this.canvasEl.focus(); this.pausePlayGame(); }, 200);
}
setDifficulty(difficulty) {
if (difficulty <= 0) difficulty = 0;
this.difficulty = difficulty;
this.gridWidth = 11 + 4 * this.difficulty;
this.gridHeight = 11 + 4 * this.difficulty;
}
onKeyDown(e) {
if (e.key == Maze_CTX2D.KEY_ESCAPE) {
console.log('esc');
console.log('running:', this.running);
if (this.running) {
this.pausePlayGame();
} else {
this.gameState = Maze_CTX2D.STATE_MENU;
this.drawCanvas();
}
e.preventDefault();
return false;
}
switch (this.gameState) {
case Maze_CTX2D.STATE_MENU:
if (e.key == Maze_CTX2D.KEY_ENTER || e.key == Maze_CTX2D.KEY_SPACEBAR) {
// console.log("ENTER!");
this.newGame(this.gridWidth, this.gridHeight);
this.gameState = Maze_CTX2D.STATE_MAP_GEN_ANIM;
this.startRunning(() => this.updateCanvas());
this.playSound(4);
e.preventDefault();
return false;
}
if (e.key == Maze_CTX2D.KEY_S || e.key == Maze_CTX2D.KEY_ARROW_DOWN ||
e.key == Maze_CTX2D.KEY_A || e.key == Maze_CTX2D.KEY_ARROW_LEFT) {
this.setDifficulty(this.difficulty - 1);
this.playSound(1);
this.drawCanvas();
e.preventDefault();
return false;
}
if (e.key == Maze_CTX2D.KEY_W || e.key == Maze_CTX2D.KEY_ARROW_UP ||
e.key == Maze_CTX2D.KEY_D || e.key == Maze_CTX2D.KEY_ARROW_RIGHT) {
this.setDifficulty(this.difficulty + 1);
this.playSound(1);
this.drawCanvas();
e.preventDefault();
return false;
}
break;
case Maze_CTX2D.STATE_MAP_GEN_ANIM:
if (e.key == Maze_CTX2D.KEY_ENTER || e.key == Maze_CTX2D.KEY_SPACEBAR) {
if (this.running) {
this.animationPos = this.animation.length;
} else {
this.startRunning(() => this.updateCanvas());
}
e.preventDefault();
return false;
}
break;
case Maze_CTX2D.STATE_PLAYING:
if (e.key == 'r') {
this.gameState = Maze_CTX2D.STATE_MAP_GEN_ANIM;
this.animationPos = 0;
this.animationStepTime = 0;
this.startRunning(() => this.updateCanvas());
e.preventDefault();
return false;
}
if (e.key == Maze_CTX2D.KEY_W || e.key == Maze_CTX2D.KEY_ARROW_UP) {
if (this.player.y > 0) {
if (this.maze[this.player.y - 1][this.player.x] == 0) {
this.player.y -= 1;
this.drawCanvas();
this.playSound(1);
} else {
this.playSound(2);
}
}
e.preventDefault();
return false;
}
if (e.key == Maze_CTX2D.KEY_S || e.key == Maze_CTX2D.KEY_ARROW_DOWN) {
if (this.player.y < this.gridHeight - 1) {
if (this.maze[this.player.y + 1][this.player.x] == 0) {
this.player.y += 1;
if (this.player.y == this.gridHeight - 1) {
this.gameState = Maze_CTX2D.STATE_ENDED;
this.fireworks = [];
this.fireworksStepTime = 0;
this.boom();
this.playSound(0);
// this.drawCanvas();
this.startRunning(() => this.updateCanvas())
} else {
this.drawCanvas();
}
this.playSound(1);
} else {
this.playSound(2);
}
}
e.preventDefault();
return false;
}
if (e.key == Maze_CTX2D.KEY_A || e.key == Maze_CTX2D.KEY_ARROW_LEFT) {
if (this.player.x > 0) {
if (this.maze[this.player.y][this.player.x - 1] == 0) {
this.player.x -= 1;
this.drawCanvas();
this.playSound(1);
} else {
this.playSound(2);
}
}
e.preventDefault();
return false;
}
if (e.key == Maze_CTX2D.KEY_D || e.key == Maze_CTX2D.KEY_ARROW_RIGHT) {
if (this.player.x < this.gridWidth) {
if (this.maze[this.player.y][this.player.x + 1] == 0) {
this.player.x += 1;
this.drawCanvas();
this.playSound(1);
} else {
this.playSound(2);
}
}
e.preventDefault();
return false;
}
break;
case Maze_CTX2D.STATE_ENDED:
if (e.key == Maze_CTX2D.KEY_ENTER || e.key == Maze_CTX2D.KEY_SPACEBAR) {
this.gameState = Maze_CTX2D.STATE_MENU;
e.preventDefault();
return false;
}
break;
}
// if (e.key == Maze_CTX2D.KEY_ARROW_UP || e.key == Maze_CTX2D.KEY_W) {
// this.human.vy = -this.speedHuman;
// e.preventDefault();
// return false;
// } else if (e.key == Maze_CTX2D.KEY_ARROW_DOWN || e.key == Maze_CTX2D.KEY_S) {
// this.human.vy = this.speedHuman;
// e.preventDefault();
// return false;
// } else if (e.key == Maze_CTX2D.KEY_ENTER || e.key == Maze_CTX2D.KEY_SPACEBAR) {
// //# next round/pause/play
// if (this.gameState == Maze_CTX2D.STATE_ENDED) {
// this.newRound();
// this.startRunning(() => this.updateCanvas());
// } else {
// this.pausePlayGame();
// }
// e.preventDefault();
// return false;
// }
return true;
}
onKeyUp(e) {
// if (e.key == Maze_CTX2D.KEY_ARROW_UP || e.key == Maze_CTX2D.KEY_W) {
// this.human.vy = 0;
// e.preventDefault();
// return false;
// } else if (e.key == Maze_CTX2D.KEY_ARROW_DOWN || e.key == Maze_CTX2D.KEY_S) {
// this.human.vy = 0;
// e.preventDefault();
// return false;
// }
return true;
}
onBlur() {
this.isFocused = false;
this.canvasEl.style.borderColor = null;
if (this.running) {
this.pausePlayGame();
} else {
this.drawCanvas();
}
}
onFocus() {
this.isFocused = true;
this.canvasEl.style.borderColor = "red";
if (!this.running && (Maze_CTX2D.AUTO_CONTINUE_ON_FOCUS
|| this.gameState == Maze_CTX2D.STATE_MAP_GEN_ANIM
|| this.gameState == Maze_CTX2D.STATE_ENDED)) {
this.pausePlayGame();
} else {
this.drawCanvas();
}
}
boom() {
const colors = ['red', 'green', 'blue', 'violet', 'orange', 'goldenrod'];
const color = colors[Math.floor(Math.random() * colors.length)];
const cx = this.width / 2;
const cy = this.height / 2;
const speed = 100;
const size = 10;
const count = Math.random() < 0.75 ? 2 : 3;
for (let i = 0; i < count; i++) {
let p = {}
p.x = cx;
p.y = cy;
let rot = Math.random() * Math.PI * .5 - 0.75 * Math.PI;
p.vx = Math.cos(rot) * speed;
p.vy = Math.sin(rot) * speed;
p.type = 0;
p.time = 0 + Math.random() * 0.5;
p.size = size + Math.random() * 10;
p.color = color;
this.fireworks.push(p)
}
}
generateBacktrackingMaze(width, height) {
// Make them odd
width -= width % 2; width++;
height -= height % 2; height++;
// Fill maze with 1's (walls)
let maze = [];
for (let i = 0; i < height; i++) {
maze.push([]);
for (let j = 0; j < width; j++) {
maze[i].push(1);
}
}
let anim = [];
// Opening at top - start of maze
maze[0][1] = 0;
let start = [];
do {
start[0] = Math.floor(Math.random() * height)
} while (start[0] % 2 == 0);
do {
start[1] = Math.floor(Math.random() * width)
} while (start[1] % 2 == 0);
maze[start[0]][start[1]] = 0;
anim.push({ state: 1, x: start[1], y: start[0] });
// First open cell
let openCells = [start];
while (openCells.length) {
let cell, n;
// Add unnecessary element for elegance of code
// Allows openCells.pop() at beginning of do while loop
openCells.push([-1, -1]);
// Define current cell as last element in openCells
// and get neighbors, discarding "locked" cells
do {
let step = openCells.pop();
if (step[0] > 0 && step[1] > 0) anim.push({ state: 0, x: step[1], y: step[0] });
if (openCells.length == 0)
break;
cell = openCells[openCells.length - 1];
n = this.getMazeNeighbors(maze, cell[0], cell[1]);
} while (n.length == 0 && openCells.length > 0);
// If we're done, don't bother continuing
if (openCells.length == 0)
break;
// Choose random neighbor and add it to openCells
let choice = n[Math.floor(Math.random() * n.length)];
openCells.push(choice);
// Set neighbor to 0 (path, not wall)
// Set connecting node between cell and choice to 0
let connectY = (choice[0] + cell[0]) / 2;
let connectX = (choice[1] + cell[1]) / 2;
maze[choice[0]][choice[1]] = 0;
maze[connectY][connectX] = 0;
anim.push({ state: 0, x: connectX, y: connectY });
anim.push({ state: 1, x: choice[1], y: choice[0] });
}
// Opening at bottom - end of maze
maze[maze.length - 1][maze[0].length - 2] = 0;
// maze[maze.length - 2][maze[0].length - 2] = 0;
anim.push({ state: 0, x: maze[0].length - 2, y: maze.length - 1 });
// anim.push({ state: 0, x: maze[0].length - 1, y: maze.length - 1 });
return { maze: maze, animation: anim };
}
getMazeNeighbors(maze, ic, jc) {
let final = [];
for (let i = 0; i < 4; i++) {
let n = [ic, jc];
// Iterates through four neighbors
// [i][j - 2]
// [i][j + 2]
// [i - 2][j]
// [i + 2][j]
n[i % 2] += ((Math.floor(i / 2) * 2) || -2);
if (n[0] < maze.length &&
n[1] < maze[0].length &&
n[0] > 0 &&
n[1] > 0) {
if (maze[n[0]][n[1]] == 1) {
final.push(n);
}
}
}
return final;
}
}//-> class Maze_CTX2D
function startMaze(divId, width = 480, height = 320, difficulty = 1, showFps = true) {
return new Maze_CTX2D(divId, width, height, difficulty, showFps);
}

View File

@ -41,6 +41,8 @@ class PingPong_CTX2D {
this.audioCtx.createMediaElementSource(this.sounds[0]).connect(this.audioCtx.destination); this.audioCtx.createMediaElementSource(this.sounds[0]).connect(this.audioCtx.destination);
this.sounds[1] = new Audio('./snd/short-success.mp3'); this.sounds[1] = new Audio('./snd/short-success.mp3');
this.audioCtx.createMediaElementSource(this.sounds[1]).connect(this.audioCtx.destination); this.audioCtx.createMediaElementSource(this.sounds[1]).connect(this.audioCtx.destination);
this.sounds[2] = new Audio('./snd/flash.mp3');
this.audioCtx.createMediaElementSource(this.sounds[2]).connect(this.audioCtx.destination);
this.showFps = showFps; this.showFps = showFps;
if (showFps) { if (showFps) {
@ -255,6 +257,7 @@ class PingPong_CTX2D {
this.countDown -= timeDelta;//PONG_UPDATE_INTERVAL; this.countDown -= timeDelta;//PONG_UPDATE_INTERVAL;
if (this.countDown < 0) { if (this.countDown < 0) {
this.gameState = PingPong_CTX2D.STATE_PLAYING; this.gameState = PingPong_CTX2D.STATE_PLAYING;
this.playSound(2);
} }
} else if (this.gameState == PingPong_CTX2D.STATE_PLAYING) { } else if (this.gameState == PingPong_CTX2D.STATE_PLAYING) {
//cpu actions //cpu actions

View File

@ -40,6 +40,7 @@ class GamesLauncher {
this.selectGameEl = createElement('select', { id: 'games', style: 'float: right;' }); this.selectGameEl = createElement('select', { id: 'games', style: 'float: right;' });
this.selectGameEl.appendChild(createElement('option', { value: 'pong-context2d' }, 'Pingpong (Context2D)')); this.selectGameEl.appendChild(createElement('option', { value: 'pong-context2d' }, 'Pingpong (Context2D)'));
this.selectGameEl.appendChild(createElement('option', { value: 'tetris-context2d' }, 'Tetris (Context2D)')); this.selectGameEl.appendChild(createElement('option', { value: 'tetris-context2d' }, 'Tetris (Context2D)'));
this.selectGameEl.appendChild(createElement('option', { value: 'maze-context2d' }, 'Maze (Context2D)'));
this.selectGameEl.onchange = () => this.onSelectGameChange(); this.selectGameEl.onchange = () => this.onSelectGameChange();
this.gameEl.appendChild(this.selectGameEl); this.gameEl.appendChild(this.selectGameEl);
this.gameEl.appendChild(createElement('br')); this.gameEl.appendChild(createElement('br'));
@ -93,6 +94,11 @@ class GamesLauncher {
this.heightEl.value = 600; this.heightEl.value = 600;
// this.difficultyEl.value = 1; // this.difficultyEl.value = 1;
break; break;
case 'maze-context2d':
this.widthEl.value = 800;
this.heightEl.value = 640;
// this.difficultyEl.value = 1;
break;
} }
} }
@ -113,6 +119,11 @@ class GamesLauncher {
this.currentGame = new Tetris_CTX2D(this.gameId, width, height, difficulty, showFps); this.currentGame = new Tetris_CTX2D(this.gameId, width, height, difficulty, showFps);
this.currentGame.canvasEl.focus(); this.currentGame.canvasEl.focus();
break; break;
case 'maze-context2d':
this.createCloseExitGameButton();
this.currentGame = new Maze_CTX2D(this.gameId, width, height, difficulty, showFps);
this.currentGame.canvasEl.focus();
break;
default: default:
alert("Select a game first?"); alert("Select a game first?");
} }

View File

@ -10,7 +10,7 @@ function toggleTheme() {
} }
function setTheme(themeName) { function setTheme(themeName) {
console.log("setting them: " + themeName); console.log("setting theme: " + themeName);
const root = document.querySelector(":root"); const root = document.querySelector(":root");
root.classList.add(themeName); root.classList.add(themeName);
localStorage.setItem('theme', themeName); localStorage.setItem('theme', themeName);

Binary file not shown.

BIN
src/snd/flash.mp3 Normal file

Binary file not shown.

BIN
src/snd/footstep.mp3 Normal file

Binary file not shown.

Binary file not shown.

BIN
src/snd/metronome.mp3 Normal file

Binary file not shown.

BIN
src/snd/snap.mp3 Normal file

Binary file not shown.

BIN
src/snd/surprise.mp3 Normal file

Binary file not shown.