add subprojects
This commit is contained in:
parent
3ac982dfa6
commit
07670ef8af
BIN
centipede/.DS_Store
vendored
Normal file
BIN
centipede/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
centipede/assets/.DS_Store
vendored
Normal file
BIN
centipede/assets/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
centipede/assets/images/.DS_Store
vendored
Normal file
BIN
centipede/assets/images/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
centipede/assets/images/centipede-assets.png
Normal file
BIN
centipede/assets/images/centipede-assets.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
centipede/assets/sounds/.DS_Store
vendored
Normal file
BIN
centipede/assets/sounds/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
centipede/assets/sounds/enemy_hit.wav
Normal file
BIN
centipede/assets/sounds/enemy_hit.wav
Normal file
Binary file not shown.
BIN
centipede/assets/sounds/laser.wav
Normal file
BIN
centipede/assets/sounds/laser.wav
Normal file
Binary file not shown.
BIN
centipede/assets/sounds/mushroom_hit.wav
Normal file
BIN
centipede/assets/sounds/mushroom_hit.wav
Normal file
Binary file not shown.
63
centipede/css/style.css
Normal file
63
centipede/css/style.css
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Lucida Sans Typewriter,Lucida Console,monaco,Bitstream Vera Sans Mono,monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-holder canvas {
|
||||||
|
padding: 0;
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
height: 100vh;
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-hud {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -5px);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
background-color: rgba(255,255,255,0.5);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-top: 3px;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-hud p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 3vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
text-align:center;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
position:absolute;
|
||||||
|
|
||||||
|
background-color: rgba(255,255,255,0.75);
|
||||||
|
border: 1px solid #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button:hover {
|
||||||
|
background-color: #d0d0d0;
|
||||||
|
}
|
38
centipede/index.html
Normal file
38
centipede/index.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Centipede</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="text-align:center">
|
||||||
|
<div class="canvas-holder">
|
||||||
|
<canvas id="game-canvas" width="1400" height="1000"></canvas>
|
||||||
|
<div class="game-hud" id="hud">
|
||||||
|
<p>Hello, world!</p>
|
||||||
|
</div>
|
||||||
|
<div class="menu" style="display: none" id="menu">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/game/game.js"></script>
|
||||||
|
<script src="js/game/input.js"></script>
|
||||||
|
<script src="js/game/menu.js"></script>
|
||||||
|
<script src="js/game/graphics.js"></script>
|
||||||
|
<script src="js/game/sprites.js"></script>
|
||||||
|
<script src="js/game/sounds.js"></script>
|
||||||
|
<script src="js/game/object.js"></script>
|
||||||
|
<script src="js/game/objects/player.js"></script>
|
||||||
|
<script src="js/game/objects/mushroom.js"></script>
|
||||||
|
<script src="js/game/objects/bullet.js"></script>
|
||||||
|
<script src="js/game/objects/centipede.js"></script>
|
||||||
|
<script src="js/game/objects/explosion.js"></script>
|
||||||
|
<script src="js/game/objects/flea.js"></script>
|
||||||
|
<script src="js/game/objects/spider.js"></script>
|
||||||
|
<script src="js/game/objects/scorpion.js"></script>
|
||||||
|
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
39
centipede/js/game/game.js
Normal file
39
centipede/js/game/game.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
const game = {
|
||||||
|
stopped: false,
|
||||||
|
width: document.getElementById('game-canvas').width,
|
||||||
|
height: document.getElementById('game-canvas').height,
|
||||||
|
level: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
game.resume = () => {
|
||||||
|
game.stopped = false;
|
||||||
|
game.lastTimeStamp = performance.now();
|
||||||
|
menu.reRegisterKeys();
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
}
|
||||||
|
|
||||||
|
game.resetObjects = () => {
|
||||||
|
game.player.x = game.width/2;
|
||||||
|
game.player.y = game.height/2;
|
||||||
|
game.bullets = [];
|
||||||
|
game.explosions = [];
|
||||||
|
game.mushroomDims = {width: 40, height: 40};
|
||||||
|
game.mushrooms = game.Mushroom.generateMushrooms(game.mushroomDims);
|
||||||
|
game.centipede = game.Centipede({segments: Math.min(game.level*5 + 5, 15), startX: game.width/2, startY: 0, rot: 180, width: 40, height: 40, dx: 0.2, dy: 0});
|
||||||
|
game.spiders = [];
|
||||||
|
game.fleas = [];
|
||||||
|
game.scorpions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
game.gameOver = () => {
|
||||||
|
menu.showMenu();
|
||||||
|
menu.setState('game-over');
|
||||||
|
menu.addScore(game.score);
|
||||||
|
|
||||||
|
menu.onHide = initialize;
|
||||||
|
}
|
||||||
|
|
||||||
|
game.getObjects = () => [game.player, ...game.bullets, ...game.mushrooms, ...game.spiders, ...game.fleas, ...game.scorpions, game.centipede, ...game.explosions];
|
||||||
|
game.getBulletCollidableObjects = () => [...game.mushrooms, ...game.spiders, ...game.fleas, ...game.scorpions, game.centipede];
|
||||||
|
game.getMushroomCollidableObjects = () => [game.player, ...game.scorpions, game.centipede];
|
||||||
|
game.getPlayerCollidableObjects = () => [...game.spiders, ...game.fleas, ...game.scorpions, game.centipede]
|
55
centipede/js/game/graphics.js
Normal file
55
centipede/js/game/graphics.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
game.graphics = (
|
||||||
|
(context) => {
|
||||||
|
context.imageSmoothingEnabled = false;
|
||||||
|
const clear = () => {
|
||||||
|
context.clearRect(0, 0, game.width, game.height);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Sprite = ({sheetSrc, spriteX, spriteY, spriteWidth, spriteHeight, timePerFrame, cols, rows, numFrames, drawFunction}) => {
|
||||||
|
timePerFrame = timePerFrame ?? 100;
|
||||||
|
numFrames = numFrames ?? 1;
|
||||||
|
cols = cols ?? numFrames;
|
||||||
|
rows = rows ?? 1;
|
||||||
|
|
||||||
|
let ready = false;
|
||||||
|
|
||||||
|
let image;
|
||||||
|
if (sheetSrc) {
|
||||||
|
image = new Image();
|
||||||
|
image.src = sheetSrc;
|
||||||
|
image.onload = () => { ready = true; };
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentFrame = 0;
|
||||||
|
let lastTime = performance.now();
|
||||||
|
|
||||||
|
let draw;
|
||||||
|
if (!drawFunction) {
|
||||||
|
draw = (_elapsedTime, {x, y, rot, width, height}) => {
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
if (numFrames > 1) {
|
||||||
|
if (performance.now()-lastTime > timePerFrame) {
|
||||||
|
lastTime = performance.now();
|
||||||
|
currentFrame = (currentFrame + 1) % numFrames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.save();
|
||||||
|
context.translate(x+width/2, y+height/2);
|
||||||
|
context.rotate(rot * Math.PI / 180);
|
||||||
|
context.translate(-x-width/2, -y-height/2);
|
||||||
|
const row = currentFrame % rows;
|
||||||
|
const col = Math.floor(currentFrame / rows);
|
||||||
|
context.drawImage(image, spriteX+col*spriteWidth, spriteY+row*spriteHeight, spriteWidth, spriteHeight, x, y, width, height);
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
draw = (elapsedTime, drawSpec) => drawFunction(elapsedTime, drawSpec, context);
|
||||||
|
}
|
||||||
|
return { draw, timePerFrame, numFrames };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { clear, Sprite };
|
||||||
|
}
|
||||||
|
)(document.getElementById("game-canvas").getContext("2d"));
|
32
centipede/js/game/input.js
Normal file
32
centipede/js/game/input.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
game.input = (() => {
|
||||||
|
"use strict";
|
||||||
|
const Keyboard = () => {
|
||||||
|
const keys = {};
|
||||||
|
const handlers = {};
|
||||||
|
const keyPress = (event) => {
|
||||||
|
keys[event.key] = event.timeStamp;
|
||||||
|
};
|
||||||
|
const keyRelease = (event) => {
|
||||||
|
delete keys[event.key];
|
||||||
|
};
|
||||||
|
const registerCommand = (key, handler) => {
|
||||||
|
handlers[key] = handler;
|
||||||
|
};
|
||||||
|
const unregisterCommand = (key) => {
|
||||||
|
delete handlers[key];
|
||||||
|
}
|
||||||
|
const update = (elapsedTime) => {
|
||||||
|
for (let key in keys) {
|
||||||
|
if (keys.hasOwnProperty(key)) {
|
||||||
|
if (handlers[key]) {
|
||||||
|
handlers[key](elapsedTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("keydown", keyPress);
|
||||||
|
window.addEventListener("keyup", keyRelease);
|
||||||
|
return {keys, handlers, registerCommand, unregisterCommand, update};
|
||||||
|
}
|
||||||
|
return { Keyboard };
|
||||||
|
})();
|
143
centipede/js/game/menu.js
Normal file
143
centipede/js/game/menu.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
const menu = {};
|
||||||
|
menu.initialize = () => {
|
||||||
|
menu.scores = localStorage.getItem("scores") ? JSON.parse(localStorage.getItem("scores")) : [];
|
||||||
|
menu.state = "main";
|
||||||
|
|
||||||
|
menu.controls = localStorage.getItem("controls") ? JSON.parse(localStorage.getItem("controls")) : {
|
||||||
|
"moveUp": "ArrowUp",
|
||||||
|
"moveDown": "ArrowDown",
|
||||||
|
"moveLeft": "ArrowLeft",
|
||||||
|
"moveRight": "ArrowRight",
|
||||||
|
"shoot": " ",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.setState = (state) => {
|
||||||
|
menu.state = state;
|
||||||
|
menu.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.escapeEventListener = (e) => {
|
||||||
|
if (e.key == "Escape") {
|
||||||
|
menu.setState('main');
|
||||||
|
menu.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.showMenu = () => {
|
||||||
|
menu.draw();
|
||||||
|
game.stopped = true;
|
||||||
|
window.addEventListener("keydown", menu.escapeEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.reRegisterKeys = () => {
|
||||||
|
Object.keys(game.keyboard.handlers).map(key => game.keyboard.unregisterCommand(key));
|
||||||
|
game.keyboard.registerCommand(menu.controls.moveUp, game.player.moveUp);
|
||||||
|
game.keyboard.registerCommand(menu.controls.moveDown, game.player.moveDown);
|
||||||
|
game.keyboard.registerCommand(menu.controls.moveLeft, game.player.moveLeft);
|
||||||
|
game.keyboard.registerCommand(menu.controls.moveRight, game.player.moveRight);
|
||||||
|
game.keyboard.registerCommand(menu.controls.shoot, game.player.shoot);
|
||||||
|
game.keyboard.registerCommand("Escape", menu.showMenu);
|
||||||
|
localStorage.setItem("controls", JSON.stringify(menu.controls));
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.addScore = (score) => {
|
||||||
|
menu.scores.push(score);
|
||||||
|
menu.scores.sort((a, b) => b - a);
|
||||||
|
localStorage.setItem("scores", JSON.stringify(menu.scores));
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.hide = () => {
|
||||||
|
const menuElement = document.getElementById("menu");
|
||||||
|
menuElement.style.display = "none";
|
||||||
|
menu.reRegisterKeys();
|
||||||
|
window.removeEventListener("keydown", menu.escapeEventListener);
|
||||||
|
if (menu.onHide) {
|
||||||
|
menu.onHide();
|
||||||
|
menu.onHide = null;
|
||||||
|
}
|
||||||
|
game.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.listenFor = (action, elementId) => {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
element.innerHTML = "Listening...";
|
||||||
|
const handleKey = (event) => {
|
||||||
|
window.removeEventListener("keydown", handleKey);
|
||||||
|
if (event.key == "Escape") {
|
||||||
|
element.innerHTML = menu.controls[action];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
menu.controls[action] = event.key;
|
||||||
|
element.innerHTML = event.key;
|
||||||
|
}
|
||||||
|
window.addEventListener("keydown", handleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.draw = () => {
|
||||||
|
const menuElement = document.getElementById("menu");
|
||||||
|
menuElement.style.display = "block";
|
||||||
|
menuElement.innerHTML = `<h1>Centipede</h1>`;
|
||||||
|
if (menu.state == "main") {
|
||||||
|
menuElement.innerHTML += `
|
||||||
|
<div class='menu-button' onclick='menu.setState("controls")'>Change Controls</div>
|
||||||
|
<div class='menu-button' onclick='menu.setState("credits")'>Credits</div>
|
||||||
|
<div class='menu-button' onclick='menu.setState("scores")'>High Scores</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
else if (menu.state == "controls") {
|
||||||
|
menuElement.innerHTML += `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Move left: <button id="moveLeft" onfocus='menu.listenFor("moveLeft", "moveLeft")'>${menu.controls.moveLeft}</button>
|
||||||
|
<br>
|
||||||
|
Move right: <button id="moveRight" onfocus='menu.listenFor("moveRight", "moveRight")'>${menu.controls.moveRight}</button>
|
||||||
|
<br>
|
||||||
|
Move up: <button id="moveUp" onfocus='menu.listenFor("moveUp", "moveUp")'>${menu.controls.moveUp}</button>
|
||||||
|
<br>
|
||||||
|
Move down: <button id="moveDown" onfocus='menu.listenFor("moveDown", "moveDown")'>${menu.controls.moveDown}</button>
|
||||||
|
<br>
|
||||||
|
Shoot: <button id="shoot" onfocus='menu.listenFor("shoot", "shoot")'>${menu.controls.shoot}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
} else if (menu.state == "credits") {
|
||||||
|
menuElement.innerHTML += `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Sounds from <a href="https://opengameart.org/content/laser-fire">dklon</a>
|
||||||
|
<br>
|
||||||
|
Sprites from <a href="https://www.pngkit.com/view/u2w7r5u2e6u2a9r5_general-sprites-centipede-arcade-game-sprites/">PNGKit</a>
|
||||||
|
<br>
|
||||||
|
Some code from <a href="https://www.usu.edu/directory/?person=56DB0BFCCAEECEC8D5">Dr. Mathias</a>
|
||||||
|
<br>
|
||||||
|
Developed by Logan Hunt
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
} else if (menu.state == "scores") {
|
||||||
|
menuElement.innerHTML += `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
${menu.scores.map((score, index) => `${index + 1}: ${score}<br>`).join("")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
} else if (menu.state == "game-over") {
|
||||||
|
menuElement.innerHTML += `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Game Over
|
||||||
|
<br>
|
||||||
|
Your final score was: ${game.score}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
menuElement.innerHTML += "<div class='menu-button' onclick='menu.hide()'>Resume Game</div>"
|
||||||
|
if (menu.state !== "main") {
|
||||||
|
menuElement.innerHTML += "<div class='menu-button' onclick='menu.setState(\"main\")'>Back</div>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.initialize();
|
51
centipede/js/game/object.js
Normal file
51
centipede/js/game/object.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
game.Object = (object) => {
|
||||||
|
object.dx = object.dx ?? 0;
|
||||||
|
object.dy = object.dy ?? 0;
|
||||||
|
object.rot = object.rot ?? 0;
|
||||||
|
object.drot = object.drot ?? 0;
|
||||||
|
object.alive = object.alive ?? true;
|
||||||
|
|
||||||
|
object.poisonedTimer = object.poisonedTimer ?? 4000;
|
||||||
|
object.poisoned = false;
|
||||||
|
object.elapsedPoisonedTimer = 0;
|
||||||
|
object.poison = () => {
|
||||||
|
object.poisoned = true;
|
||||||
|
object.elapsedPoisonedTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
object.intersects = (other) => {
|
||||||
|
if (object.x + object.width <= other.x) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (object.x >= other.x + other.width) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (object.y + object.height <= other.y) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (object.y >= other.y + other.height) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
if (object.poisoned && object.y >= game.height - object.height) {
|
||||||
|
object.elapsedPoisonedTimer += elapsedTime;
|
||||||
|
if (object.elapsedPoisonedTimer > object.poisonedTimer) {
|
||||||
|
object.poisoned = false;
|
||||||
|
object.elapsedPoisonedTimer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object.x += (object.poisoned ? 0 : object.dx)*elapsedTime;
|
||||||
|
object.y += (object.poisoned ? 0.2 : object.dy)*elapsedTime;
|
||||||
|
object.rot += object.drot*elapsedTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
object.draw = (elapsedTime) => {
|
||||||
|
object.sprite.draw(elapsedTime, object);
|
||||||
|
};
|
||||||
|
|
||||||
|
return object;
|
||||||
|
};
|
11
centipede/js/game/objects/bullet.js
Normal file
11
centipede/js/game/objects/bullet.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
game.Bullet = (spec) => {
|
||||||
|
const object = game.Object(spec);
|
||||||
|
const parentUpdate = object.update;
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
parentUpdate(elapsedTime);
|
||||||
|
if (object.y < 0) {
|
||||||
|
object.alive = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return object;
|
||||||
|
}
|
105
centipede/js/game/objects/centipede.js
Normal file
105
centipede/js/game/objects/centipede.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
game.CentipedePiece = (spec) => {
|
||||||
|
const object = game.Object(spec);
|
||||||
|
object.poisonedTimer = 1000;
|
||||||
|
const parentUpdate = object.update;
|
||||||
|
object.turningState = {
|
||||||
|
turning: false,
|
||||||
|
turnDirectionY: 1,
|
||||||
|
objectStateBeforeTurn: null,
|
||||||
|
};
|
||||||
|
object.turn = () => {
|
||||||
|
object.turningState.objectStateBeforeTurn = {dx: object.dx, dy: object.dy, x: object.x, y: object.y};
|
||||||
|
object.turningState.turning = true;
|
||||||
|
if (object.y >= game.height - object.height) {
|
||||||
|
object.turningState.turnDirectionY = -1;
|
||||||
|
}
|
||||||
|
if (object.y <= 0) {
|
||||||
|
object.turningState.turnDirectionY = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
if (object.poisoned) {
|
||||||
|
object.turningState.turning = false;
|
||||||
|
}
|
||||||
|
else if ((object.x+object.width > game.width || object.x < 0) && !object.turningState.turning) {
|
||||||
|
object.x = Math.min(Math.max(object.x, 0), game.width - object.width);
|
||||||
|
object.turn();
|
||||||
|
}
|
||||||
|
if (object.turningState.turning) {
|
||||||
|
object.dx = 0;
|
||||||
|
object.dy = Math.abs(object.turningState.objectStateBeforeTurn.dx) * object.turningState.turnDirectionY;
|
||||||
|
object.rot = object.dy > 0 ? -90 : 90;
|
||||||
|
if (Math.abs(object.turningState.objectStateBeforeTurn.y - object.y) >= object.height) {
|
||||||
|
object.y = object.turningState.objectStateBeforeTurn.y + object.height * object.turningState.turnDirectionY;
|
||||||
|
object.dx = -object.turningState.objectStateBeforeTurn.dx;
|
||||||
|
object.rot = object.dx > 0 ? 180 : 0;
|
||||||
|
object.dy = 0;
|
||||||
|
object.turningState.turning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parentUpdate(elapsedTime);
|
||||||
|
object.y = Math.min(Math.max(object.y, 0), game.height - object.height);
|
||||||
|
};
|
||||||
|
object.onMushroomCollision = (mushroom) => {
|
||||||
|
if (mushroom.poisoned && object.dy === 0) {
|
||||||
|
object.poison();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!object.turningState.turning) {
|
||||||
|
if (mushroom.x < object.x && object.dx > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object.turn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
game.Centipede = (spec) => {
|
||||||
|
const segments = [
|
||||||
|
...Array(spec.segments).fill(0).map((_, i) => game.CentipedePiece({...spec, x: spec.startX - spec.width*(i+1), y: spec.startY, sprite: game.sprites.centipedeBody})),
|
||||||
|
game.CentipedePiece({...spec, x: spec.startX, y: spec.startY, sprite: game.sprites.centipedeHead}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const update = (elapsedTime) => {
|
||||||
|
segments.map((segment) => segment.update(elapsedTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
const draw = (elapsedTime) => {
|
||||||
|
segments.map((segment) => segment.draw(elapsedTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
const intersects = (object) => {
|
||||||
|
return segments.filter((segment) => segment.intersects(object)).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBulletCollision = (bullet) => {
|
||||||
|
if (bullet.alive) {
|
||||||
|
const segment = segments.find((segment) => segment.intersects(bullet));
|
||||||
|
const segmentIndex = segments.indexOf(segment);
|
||||||
|
|
||||||
|
const {mushX, mushY} = game.Mushroom.toMushCoords(segment);
|
||||||
|
game.explosions.push(game.Explosion({...game.Mushroom.toGameCoords({mushX, mushY}), width: segment.width, height: segment.height, sprite: game.sprites.explosionSmall}));
|
||||||
|
if (!game.mushrooms.find((mushroom) => mushroom.mushX === mushX && mushroom.mushY === mushY)) {
|
||||||
|
game.mushrooms.push(game.Mushroom({mushX, mushY, ...game.mushroomDims}));
|
||||||
|
}
|
||||||
|
game.score += segment.sprite === game.sprites.centipedeHead ? 20 : 5;
|
||||||
|
game.sounds.enemy_hit.load();
|
||||||
|
game.sounds.enemy_hit.play();
|
||||||
|
segments.splice(segmentIndex, 1);
|
||||||
|
}
|
||||||
|
bullet.alive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMushroomCollision = (mushroom) => {
|
||||||
|
segments.find((segment) => segment.intersects(mushroom)).onMushroomCollision(mushroom);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPlayerCollision = (player) => {
|
||||||
|
player.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
const alive = () => segments.length ? true : false;
|
||||||
|
|
||||||
|
return {update, draw, segments, intersects, onBulletCollision, onMushroomCollision, onPlayerCollision, alive};
|
||||||
|
}
|
14
centipede/js/game/objects/explosion.js
Normal file
14
centipede/js/game/objects/explosion.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
game.Explosion = (spec) => {
|
||||||
|
const object = game.Object(spec);
|
||||||
|
let explosionTime = 0;
|
||||||
|
const parentUpdate = object.update;
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
parentUpdate(elapsedTime);
|
||||||
|
explosionTime += elapsedTime;
|
||||||
|
|
||||||
|
if (explosionTime > (object.sprite.numFrames * object.sprite.timePerFrame)) {
|
||||||
|
object.alive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
49
centipede/js/game/objects/flea.js
Normal file
49
centipede/js/game/objects/flea.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
game.Flea = (spec) => {
|
||||||
|
const object = game.Object(spec);
|
||||||
|
const parentUpdate = object.update;
|
||||||
|
object.mushroomCoords = game.Mushroom.toMushCoords(object);
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
const newMushroomCoords = game.Mushroom.toMushCoords(object);
|
||||||
|
if (newMushroomCoords.mushY !== object.mushroomCoords.mushY || newMushroomCoords.mushX !== object.mushroomCoords.mushX) {
|
||||||
|
if (Math.random() < Math.min(0.15 + 0.05*game.level, 0.7)) {
|
||||||
|
if (!game.mushrooms.find((mushroom) => mushroom.mushX === newMushroomCoords.mushX && mushroom.mushY === newMushroomCoords.mushY)) {
|
||||||
|
game.mushrooms.push(game.Mushroom({...newMushroomCoords, ...game.mushroomDims}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
object.mushroomCoords = newMushroomCoords;
|
||||||
|
}
|
||||||
|
parentUpdate(elapsedTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
object.onMushroomCollision = (mushroom) => {
|
||||||
|
if (mushroom.poisoned) {
|
||||||
|
mushroom.state = 0;
|
||||||
|
object.poison();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object.explode = () => {
|
||||||
|
game.explosions.push(game.Explosion({x: object.x, y: object.y, width: object.width, height: object.height, sprite: game.sprites.explosionSmall}));
|
||||||
|
game.sounds.enemy_hit.load();
|
||||||
|
game.sounds.enemy_hit.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onBulletCollision = (bullet) => {
|
||||||
|
game.score += 20;
|
||||||
|
object.alive = false;
|
||||||
|
object.explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onPlayerCollision = (player) => {
|
||||||
|
object.alive = false;
|
||||||
|
player.kill();
|
||||||
|
object.explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onBulletCollision = (bullet) => {
|
||||||
|
object.explode();
|
||||||
|
object.alive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
43
centipede/js/game/objects/mushroom.js
Normal file
43
centipede/js/game/objects/mushroom.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
game.Mushroom = (spec) => {
|
||||||
|
spec.state = spec.state ?? 4;
|
||||||
|
const {mushX, mushY} = spec;
|
||||||
|
const objectSpec = {...spec};
|
||||||
|
objectSpec.x = mushX * objectSpec.width;
|
||||||
|
objectSpec.y = mushY * objectSpec.height;
|
||||||
|
const object = {...spec, ...game.Object(objectSpec)};
|
||||||
|
object.onBulletCollision = (bullet) => {
|
||||||
|
if (bullet.alive) {
|
||||||
|
object.state--;
|
||||||
|
game.score += 5;
|
||||||
|
game.sounds.mushroom_hit.load();
|
||||||
|
game.sounds.mushroom_hit.play();
|
||||||
|
}
|
||||||
|
bullet.alive = false;
|
||||||
|
};
|
||||||
|
object.draw = (elapsedTime) => {
|
||||||
|
if (object.state) {
|
||||||
|
object.sprite = object.poisoned ? game.sprites.poisonMushrooms[object.state-1] : game.sprites.regularMushrooms[object.state-1];
|
||||||
|
object.sprite.draw(elapsedTime, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
};
|
||||||
|
|
||||||
|
game.Mushroom.toMushCoords = (coords) => {
|
||||||
|
return {mushX: Math.ceil(coords.x / game.mushroomDims.width), mushY: Math.ceil(coords.y / game.mushroomDims.height)};
|
||||||
|
}
|
||||||
|
|
||||||
|
game.Mushroom.toGameCoords = (mushCoords) => {
|
||||||
|
return {x: mushCoords.mushX * game.mushroomDims.width, y: mushCoords.mushY * game.mushroomDims.height};
|
||||||
|
}
|
||||||
|
|
||||||
|
game.Mushroom.generateMushrooms = (mushroomSpec) => {
|
||||||
|
const mushPositions = new Set();
|
||||||
|
for (let i = 0; i < Math.max(Math.random(), 0.05) * game.height / mushroomSpec.height * game.width / mushroomSpec.width * game.level * 0.5; i++) {
|
||||||
|
mushPositions.add(JSON.stringify([Math.floor(Math.random() * game.width / mushroomSpec.width), Math.max(1, Math.floor(Math.random() * (game.height / mushroomSpec.height - 3)))]));
|
||||||
|
}
|
||||||
|
return Array.from(mushPositions).map((pos) => {
|
||||||
|
const [mushX, mushY] = JSON.parse(pos);
|
||||||
|
return game.Mushroom({...mushroomSpec, mushX, mushY});
|
||||||
|
});
|
||||||
|
}
|
63
centipede/js/game/objects/player.js
Normal file
63
centipede/js/game/objects/player.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
game.Player = (spec) => {
|
||||||
|
const object = game.Object(spec);
|
||||||
|
object.poisonedTimer = 4000;
|
||||||
|
object.elapsedPoisonedTimer = 0;
|
||||||
|
object.poisoned = false;
|
||||||
|
object.bulletTimer = spec.bulletTimer ?? 150;
|
||||||
|
object.maxPlayerHeight = spec.maxPlayerHeight ?? game.height - object.height*6;
|
||||||
|
object.elapsedBulletTimer = 0;
|
||||||
|
object.lives = spec.lives ?? 3;
|
||||||
|
|
||||||
|
const parentUpdate = object.update;
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
parentUpdate(elapsedTime);
|
||||||
|
object.x = Math.max(0, Math.min(object.x, game.width - object.width));
|
||||||
|
object.y = Math.max(object.maxPlayerHeight, Math.min(object.y, game.height - object.height));
|
||||||
|
object.dx = object.dy = 0;
|
||||||
|
object.elapsedBulletTimer += elapsedTime;
|
||||||
|
};
|
||||||
|
object.moveUp = () => {
|
||||||
|
object.dy = -0.75;
|
||||||
|
}
|
||||||
|
object.moveDown = () => {
|
||||||
|
object.dy = 0.75;
|
||||||
|
}
|
||||||
|
object.moveLeft = () => {
|
||||||
|
object.dx = -0.5;
|
||||||
|
}
|
||||||
|
object.moveRight = () => {
|
||||||
|
object.dx = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
object.shoot = () => {
|
||||||
|
if (object.elapsedBulletTimer > object.bulletTimer) {
|
||||||
|
object.elapsedBulletTimer = 0;
|
||||||
|
game.bullets.push(game.Bullet({x: object.x + object.width/2 - 5, y: object.y-object.height/2, dx: 0, dy: -1.5, width: 5, height: 50, sprite: game.sprites.bullet}));
|
||||||
|
game.sounds.laser.load();
|
||||||
|
game.sounds.laser.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onMushroomCollision = (mushroom) => {
|
||||||
|
if (mushroom.poisoned) {
|
||||||
|
mushroom.state = 0;
|
||||||
|
object.poison();
|
||||||
|
}
|
||||||
|
if (mushroom.x > object.x) {
|
||||||
|
object.x = mushroom.x - mushroom.width;
|
||||||
|
}
|
||||||
|
if (mushroom.x < object.x) {
|
||||||
|
object.x = mushroom.x + mushroom.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object.kill = () => {
|
||||||
|
object.lives--;
|
||||||
|
game.resetObjects();
|
||||||
|
if (object.lives == 0) {
|
||||||
|
game.gameOver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
32
centipede/js/game/objects/scorpion.js
Normal file
32
centipede/js/game/objects/scorpion.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
game.Scorpion = (spec) => {
|
||||||
|
const object = game.Object(spec);
|
||||||
|
|
||||||
|
const parentUpdate = object.update;
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
parentUpdate(elapsedTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
object.explode = () => {
|
||||||
|
game.explosions.push(game.Explosion({x: object.x, y: object.y, width: object.width, height: object.height, sprite: game.sprites.explosionBig}));
|
||||||
|
game.sounds.enemy_hit.load();
|
||||||
|
game.sounds.enemy_hit.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onBulletCollision = (bullet) => {
|
||||||
|
game.score += 100;
|
||||||
|
object.alive = false;
|
||||||
|
object.explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onPlayerCollision = (player) => {
|
||||||
|
object.alive = false;
|
||||||
|
player.kill();
|
||||||
|
object.explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onMushroomCollision = (mushroom) => {
|
||||||
|
mushroom.poisoned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
45
centipede/js/game/objects/spider.js
Normal file
45
centipede/js/game/objects/spider.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
game.Spider = (spec) => {
|
||||||
|
const object = game.Object(spec);
|
||||||
|
|
||||||
|
const parentUpdate = object.update;
|
||||||
|
|
||||||
|
object.randomizeVel = () => {
|
||||||
|
object.dx = Math.min(Math.random(), 0.25 + 0.05*game.level) * (Math.random() > 0.5 ? 1 : -1);
|
||||||
|
object.dy = Math.min(Math.random(), 0.25 + 0.05*game.level) * (Math.random() > 0.5 ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
object.update = (elapsedTime) => {
|
||||||
|
if (Math.random() < 0.01*game.level) {
|
||||||
|
object.randomizeVel();
|
||||||
|
}
|
||||||
|
if (object.x < 0 || object.x > game.width - object.width) {
|
||||||
|
object.dx = -object.dx;
|
||||||
|
}
|
||||||
|
if (object.y < 0 || object.y > game.height - object.height) {
|
||||||
|
object.dy = -object.dy;
|
||||||
|
}
|
||||||
|
object.x = Math.max(0, Math.min(game.width - object.width, object.x));
|
||||||
|
object.y = Math.max(0, Math.min(game.height - object.height, object.y));
|
||||||
|
parentUpdate(elapsedTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
object.explode = () => {
|
||||||
|
game.explosions.push(game.Explosion({x: object.x, y: object.y, width: object.width, height: object.height, sprite: game.sprites.explosionBig}));
|
||||||
|
game.sounds.enemy_hit.load();
|
||||||
|
game.sounds.enemy_hit.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onBulletCollision = (bullet) => {
|
||||||
|
game.score += 150;
|
||||||
|
object.alive = false;
|
||||||
|
object.explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
object.onPlayerCollision = (player) => {
|
||||||
|
object.alive = false;
|
||||||
|
player.kill();
|
||||||
|
object.explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
5
centipede/js/game/sounds.js
Normal file
5
centipede/js/game/sounds.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
game.sounds = {
|
||||||
|
mushroom_hit: new Audio("assets/sounds/mushroom_hit.wav"),
|
||||||
|
enemy_hit: new Audio("assets/sounds/enemy_hit.wav"),
|
||||||
|
laser: new Audio("assets/sounds/laser.wav"),
|
||||||
|
}
|
120
centipede/js/game/sprites.js
Normal file
120
centipede/js/game/sprites.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
game.sprites = {
|
||||||
|
centipedeHead: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 0,
|
||||||
|
spriteY: 0,
|
||||||
|
spriteWidth: 40,
|
||||||
|
spriteHeight: 40,
|
||||||
|
numFrames: 4,
|
||||||
|
timePerFrame: 100,
|
||||||
|
}),
|
||||||
|
centipedeBody: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 0,
|
||||||
|
spriteY: 80,
|
||||||
|
spriteWidth: 40,
|
||||||
|
spriteHeight: 40,
|
||||||
|
numFrames: 4,
|
||||||
|
timePerFrame: 100,
|
||||||
|
}),
|
||||||
|
spider: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 0,
|
||||||
|
spriteY: 160,
|
||||||
|
spriteWidth: 80,
|
||||||
|
spriteHeight: 40,
|
||||||
|
numFrames: 8,
|
||||||
|
timePerFrame: 100,
|
||||||
|
cols: 4,
|
||||||
|
rows: 2,
|
||||||
|
}),
|
||||||
|
flea: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 320,
|
||||||
|
spriteY: 160,
|
||||||
|
spriteWidth: 45,
|
||||||
|
spriteHeight: 40,
|
||||||
|
numFrames: 4,
|
||||||
|
timePerFrame: 500,
|
||||||
|
cols: 2,
|
||||||
|
rows: 2,
|
||||||
|
}),
|
||||||
|
scorpion: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 0,
|
||||||
|
spriteY: 280,
|
||||||
|
spriteWidth: 80,
|
||||||
|
spriteHeight: 40,
|
||||||
|
numFrames: 4,
|
||||||
|
timePerFrame: 500,
|
||||||
|
cols: 4,
|
||||||
|
}),
|
||||||
|
ship: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 0,
|
||||||
|
spriteY: 400,
|
||||||
|
spriteWidth: 40,
|
||||||
|
spriteHeight: 40,
|
||||||
|
numFrames: 1,
|
||||||
|
timePerFrame: 0,
|
||||||
|
cols: 1,
|
||||||
|
rows: 1
|
||||||
|
}),
|
||||||
|
bullet: game.graphics.Sprite({
|
||||||
|
drawFunction: (_elapsedTime, {x, y, rot, width, height}, context) => {
|
||||||
|
context.save();
|
||||||
|
context.translate(x+width/2, y+height/2);
|
||||||
|
context.rotate(rot * Math.PI / 180);
|
||||||
|
context.translate(-x-width/2, -y-height/2);
|
||||||
|
const fillStyle = context.fillStyle;
|
||||||
|
context.fillStyle = "#FF0000";
|
||||||
|
context.fillRect(x, y, width, height);
|
||||||
|
context.fillStyle = fillStyle;
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
explosionBig: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 0,
|
||||||
|
spriteY: 320,
|
||||||
|
numFrames: 8,
|
||||||
|
spriteWidth: 80,
|
||||||
|
spriteHeight: 40,
|
||||||
|
cols: 4,
|
||||||
|
rows: 2,
|
||||||
|
timePerFrame: 30,
|
||||||
|
}),
|
||||||
|
explosionSmall: game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 320,
|
||||||
|
spriteY: 320,
|
||||||
|
numFrames: 6,
|
||||||
|
spriteWidth: 40,
|
||||||
|
spriteHeight: 40,
|
||||||
|
cols: 3,
|
||||||
|
rows: 2,
|
||||||
|
timePerFrame: 30,
|
||||||
|
}),
|
||||||
|
regularMushrooms: [3,2,1,0].map(i =>
|
||||||
|
game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 320 + i*40,
|
||||||
|
spriteY: 0,
|
||||||
|
numFrames: 1,
|
||||||
|
spriteWidth: 40,
|
||||||
|
spriteHeight: 40,
|
||||||
|
timePerFrame: 0,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
poisonMushrooms: [3,2,1,0].map(i =>
|
||||||
|
game.graphics.Sprite({
|
||||||
|
sheetSrc: "assets/images/centipede-assets.png",
|
||||||
|
spriteX: 320 + i*40,
|
||||||
|
spriteY: 40,
|
||||||
|
numFrames: 1,
|
||||||
|
spriteWidth: 40,
|
||||||
|
spriteHeight: 40,
|
||||||
|
timePerFrame: 0,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
75
centipede/js/main.js
Normal file
75
centipede/js/main.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
let handleInput;
|
||||||
|
const initialize = () => {
|
||||||
|
game.level = 1;
|
||||||
|
game.score = 0;
|
||||||
|
game.totalTime = 0;
|
||||||
|
|
||||||
|
game.keyboard = game.input.Keyboard();
|
||||||
|
handleInput = game.keyboard.update;
|
||||||
|
|
||||||
|
game.lastTimeStamp = performance.now();
|
||||||
|
|
||||||
|
game.player = game.Player({x: game.width/2 - 20, y: game.height-40, width: 40, height: 40, sprite: game.sprites.ship});
|
||||||
|
|
||||||
|
game.resetObjects();
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = (elapsedTime) => {
|
||||||
|
game.totalTime += elapsedTime;
|
||||||
|
|
||||||
|
game.mushrooms.map((mushroom) => game.getMushroomCollidableObjects().filter((object) => object.intersects(mushroom))).map((objects, i) => {
|
||||||
|
objects.map((object => object.onMushroomCollision ? object.onMushroomCollision(game.mushrooms[i]) : null));
|
||||||
|
});
|
||||||
|
game.bullets.map((bullet) => game.getBulletCollidableObjects().filter((object) => object.intersects(bullet))).map((objects, i) => {
|
||||||
|
objects.map((object) => object.onBulletCollision ? object.onBulletCollision(game.bullets[i]) : null)
|
||||||
|
})
|
||||||
|
game.getPlayerCollidableObjects().map((object) => object.intersects(game.player) ? object.onPlayerCollision(game.player) : null);
|
||||||
|
|
||||||
|
game.bullets = game.bullets.filter((bullet) => bullet.alive);
|
||||||
|
game.spiders = game.spiders.filter((spider) => spider.alive);
|
||||||
|
game.explosions = game.explosions.filter((explosion) => explosion.alive);
|
||||||
|
game.mushrooms = game.mushrooms.filter((mushroom) => mushroom.state > 0);
|
||||||
|
game.scorpions = game.scorpions.filter((scorpion) => scorpion.x >= 0 && scorpion.x <= game.width - scorpion.width && scorpion.alive);
|
||||||
|
game.fleas = game.fleas.filter((flea) => flea.y < game.height - flea.height && flea.alive);
|
||||||
|
game.getObjects().map((object) => object.update(elapsedTime));
|
||||||
|
|
||||||
|
if (Math.random() < 0.002 * game.level && game.spiders.length < game.level) {
|
||||||
|
game.spiders.push(game.Spider({x: game.width/2, y: 0, width: 80, height: 40, rot: 0, dx: -0.2, dy: 0, sprite: game.sprites.spider}));
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.001 * game.level && game.fleas.length < game.level) {
|
||||||
|
game.fleas.push(game.Flea({x: Math.floor(Math.random() * game.width / game.mushroomDims.width) * game.mushroomDims.width, y: 0, width: 40, height: 40, rot: 0, dx: 0, dy: 0.2, sprite: game.sprites.flea}));
|
||||||
|
}
|
||||||
|
if (Math.random() < 0.001 * game.level && game.scorpions.length < game.level) {
|
||||||
|
game.scorpions.push(game.Scorpion({y: Math.floor(Math.random() * game.height / game.mushroomDims.height) * game.mushroomDims.height, x: 0, width: 80, height: 40, rot: 0, dx: 0.2, dy: 0, sprite: game.sprites.scorpion}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!game.centipede.alive()) {
|
||||||
|
game.resetObjects();
|
||||||
|
game.level++;
|
||||||
|
game.score += game.level*400;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const render = (elapsedTime) => {
|
||||||
|
game.graphics.clear();
|
||||||
|
game.getObjects().map((object) => object.draw(elapsedTime));
|
||||||
|
|
||||||
|
document.getElementById("hud").innerHTML = `Level: ${game.level} Lives: ${game.player.lives} Score: ${game.score} ${game.player.poisoned ? "<span style='color:green'>POISONED<span>" : ""}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const gameLoop = (time) => {
|
||||||
|
const elapsedTime = time - game.lastTimeStamp;
|
||||||
|
game.lastTimeStamp = time;
|
||||||
|
|
||||||
|
handleInput(elapsedTime);
|
||||||
|
update(elapsedTime);
|
||||||
|
render(elapsedTime);
|
||||||
|
|
||||||
|
if (!game.stopped) {
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
menu.reRegisterKeys();
|
||||||
|
requestAnimationFrame(gameLoop);
|
1
maize-maze/.gitignore
vendored
Normal file
1
maize-maze/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
21
maize-maze/LICENSE.md
Normal file
21
maize-maze/LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
Copyright (c) 2012-2022 Scott Chacon and others
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
9
maize-maze/README.md
Normal file
9
maize-maze/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# A Simple Maze Game
|
||||||
|
|
||||||
|
Controls:
|
||||||
|
+ Arrow-keys / WASD / IJKL
|
||||||
|
+ h to toggle hint
|
||||||
|
+ p to toggle path
|
||||||
|
+ b to toggle breadcrumbs
|
||||||
|
|
||||||
|
![Screenshot](./screenshot.png)
|
24
maize-maze/css/styles.css
Normal file
24
maize-maze/css/styles.css
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: scroll;
|
||||||
|
/* Font from https://www.cssfontstack.com/Lucida-Console */
|
||||||
|
font-family: Lucida Console,Lucida Sans Typewriter,monaco,Bitstream Vera Sans Mono,monospace;
|
||||||
|
background: radial-gradient(circle, transparent 20%, black 80vw), white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-holder canvas {
|
||||||
|
padding: 0;
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
height: 70vh;
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
BIN
maize-maze/images/corn.png
Normal file
BIN
maize-maze/images/corn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
maize-maze/images/fire.png
Normal file
BIN
maize-maze/images/fire.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
BIN
maize-maze/images/popcorn.jpg
Normal file
BIN
maize-maze/images/popcorn.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
75
maize-maze/index.html
Normal file
75
maize-maze/index.html
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Amazeing Maize Maze</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||||
|
<link href="css/styles.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="modal fade" id="help-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLabel">Controls</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Move with arrow keys, WASD, or IJKL
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
h to toggle a hint
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
p to toggle the shortest path
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
b to toggle breadcrumbs
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
<h1>The A-maze-ing Maize Maze!</h1>
|
||||||
|
<p>Get to the fire to make popcorn! Current Time: <span style="color: green; font-size:24px" id="elapsed-time"></span> seconds</p>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="button" data-toggle="modal" data-target="#help-modal">
|
||||||
|
Show me the controls!
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a onclick="initialize(5)" class="button">5x5</a>
|
||||||
|
<a onclick="initialize(10)" class="button">10x10</a>
|
||||||
|
<a onclick="initialize(15)" class="button">15x15</a>
|
||||||
|
<a onclick="initialize(20)" class="button">20x20</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="canvas-holder">
|
||||||
|
<canvas id="canvas" width="1000" height="1000"></canvas>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<br>
|
||||||
|
"Low" scores (player path length - shortest path length):
|
||||||
|
<div id="scores" style="max-height: 5vh; overflow-y: scroll"></div>
|
||||||
|
</div>
|
||||||
|
<script src="js/assets.js"></script>
|
||||||
|
<script src="js/helpers.js"></script>
|
||||||
|
<script src="js/json-ds.js"></script>
|
||||||
|
<script src="js/keyboard.js"></script>
|
||||||
|
<script src="js/maze.js"></script>
|
||||||
|
<script src="js/game.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
8
maize-maze/js/assets.js
Normal file
8
maize-maze/js/assets.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const GOAL = new Image();
|
||||||
|
GOAL.src = "./images/fire.png";
|
||||||
|
|
||||||
|
const PLAYER = new Image();
|
||||||
|
PLAYER.src = "./images/corn.png";
|
||||||
|
|
||||||
|
const BACKGROUND = new Image();
|
||||||
|
BACKGROUND.src = "./images/popcorn.jpg";
|
249
maize-maze/js/game.js
Normal file
249
maize-maze/js/game.js
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
let canvas,context;
|
||||||
|
|
||||||
|
const HEIGHT = 1000;
|
||||||
|
const WIDTH = 1000;
|
||||||
|
|
||||||
|
// Next assignment: don't use so much effing global data
|
||||||
|
let n = 10;
|
||||||
|
let maze;
|
||||||
|
|
||||||
|
let total_time;
|
||||||
|
|
||||||
|
let player_pos = [];
|
||||||
|
let goal_pos = [];
|
||||||
|
|
||||||
|
let myInput = input.Keyboard();
|
||||||
|
|
||||||
|
let show_player_path = false;
|
||||||
|
let player_path = [];
|
||||||
|
let show_next_move = false;
|
||||||
|
let show_shortest_path = false;
|
||||||
|
let shortest_path = [];
|
||||||
|
|
||||||
|
let do_score_update = false;
|
||||||
|
let scores = [];
|
||||||
|
let initial_shortest_path_length = 0;
|
||||||
|
let current_score;
|
||||||
|
|
||||||
|
let do_time_update = false;
|
||||||
|
let dom_time;
|
||||||
|
let elapsed_time;
|
||||||
|
let last_time;
|
||||||
|
|
||||||
|
function render_maze(maze, n, dx, dy) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
context.beginPath();
|
||||||
|
if (maze[i][j].left) {
|
||||||
|
context.moveTo(j * dx, i * dy);
|
||||||
|
context.lineTo(j * dx, (i + 1) * dy);
|
||||||
|
}
|
||||||
|
if (maze[i][j].right) {
|
||||||
|
context.moveTo((j + 1) * dx, i * dy);
|
||||||
|
context.lineTo((j + 1) * dx, (i + 1) * dy);
|
||||||
|
}
|
||||||
|
if (maze[i][j].top) {
|
||||||
|
context.moveTo(j * dx, i * dy);
|
||||||
|
context.lineTo((j + 1) * dx, i * dy);
|
||||||
|
}
|
||||||
|
if (maze[i][j].bottom) {
|
||||||
|
context.moveTo(j * dx, (i + 1) * dy);
|
||||||
|
context.lineTo((j + 1) * dx, (i + 1) * dy);
|
||||||
|
}
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_player(x, y, dx, dy) {
|
||||||
|
context.drawImage(PLAYER, x*dx, y*dy, dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_scores(scores) {
|
||||||
|
let html = scores.sort((a,b) => a-b).map((x,i) => `${i+1}: ${x}`).join('<br>');
|
||||||
|
document.getElementById('scores').innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_time(total_time) {
|
||||||
|
document.getElementById('elapsed-time').innerHTML = total_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_goal(x, y, dx, dy) {
|
||||||
|
context.drawImage(GOAL, x*dx, y*dy, dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_background(n, dx, dy) {
|
||||||
|
for (let x = 0; x < WIDTH; x += WIDTH/n) {
|
||||||
|
for (let y = 0; y < HEIGHT; y += HEIGHT/n) {
|
||||||
|
context.drawImage(BACKGROUND, x, y, dx, dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_circle(x,y,r,color) {
|
||||||
|
context.beginPath();
|
||||||
|
context.fillStyle = color;
|
||||||
|
context.arc(x, y, r, 0, 2*Math.PI);
|
||||||
|
context.fill();
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_path(path, dx, dy, color) {
|
||||||
|
path.map((coord) => {
|
||||||
|
render_circle(coord[0]*dx + dx/2, coord[1]*dy + dy/2, Math.min(dx/4, dy/4), color);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(elapsed) {
|
||||||
|
const dx = WIDTH / n;
|
||||||
|
const dy = HEIGHT / n;
|
||||||
|
|
||||||
|
context.fillStyle = 'rgba(255,255,255,255)';
|
||||||
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
context.rect(0, 0, canvas.width, canvas.height);
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
render_background(n, dx, dy);
|
||||||
|
render_maze(maze, n, dx, dy);
|
||||||
|
|
||||||
|
if (show_player_path) {
|
||||||
|
render_path(player_path, dx, dy, 'rgba(255, 0, 0, 255)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_shortest_path) {
|
||||||
|
render_path(shortest_path, dx, dy, 'rgba(255, 255, 0, 255)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_next_move && shortest_path.length>1) {
|
||||||
|
render_path([shortest_path[1]], dx, dy, 'rgba(255, 255, 0, 255)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_score_update) {
|
||||||
|
render_scores(scores);
|
||||||
|
}
|
||||||
|
if (do_time_update) {
|
||||||
|
render_time(dom_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_player(Math.floor(player_pos[0]), Math.floor(player_pos[1]), dx, dy);
|
||||||
|
render_goal(goal_pos[0], goal_pos[1], dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_actions_down = {};
|
||||||
|
let key_actions = {
|
||||||
|
"move_up": ['ArrowUp', 'i', 'w'],
|
||||||
|
"move_right": ['ArrowRight', 'l', 'd'],
|
||||||
|
"move_down": ['ArrowDown', 'k', 's'],
|
||||||
|
"move_left": ['ArrowLeft', 'j', 'a'],
|
||||||
|
"breadcrumbs": ['b'],
|
||||||
|
"shortest_path": ['p'],
|
||||||
|
"hint": ['h']
|
||||||
|
};
|
||||||
|
function handle_input(input) {
|
||||||
|
if (input) {
|
||||||
|
if (any(key_actions['move_up'].map((x) => input.keys[x])) && !key_actions_down['move_up'] && player_pos[1] > 0) {
|
||||||
|
key_actions_down['move_up'] = true;
|
||||||
|
if (!maze[player_pos[1]][player_pos[0]].top) {
|
||||||
|
player_pos[1] -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any(key_actions['move_right'].map((x) => input.keys[x])) && !key_actions_down['move_right'] && player_pos[0] < n-1) {
|
||||||
|
key_actions_down['move_right'] = true;
|
||||||
|
if (!maze[player_pos[1]][player_pos[0]].right) {
|
||||||
|
player_pos[0] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any(key_actions['move_down'].map((x) => input.keys[x])) && !key_actions_down['move_down'] && player_pos[1] < n-1) {
|
||||||
|
key_actions_down['move_down'] = true;
|
||||||
|
if (!maze[player_pos[1]][player_pos[0]].bottom) {
|
||||||
|
player_pos[1] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any(key_actions['move_left'].map((x) => input.keys[x])) && !key_actions_down['move_left'] && player_pos[0] > 0) {
|
||||||
|
key_actions_down['move_left'] = true;
|
||||||
|
if (!maze[player_pos[1]][player_pos[0]].left) {
|
||||||
|
player_pos[0] -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (input.keys['b'] && !key_actions_down['breadcrumbs']) {
|
||||||
|
key_actions_down['breadcrumbs'] = true;
|
||||||
|
show_player_path = !show_player_path;
|
||||||
|
}
|
||||||
|
if (input.keys['p'] && !key_actions_down['shortest_path']) {
|
||||||
|
key_actions_down['shortest_path'] = true;
|
||||||
|
show_shortest_path = !show_shortest_path;
|
||||||
|
}
|
||||||
|
if (input.keys['h'] && !key_actions_down['hint']) {
|
||||||
|
key_actions_down['hint'] = true;
|
||||||
|
show_next_move = !show_next_move;
|
||||||
|
}
|
||||||
|
Object.keys(key_actions).map((x) => {
|
||||||
|
if (key_actions_down[x] && !any(key_actions[x].map((y) => input.keys[y]))) {
|
||||||
|
key_actions_down[x] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(elapsed) {
|
||||||
|
total_time += elapsed;
|
||||||
|
|
||||||
|
if (do_score_update) {
|
||||||
|
do_score_update = false;
|
||||||
|
}
|
||||||
|
if (do_time_update) {
|
||||||
|
do_time_update = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON.stringify(player_pos) !== JSON.stringify(player_path[player_path.length-1])) {
|
||||||
|
player_path.push([player_pos[0], player_pos[1]]);
|
||||||
|
shortest_path = solve_maze(maze, player_pos[0], player_pos[1], goal_pos[0], goal_pos[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_time / 1000 > dom_time) {
|
||||||
|
dom_time = Math.floor(total_time/1000);
|
||||||
|
}
|
||||||
|
do_time_update = true;
|
||||||
|
|
||||||
|
if (player_pos[0] == goal_pos[0] && player_pos[1] == goal_pos[1]) {
|
||||||
|
current_score = player_path.length - initial_shortest_path_length;
|
||||||
|
scores.push(current_score);
|
||||||
|
initialize();
|
||||||
|
do_score_update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize(new_n) {
|
||||||
|
if (new_n) {
|
||||||
|
n = new_n;
|
||||||
|
}
|
||||||
|
maze = generate_maze(n);
|
||||||
|
player_pos = [random_in_range(0, 2), random_in_range(0, 2)];
|
||||||
|
goal_pos = [random_in_range(n-2, n), random_in_range(n-2, n)];
|
||||||
|
player_path = [];
|
||||||
|
shortest_path = solve_maze(maze, player_pos[0], player_pos[1], goal_pos[0], goal_pos[1]);
|
||||||
|
initial_shortest_path_length = shortest_path.length;
|
||||||
|
total_time = 0;
|
||||||
|
current_score = 0;
|
||||||
|
dom_time = 0;
|
||||||
|
last_time = performance.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
function game_loop(time_stamp) {
|
||||||
|
elapsed_time = time_stamp - last_time;
|
||||||
|
last_time = time_stamp;
|
||||||
|
handle_input(myInput);
|
||||||
|
update(elapsed_time);
|
||||||
|
render(elapsed_time);
|
||||||
|
|
||||||
|
// Wow! Tail call recursion! /sarcasm
|
||||||
|
requestAnimationFrame(game_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
initialize();
|
||||||
|
canvas = document.getElementById('canvas');
|
||||||
|
context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
game_loop(performance.now());
|
||||||
|
}
|
18
maize-maze/js/helpers.js
Normal file
18
maize-maze/js/helpers.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
function random_in_range(a,z) {
|
||||||
|
return Math.floor(Math.random() * (z - a) + a);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffle_array(a) {
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
let j = random_in_range(0, i+1);
|
||||||
|
temp = a[i]; a[i] = a[j]; a[j] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sub(p1, p2) {
|
||||||
|
return [p1[0] - p2[0], p1[1] - p2[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function any(l) {
|
||||||
|
return l.filter((x) => x).length > 0;
|
||||||
|
}
|
30
maize-maze/js/json-ds.js
Normal file
30
maize-maze/js/json-ds.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// If I were to rewrite this, I would use IEFE's - Dean was right about OO in JS
|
||||||
|
class JSONSet {
|
||||||
|
items = new Set();
|
||||||
|
constructor(initial){
|
||||||
|
if (initial) {
|
||||||
|
this.apply_set_function('add', initial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apply_set_function(f_name, x) {
|
||||||
|
return this.items[f_name](JSON.stringify(x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JSONHash {
|
||||||
|
items = {};
|
||||||
|
constructor(initial_key, initial_value){
|
||||||
|
if (initial_key && initial_value) {
|
||||||
|
this.items[JSON.stringify(initial)] = initial_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_value(key, value) {
|
||||||
|
this.items[JSON.stringify(key)] = value;
|
||||||
|
}
|
||||||
|
get_value(key) {
|
||||||
|
return this.items[JSON.stringify(key)];
|
||||||
|
}
|
||||||
|
delete_value(key) {
|
||||||
|
delete this.items[JSON.stringify(key)];
|
||||||
|
}
|
||||||
|
}
|
22
maize-maze/js/keyboard.js
Normal file
22
maize-maze/js/keyboard.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Shameless stolen code from "Process the Input" presentation
|
||||||
|
let input = (function() {
|
||||||
|
function Keyboard() {
|
||||||
|
let that = {
|
||||||
|
keys : {}
|
||||||
|
};
|
||||||
|
function keyPress(e) {
|
||||||
|
that.keys[e.key] = e.timeStamp;
|
||||||
|
}
|
||||||
|
function keyRelease(e) {
|
||||||
|
delete that.keys[e.key];
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', keyPress);
|
||||||
|
window.addEventListener('keyup', keyRelease);
|
||||||
|
|
||||||
|
return that;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
Keyboard : Keyboard
|
||||||
|
};
|
||||||
|
}());
|
104
maize-maze/js/maze.js
Normal file
104
maize-maze/js/maze.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
function get_neighbors(maze, x, y) {
|
||||||
|
let neighbor_indices = [];
|
||||||
|
if (!maze[y][x].left && x > 0) {
|
||||||
|
neighbor_indices.push([x-1,y]);
|
||||||
|
}
|
||||||
|
if (!maze[y][x].right && x < maze[0].length-1) {
|
||||||
|
neighbor_indices.push([x+1,y]);
|
||||||
|
}
|
||||||
|
if (!maze[y][x].top && y > 0) {
|
||||||
|
neighbor_indices.push([x,y-1]);
|
||||||
|
}
|
||||||
|
if (!maze[y][x].bottom && y < maze.length-1) {
|
||||||
|
neighbor_indices.push([x,y+1]);
|
||||||
|
}
|
||||||
|
return neighbor_indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
function new_cell() {
|
||||||
|
return {
|
||||||
|
left: false,
|
||||||
|
right: false,
|
||||||
|
top: false,
|
||||||
|
bottom: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate_maze(n) {
|
||||||
|
let grid = new Array(n).fill().map((x) => (new Array(n).fill().map(new_cell)));
|
||||||
|
|
||||||
|
let point_sets = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
point_sets.push(new JSONSet([j,i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let edges = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
if (i !== n-1) {
|
||||||
|
edges.push([[i,j],[i+1,j]])
|
||||||
|
}
|
||||||
|
if (j !== n-1) {
|
||||||
|
edges.push([[i,j],[i,j+1]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shuffle_array(edges);
|
||||||
|
|
||||||
|
let maze_edges = edges.map((x) => x);
|
||||||
|
|
||||||
|
while (edges.length) {
|
||||||
|
let edge = edges.pop();
|
||||||
|
|
||||||
|
let set_inds = edge.map((i) => point_sets.findIndex((x) => x.apply_set_function('has', i)));
|
||||||
|
if (set_inds[0] == -1 || set_inds[1] == -1) {
|
||||||
|
throw new Error("Could not find correct index");
|
||||||
|
}
|
||||||
|
if (set_inds[0] == set_inds[1]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the union of the sets
|
||||||
|
for (let i of point_sets[set_inds[1]].items) {
|
||||||
|
point_sets[set_inds[0]].items.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
point_sets.splice(set_inds[1], 1);
|
||||||
|
maze_edges = maze_edges.filter((x) => x !== edge);
|
||||||
|
}
|
||||||
|
maze_edges.forEach((edge) => {
|
||||||
|
let direction = sub(edge[0], edge[1]);
|
||||||
|
if (direction[0] == -1) {
|
||||||
|
grid[edge[0][1]][edge[0][0]].right = grid[edge[1][1]][edge[1][0]].left = true;
|
||||||
|
}
|
||||||
|
else if (direction[1] == -1) {
|
||||||
|
grid[edge[0][1]][edge[0][0]].bottom = grid[edge[1][1]][edge[1][0]].top = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function solve_maze(maze, x, y, end_x, end_y) {
|
||||||
|
let path = new JSONHash();
|
||||||
|
let visited = new JSONSet();
|
||||||
|
let queue = [[x,y]];
|
||||||
|
while (queue.length) {
|
||||||
|
let cell = queue.shift();
|
||||||
|
visited.apply_set_function('add', cell);
|
||||||
|
if (cell[0] == end_x && cell[1] == end_y) {
|
||||||
|
let sol_path = [[end_x, end_y]];
|
||||||
|
while (sol_path[0][0] != x || sol_path[0][1] != y) {
|
||||||
|
sol_path.unshift(path.get_value(sol_path[0]));
|
||||||
|
}
|
||||||
|
return sol_path;
|
||||||
|
}
|
||||||
|
for (let i of get_neighbors(maze, cell[0], cell[1])) {
|
||||||
|
if (!visited.apply_set_function('has', i)) {
|
||||||
|
queue.push(i);
|
||||||
|
path.set_value(i, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
maize-maze/screenshot.png
Normal file
BIN
maize-maze/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
Loading…
Reference in New Issue
Block a user