added Maze game
This commit is contained in:
parent
60d815f71c
commit
b6f9e1e9f8
46
src/game-maze-context2d.html
Normal file
46
src/game-maze-context2d.html
Normal 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();">☀</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>
|
@ -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>
|
||||||
|
@ -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> ⚒WIP⚒</li>
|
<li> <a href="test-collision.html">Collision test(context2d)</a> ⚒WIP⚒</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
692
src/js/game-maze-context2d.js
Normal file
692
src/js/game-maze-context2d.js
Normal 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);
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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?");
|
||||||
}
|
}
|
||||||
|
@ -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
BIN
src/snd/flash.mp3
Normal file
Binary file not shown.
BIN
src/snd/footstep.mp3
Normal file
BIN
src/snd/footstep.mp3
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/snd/metronome.mp3
Normal file
BIN
src/snd/metronome.mp3
Normal file
Binary file not shown.
BIN
src/snd/snap.mp3
Normal file
BIN
src/snd/snap.mp3
Normal file
Binary file not shown.
BIN
src/snd/surprise.mp3
Normal file
BIN
src/snd/surprise.mp3
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user