summaryrefslogtreecommitdiff
path: root/centipede/js/game
diff options
context:
space:
mode:
authorLizzy Hunt <lizzy.hunt@usu.edu>2024-01-12 19:13:13 -0700
committerLizzy Hunt <lizzy.hunt@usu.edu>2024-01-12 19:13:13 -0700
commit07670ef8afb5a273267ea7149d5f7eef02fdf66b (patch)
tree2d0c8e64936c7fa2588786f4af199abf1bb48a60 /centipede/js/game
parent3ac982dfa653f0eb7fbceeb1678a3cae93b512f4 (diff)
downloadsimponic.xyz-07670ef8afb5a273267ea7149d5f7eef02fdf66b.tar.gz
simponic.xyz-07670ef8afb5a273267ea7149d5f7eef02fdf66b.zip
add subprojects
Diffstat (limited to 'centipede/js/game')
-rw-r--r--centipede/js/game/game.js39
-rw-r--r--centipede/js/game/graphics.js55
-rw-r--r--centipede/js/game/input.js32
-rw-r--r--centipede/js/game/menu.js143
-rw-r--r--centipede/js/game/object.js51
-rw-r--r--centipede/js/game/objects/bullet.js11
-rw-r--r--centipede/js/game/objects/centipede.js105
-rw-r--r--centipede/js/game/objects/explosion.js14
-rw-r--r--centipede/js/game/objects/flea.js49
-rw-r--r--centipede/js/game/objects/mushroom.js43
-rw-r--r--centipede/js/game/objects/player.js63
-rw-r--r--centipede/js/game/objects/scorpion.js32
-rw-r--r--centipede/js/game/objects/spider.js45
-rw-r--r--centipede/js/game/sounds.js5
-rw-r--r--centipede/js/game/sprites.js120
15 files changed, 807 insertions, 0 deletions
diff --git a/centipede/js/game/game.js b/centipede/js/game/game.js
new file mode 100644
index 0000000..30454f9
--- /dev/null
+++ b/centipede/js/game/game.js
@@ -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] \ No newline at end of file
diff --git a/centipede/js/game/graphics.js b/centipede/js/game/graphics.js
new file mode 100644
index 0000000..3a0d7f9
--- /dev/null
+++ b/centipede/js/game/graphics.js
@@ -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")); \ No newline at end of file
diff --git a/centipede/js/game/input.js b/centipede/js/game/input.js
new file mode 100644
index 0000000..6ceff13
--- /dev/null
+++ b/centipede/js/game/input.js
@@ -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 };
+})(); \ No newline at end of file
diff --git a/centipede/js/game/menu.js b/centipede/js/game/menu.js
new file mode 100644
index 0000000..d4a7fe9
--- /dev/null
+++ b/centipede/js/game/menu.js
@@ -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(); \ No newline at end of file
diff --git a/centipede/js/game/object.js b/centipede/js/game/object.js
new file mode 100644
index 0000000..1e22ec6
--- /dev/null
+++ b/centipede/js/game/object.js
@@ -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;
+};
diff --git a/centipede/js/game/objects/bullet.js b/centipede/js/game/objects/bullet.js
new file mode 100644
index 0000000..5791aca
--- /dev/null
+++ b/centipede/js/game/objects/bullet.js
@@ -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;
+} \ No newline at end of file
diff --git a/centipede/js/game/objects/centipede.js b/centipede/js/game/objects/centipede.js
new file mode 100644
index 0000000..7e4c8f6
--- /dev/null
+++ b/centipede/js/game/objects/centipede.js
@@ -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};
+} \ No newline at end of file
diff --git a/centipede/js/game/objects/explosion.js b/centipede/js/game/objects/explosion.js
new file mode 100644
index 0000000..f38d820
--- /dev/null
+++ b/centipede/js/game/objects/explosion.js
@@ -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;
+} \ No newline at end of file
diff --git a/centipede/js/game/objects/flea.js b/centipede/js/game/objects/flea.js
new file mode 100644
index 0000000..23479cc
--- /dev/null
+++ b/centipede/js/game/objects/flea.js
@@ -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;
+} \ No newline at end of file
diff --git a/centipede/js/game/objects/mushroom.js b/centipede/js/game/objects/mushroom.js
new file mode 100644
index 0000000..b1f4787
--- /dev/null
+++ b/centipede/js/game/objects/mushroom.js
@@ -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});
+ });
+}
diff --git a/centipede/js/game/objects/player.js b/centipede/js/game/objects/player.js
new file mode 100644
index 0000000..a1f6ea0
--- /dev/null
+++ b/centipede/js/game/objects/player.js
@@ -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;
+} \ No newline at end of file
diff --git a/centipede/js/game/objects/scorpion.js b/centipede/js/game/objects/scorpion.js
new file mode 100644
index 0000000..036db14
--- /dev/null
+++ b/centipede/js/game/objects/scorpion.js
@@ -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;
+} \ No newline at end of file
diff --git a/centipede/js/game/objects/spider.js b/centipede/js/game/objects/spider.js
new file mode 100644
index 0000000..732c0a3
--- /dev/null
+++ b/centipede/js/game/objects/spider.js
@@ -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;
+} \ No newline at end of file
diff --git a/centipede/js/game/sounds.js b/centipede/js/game/sounds.js
new file mode 100644
index 0000000..584dbd3
--- /dev/null
+++ b/centipede/js/game/sounds.js
@@ -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"),
+} \ No newline at end of file
diff --git a/centipede/js/game/sprites.js b/centipede/js/game/sprites.js
new file mode 100644
index 0000000..3cdd77f
--- /dev/null
+++ b/centipede/js/game/sprites.js
@@ -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,
+ })
+ )
+}; \ No newline at end of file