add subprojects

This commit is contained in:
Lizzy Hunt 2024-01-12 19:13:13 -07:00
parent 3ac982dfa6
commit 07670ef8af
No known key found for this signature in database
GPG Key ID: E835BD4B08CCAF96
41 changed files with 1544 additions and 0 deletions

BIN
centipede/.DS_Store vendored Normal file

Binary file not shown.

BIN
centipede/assets/.DS_Store vendored Normal file

Binary file not shown.

BIN
centipede/assets/images/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
centipede/assets/sounds/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

63
centipede/css/style.css Normal file
View 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
View 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
View 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]

View 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"));

View 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
View 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();

View 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;
};

View 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;
}

View 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};
}

View 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;
}

View 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;
}

View 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});
});
}

View 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;
}

View 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;
}

View 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;
}

View 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"),
}

View 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
View 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
View File

@ -0,0 +1 @@
.DS_Store

21
maize-maze/LICENSE.md Normal file
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
maize-maze/images/fire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

75
maize-maze/index.html Normal file
View 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">&times;</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
View 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
View 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);