summaryrefslogtreecommitdiff
path: root/Homework/cs5410/bbiy/src/systems
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2026-07-02 11:55:17 -0700
committerElizabeth Alexander Hunt <me@liz.coffee>2026-07-02 11:55:17 -0700
commit6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6 (patch)
treeed97e39ec77c5231ffd2c394493e68d00ddac5a4 /Homework/cs5410/bbiy/src/systems
downloadmisc-undergrad-main.tar.gz
misc-undergrad-main.zip
Diffstat (limited to 'Homework/cs5410/bbiy/src/systems')
-rw-r--r--Homework/cs5410/bbiy/src/systems/collision.js93
-rw-r--r--Homework/cs5410/bbiy/src/systems/grid.js71
-rw-r--r--Homework/cs5410/bbiy/src/systems/keyboardInput.js46
-rw-r--r--Homework/cs5410/bbiy/src/systems/logic.js149
-rw-r--r--Homework/cs5410/bbiy/src/systems/menu.js114
-rw-r--r--Homework/cs5410/bbiy/src/systems/particle.js57
-rw-r--r--Homework/cs5410/bbiy/src/systems/physics.js16
-rw-r--r--Homework/cs5410/bbiy/src/systems/render.js26
-rw-r--r--Homework/cs5410/bbiy/src/systems/system.js1
-rw-r--r--Homework/cs5410/bbiy/src/systems/undo.js32
10 files changed, 605 insertions, 0 deletions
diff --git a/Homework/cs5410/bbiy/src/systems/collision.js b/Homework/cs5410/bbiy/src/systems/collision.js
new file mode 100644
index 0000000..0732c74
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/collision.js
@@ -0,0 +1,93 @@
+game.system.Collision = (entitiesGrid) => {
+ const update = (elapsedTime, entities, changedIds) => {
+ const thisChangedIds = new Set();
+ for (let entity of Object.keys(entities).map((id) => entities[id])) {
+ if (entity.hasComponent("position") && entity.hasComponent("gridPosition") && (entity.hasComponent("burnable") || entity.hasComponent("sinkable")) && entity.hasComponent("alive")) {
+ for (let collided of entitiesGrid[entity.components.gridPosition.y][entity.components.gridPosition.x].values()) {
+ if (!equivalence(entity, collided) && collided.hasComponent("alive")){
+ if (entity.hasComponent("burnable") && collided.hasComponent("burn")) {
+ const burnedParticleSpawner = game.createBorderParticles({colors: ["#f58d42", "#d6600b", "#c7a312", "#f2b844"]});
+ burnedParticleSpawner.addComponent(game.components.Position(collided.components.position));
+ burnedParticleSpawner.addComponent(game.components.Appearance({width: game.canvas.width / game.config.xDim, height: game.canvas.height / game.config.yDim}));
+ game.entities[burnedParticleSpawner.id] = burnedParticleSpawner;
+ entity.removeComponent("alive");
+ game.assets.death.play();
+ break;
+ } else if (entity.hasComponent("sinkable") && collided.hasComponent("sink")) {
+ const sunkParticleSpawner = game.createBorderParticles({colors: ["#16f7c9", "#0d6e5a", "#2fa18a", "#48cfb4", "#58877d", "#178054", "#2cdb92"]});
+ sunkParticleSpawner.addComponent(game.components.Position(collided.components.position));
+ sunkParticleSpawner.addComponent(game.components.Appearance({width: game.canvas.width / game.config.xDim, height: game.canvas.height / game.config.yDim}));
+ game.entities[sunkParticleSpawner.id] = sunkParticleSpawner;
+ entity.removeComponent("alive");
+ collided.removeComponent("alive");
+ game.assets.death.play();
+ break;
+ }
+ }
+ }
+ }
+ if (entity.hasComponent("controllable") && entity.hasComponent("gridPosition")) {
+ for (let collided of entitiesGrid[entity.components.gridPosition.y][entity.components.gridPosition.x].values()) {
+ if (collided.hasComponent("win")) {
+ game.assets.win.play();
+ game.win = true;
+ game.systems.menu.bringUpMenu();
+ game.systems.menu.setState("levelSelect");
+ }
+ }
+
+ if (entity.hasComponent("momentum")) {
+ const momentum = unitize(entity.components.momentum);
+
+ let found;
+ const proposed = {x: entity.components.gridPosition.x + momentum.dx, y: entity.components.gridPosition.y + momentum.dy};
+ const entitiesToPush = [];
+ let wall = false;
+ do {
+ const proposedClampedInBounds = clamp(proposed, game.config.xDim-1, game.config.yDim-1);
+ if (!equivalence(proposed, proposedClampedInBounds)) {
+ break;
+ }
+
+ found = false;
+
+ const entitiesInCell = entitiesGrid[proposed.y][proposed.x];
+
+ for (let next of entitiesInCell.values()) {
+ if (next.hasComponent("alive")) {
+ if (next.hasComponent("stop")) {
+ wall = next;
+ found = false;
+ break;
+ }
+ if (next.hasComponent("pushable")) {
+ entitiesToPush.push(next);
+ found = true;
+ }
+ }
+ }
+
+ proposed.x += momentum.dx;
+ proposed.y += momentum.dy;
+ } while(found);
+
+ if (wall) {
+ entity.removeComponent("momentum");
+ } else {
+ game.assets.move.play();
+ entitiesToPush.map((e) => {
+ const pushedParticleSpawner = game.createBorderParticles({maxSpeed: 0.1, minAmount: 10, maxAmount: 15});
+ pushedParticleSpawner.addComponent(game.components.Position(e.components.position));
+ pushedParticleSpawner.addComponent(game.components.Appearance({width: game.canvas.width / game.config.xDim, height: game.canvas.height / game.config.yDim}));
+ game.entities[pushedParticleSpawner.id] = pushedParticleSpawner;
+
+ e.addComponent(game.components.Momentum({...momentum}));
+ });
+ }
+ }
+ }
+ }
+ return thisChangedIds;
+ };
+ return { update };
+};
diff --git a/Homework/cs5410/bbiy/src/systems/grid.js b/Homework/cs5410/bbiy/src/systems/grid.js
new file mode 100644
index 0000000..6d4cf84
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/grid.js
@@ -0,0 +1,71 @@
+game.system.Grid = (entitiesGrid) => {
+ let gridWidth = game.canvas.width / game.config.xDim;
+ let gridHeight = game.canvas.height / game.config.yDim;
+
+ const gameCoordsToGrid = ({ x, y }) => {
+ return { x: Math.floor((x+gridWidth/2) / game.canvas.width * game.config.yDim), y: Math.floor((y+gridHeight/2) / game.canvas.height * game.config.yDim) };
+ };
+
+ const gridCoordsToGame = ({ x, y }) => {
+ return { x: x * gridWidth, y: y * gridHeight };
+ };
+
+ const rebuildGrid = (entities) => {
+ let changedIds = new Set();
+ entities.map(entity => {
+ const { x, y } = entity.components.gridPosition;
+ if (!entitiesGrid[y][x].has(entity.id)) {
+ changedIds.add(entity.id);
+ }
+ });
+ entitiesGrid.forEach((row) => row.forEach((entitiesInCell) => {
+ for (let id of entitiesInCell.keys()) {
+ if (changedIds.has(id)) {
+ entitiesInCell.delete(id);
+ }
+ }
+ }));
+ changedIds.forEach(id => {
+ // TODO: Figure out why we HAVE to use game.entities rather than entities
+ // Hint: it breaks when changing a level in the menu
+ const entity = game.entities[id];
+ const { x, y } = entity.components.gridPosition;
+ entitiesGrid[y][x].set(entity.id, entity);
+ });
+ };
+
+ const update = (_elapsedTime, entities, changedIds) => {
+ gridEntities = Object.keys(entities).filter((x) => entities[x].hasComponent("gridPosition")).map((x) => entities[x]);
+ const thisChangedIds = new Set();
+ gridEntities.map((entity) => {
+ if (entity.hasComponent("appearance")) {
+ entity.components.appearance.width = gridWidth;
+ entity.components.appearance.height = gridHeight;
+ }
+ if (entity.hasComponent("gridPosition")) {
+ const oldGridCoords = entity.components.gridPosition;
+ if (entity.hasComponent("position")) {
+ const gameCoords = gridCoordsToGame(entity.components.gridPosition);
+ if (Math.abs(entity.components.position.x - gameCoords.x) >= gridWidth/2 || Math.abs(entity.components.position.y - gameCoords.y) >= gridHeight/2) {
+ entity.components.gridPosition = gameCoordsToGrid(entity.components.position);
+ if (entity.hasComponent("momentum")) {
+ entity.removeComponent("momentum");
+ }
+ }
+ }
+ if (!entity.hasComponent("position") || !equivalence(entity.components.gridPosition, oldGridCoords)) {
+ entity.components.position = {
+ ...entity.components.position,
+ ...gridCoordsToGame(entity.components.gridPosition)
+ };
+ thisChangedIds.add(entity.id);
+ }
+ }
+ });
+ rebuildGrid(gridEntities);
+ return thisChangedIds;
+ };
+
+
+ return { gameCoordsToGrid, gridCoordsToGame, update, gridWidth, gridHeight };
+};
diff --git a/Homework/cs5410/bbiy/src/systems/keyboardInput.js b/Homework/cs5410/bbiy/src/systems/keyboardInput.js
new file mode 100644
index 0000000..1b399ca
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/keyboardInput.js
@@ -0,0 +1,46 @@
+game.system.KeyboardInput = () => {
+ "use strict";
+ const keys = {};
+ const keyPress = (event) => {
+ if (!event.repeat) {
+ keys[event.key] = true;
+ }
+ };
+ const removeKey = (event) => {
+ delete keys[event.key];
+ };
+ const update = (elapsedTime, entities, changedIds) => {
+ for (let id in entities) {
+ const entity = entities[id];
+ if (entity.hasComponent('controllable') && entity.hasComponent('alive')) {
+ const controls = entity.components.controllable.controls;
+ if (!changedIds.has(entity.id)) {
+ if (controls.includes('left') && keys[game.controls.left]) {
+ entity.addComponent(game.components.Momentum({ dx: -1, dy: 0 }));
+ } else if (controls.includes('right') && keys[game.controls.right]) {
+ entity.addComponent(game.components.Momentum({ dx: 1, dy: 0 }));
+ } else if (controls.includes('up') && keys[game.controls.up]) {
+ entity.addComponent(game.components.Momentum({ dx: 0, dy: -1 }));
+ } else if (controls.includes('down') && keys[game.controls.down]) {
+ entity.addComponent(game.components.Momentum({ dx: 0, dy: 1 }));
+ }
+ }
+ }
+ }
+ if (keys[game.controls.undo]) {
+ game.systems.undo.undo(entities);
+ }
+ if (keys[game.controls.reset]) {
+ game.loadLevelIndex(game.level);
+ }
+ if (keys["Escape"]) {
+ game.systems.menu.bringUpMenu();
+ }
+ Object.keys(keys).map((key) => delete keys[key]);
+
+ return new Set();
+ };
+ window.addEventListener("keydown", keyPress);
+ window.addEventListener("keyup", removeKey);
+ return { keys, update };
+}
diff --git a/Homework/cs5410/bbiy/src/systems/logic.js b/Homework/cs5410/bbiy/src/systems/logic.js
new file mode 100644
index 0000000..e8854c1
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/logic.js
@@ -0,0 +1,149 @@
+game.system.Logic = (entitiesGrid) => {
+ "use strict";
+ let currentVerbRules = [];
+ let previousVerbState = {
+ controllable: new Set(),
+ win: new Set(),
+ };
+ const isWord = (entity) => entity.hasComponent("gridPosition") && (entity.hasComponent("verb") || entity.hasComponent("noun"));
+
+ const getFirstWordEntity = (gridPosition) => {
+ if (!equivalence(gridPosition, clamp(gridPosition, game.config.xDim, game.config.yDim))) {
+ return null;
+ }
+ for (let entity of entitiesGrid[gridPosition.y][gridPosition.x].values()) {
+ if (isWord(entity)) {
+ return entity;
+ }
+ }
+ return null;
+ };
+
+ const verbActionsToComponent = {
+ "stop": game.components.Stop(),
+ "push": game.components.Pushable(),
+ "you": game.components.Controllable({controls: ['left', 'right', 'up', 'down']}),
+ "burn": game.components.Burn(),
+ "sink": game.components.Sink(),
+ "win": game.components.Win(),
+ };
+
+ const nounsToEntityCreators = {
+ "rock": game.createRock,
+ "wall": game.createWall,
+ "bigblue": game.createBigBlue,
+ "flag": game.createFlag,
+ "lava": game.createLava,
+ "water": game.createWater,
+ };
+
+ const doOnRule = (rule, entities, direction) => {
+ const [applyee, application] = [entities[rule[0]], entities[rule[1]]];
+ const changedEntityIds = [];
+ if (applyee.hasComponent("noun")) {
+ const entityName = applyee.components.noun.select;
+ if (application.hasComponent("verb")) {
+ const verb = application.components.verb.action;
+ if (direction == "apply") {
+ currentVerbRules.push(rule);
+ }
+ for (let id in entities) {
+ const entity = entities[id];
+ if (entity.hasComponent("alive") && entity.hasComponent("name") && entity.components.name.selector == entityName) {
+ changedEntityIds.push(id);
+ const component = verbActionsToComponent[verb];
+ if (component) {
+ if (direction == "apply") {
+ if (verb == "you") {
+ if (!previousVerbState.controllable.has(id)) {
+ const newYouParticleSpawner = game.createBorderParticles({colors: ["#ffc0cb", "#ffb6c1", "#ffc1cc", "#ffbcd9", "#ff1493"], minAmount: 80, maxAmount: 150, maxSpeed: 0.5});
+ newYouParticleSpawner.addComponent(game.components.Position(entity.components.position));
+ newYouParticleSpawner.addComponent(game.components.Appearance({width: game.canvas.width / game.config.xDim, height: game.canvas.height / game.config.yDim}));
+ game.entities[newYouParticleSpawner.id] = newYouParticleSpawner;
+ }
+ }
+ if (verb == "win") {
+ if (!previousVerbState.win.has(id)) {
+ game.assets.win.play();
+ }
+ }
+ entity.addComponent(component);
+ } else if (direction == "deapply") {
+ if (entity.hasComponent("controllable")) {
+ previousVerbState.controllable.add(id);
+ }
+ if (entity.hasComponent("win")) {
+ previousVerbState.win.add(id);
+ }
+ entity.removeComponent(component.name);
+ }
+ }
+ }
+ }
+ if (direction == "apply") {
+ if (changedEntityIds.some((id) => previousVerbState.controllable.has(id))) {
+ previousVerbState.controllable = new Set();
+ }
+ }
+ }
+ if (application.hasComponent("noun")) {
+ const applicationEntityName = application.components.noun.select;
+ for (let id in entities) {
+ const entity = entities[id];
+ if (entity.hasComponent("name") && entity.components.name.selector == entityName) {
+ const e = nounsToEntityCreators[applicationEntityName]();
+ ["name", "sprite", "burnable", "sinkable"].map((name) => {
+ if (e.hasComponent(name)) {
+ entity.components[name] = e.components[name];
+ }
+ });
+ }
+ }
+ }
+ };
+ return changedEntityIds;
+ };
+
+ const parseRules = (entities) => {
+ currentVerbRules.map((rule) => doOnRule(rule, entities, "deapply"));
+ currentVerbRules = [];
+ const isWordGridPositions = [];
+ const changedEntityIds = new Set();
+ entitiesGrid.forEach((row) => row.forEach((entitiesInCell) => {
+ for (let entity of entitiesInCell.values()) {
+ if (isWord(entity) && entity.hasComponent("verb") && entity.components.verb.action == "Is") {
+ isWordGridPositions.push(entity.components.gridPosition);
+ }
+ }
+ }));
+ let newRules = [];
+ isWordGridPositions.forEach((gridPosition) => {
+ const east = getFirstWordEntity({y: gridPosition.y, x: gridPosition.x - 1});
+ const west = getFirstWordEntity({y: gridPosition.y, x: gridPosition.x + 1});
+ const north = getFirstWordEntity({x: gridPosition.x, y: gridPosition.y - 1});
+ const south = getFirstWordEntity({x: gridPosition.x, y: gridPosition.y + 1});
+
+ if (east && west) {
+ newRules.push([east.id, west.id]);
+ }
+ if (north && south) {
+ newRules.push([north.id, south.id]);
+ }
+ });
+ newRules = newRules.sort((a, b) => (entities[b[1]].hasComponent("noun") ? 1 : -1) - (entities[a[1]].hasComponent("noun") ? 1 : -1));
+ newRules.map((rule) => doOnRule(rule, entities, "apply").map((id) => changedEntityIds.add(id)));
+ return changedEntityIds;
+ };
+
+ const update = (_elapsedTime, entities, changedIds) => {
+ for (let id of changedIds) {
+ const changed = entities[id];
+ if (changed.hasComponent("verb") || changed.hasComponent("noun")) {
+ return parseRules(entities);
+ }
+ }
+ return new Set();
+ };
+
+ return { update, parseRules };
+};
diff --git a/Homework/cs5410/bbiy/src/systems/menu.js b/Homework/cs5410/bbiy/src/systems/menu.js
new file mode 100644
index 0000000..abd6748
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/menu.js
@@ -0,0 +1,114 @@
+game.system.Menu = () => {
+ let state;
+
+ const menuElement = document.getElementById("menu");
+
+ const escapeEventListener = (e) => {
+ if (e.key == "Escape") {
+ setState('main');
+ }
+ };
+
+ const setState = (newState) => {
+ state = newState;
+ draw();
+ };
+
+ const bringUpMenu = () => {
+ game.running = false;
+ game.assets.music.pause();
+ window.addEventListener("keydown", escapeEventListener);
+ setState("main");
+ };
+
+ const hide = () => {
+ menuElement.style.display = "none";
+ game.startLoop();
+ };
+
+ const 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;
+ }
+ game.controls[action] = event.key;
+ localStorage.setItem("controls", JSON.stringify(game.controls));
+ element.innerHTML = event.key;
+ };
+ window.addEventListener("keydown", handleKey);
+ };
+
+ const setLevel = (index) => {
+ game.loadLevelIndex(index);
+ hide();
+ };
+
+ const draw = () => {
+ menuElement.style.display = "block";
+ menuElement.innerHTML = `<h1><span style='color: blue'>Big Blue</span> Is <span style='color: green'>You</h1>`;
+ if (state == "main") {
+ menuElement.innerHTML += `
+ <div class='menu-button' onclick='game.systems.menu.setState("controls")'>Change Controls</div>
+ <div class='menu-button' onclick='game.systems.menu.setState("credits")'>Credits</div>
+ <div class='menu-button' onclick='game.systems.menu.setState("levelSelect")'>Select Level</div>
+ `;
+ }
+ else if (state == "controls") {
+ menuElement.innerHTML += `
+ <div>
+ <p>
+ Move left: <button id="moveLeft" onfocus='game.systems.menu.listenFor("left", "moveLeft")'>${game.controls.left}</button>
+ <br>
+ Move right: <button id="moveRight" onfocus='game.systems.menu.listenFor("right", "moveRight")'>${game.controls.right}</button>
+ <br>
+ Move up: <button id="moveUp" onfocus='game.systems.menu.listenFor("up", "moveUp")'>${game.controls.up}</button>
+ <br>
+ Move down: <button id="moveDown" onfocus='game.systems.menu.listenFor("down", "moveDown")'>${game.controls.down}</button>
+ <br>
+ Undo: <button id="undo" onfocus='game.systems.menu.listenFor("undo", "undo")'>${game.controls.undo}</button>
+ <br>
+ Reset: <button id="reset" onfocus='game.systems.menu.listenFor("reset", "reset")'>${game.controls.reset}</button>
+ </p>
+ </div>
+ `;
+ } else if (state == "credits") {
+ menuElement.innerHTML += `
+ <div>
+ <p>
+ Sprites from Baba Is You, as hosted <a href="https://www.spriters-resource.com/pc_computer/babaisyou/sheet/115231/">here</a>, and a few custom ones.
+ <br>
+ Background is from <a href="https://i.pinimg.com/originals/b2/2a/a2/b22aa22b2f3f55b6468361158d52e2e7.gif">PinImg</a>.
+ <br>
+ Music is <a href="https://www.youtube.com/watch?v=yQjAF3frudY">Fluffing A Duck</a> by Kevin MacLeod.
+ <br>
+ Other sound effects generated on <a href="https://www.sfxr.me/">SFXR</a>.
+ <br>
+ Developed by Logan Hunt, Ethan Payne
+ </p>
+ </div>
+ `;
+ } else if (state == "levelSelect") {
+ menuElement.innerHTML += `
+ <div>
+ <p> Select a level to play: </p>
+ ${
+ game.levels.map((level, index) => {
+ return `<div class='menu-button' onclick='game.systems.menu.setLevel(${index});'>${level.levelName}</div>`;
+ }).join("")
+ }
+ `;
+ }
+ if (!game.win) {
+ menuElement.innerHTML += "<div class='menu-button' onclick='game.systems.menu.hide()'>Resume Game</div>";
+ }
+ if (state !== "main") {
+ menuElement.innerHTML += "<div class='menu-button' onclick='game.systems.menu.setState(\"main\")'>Back</div>";
+ }
+ };
+
+ return { bringUpMenu, setState, listenFor, hide, setLevel, state };
+};
diff --git a/Homework/cs5410/bbiy/src/systems/particle.js b/Homework/cs5410/bbiy/src/systems/particle.js
new file mode 100644
index 0000000..56d8c47
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/particle.js
@@ -0,0 +1,57 @@
+game.system.Particle = () => {
+ "use strict";
+ const particleSpawners = {};
+
+ const particleSpawner = ({colors, maxAmount, minAmount, minLife, maxLife, minRadius, maxRadius, maxSpeed, spawnFunction}) => {
+ return Array(randomInRange(minAmount, maxAmount)).fill(0).map(() => {
+ let particleSpec = {
+ x: Math.random(),
+ y: Math.random(),
+ dx: Math.random() * maxSpeed - maxSpeed / 2,
+ dy: Math.random() * maxSpeed - maxSpeed / 2,
+ radius: randomInRange(minRadius, maxRadius),
+ color: colors[randomInRange(0, colors.length-1)],
+ lifetime: randomInRange(minLife, maxLife),
+ elapsed: 0,
+ };
+ if (spawnFunction) {
+ particleSpec = {...particleSpec, ...spawnFunction(particleSpec)};
+ }
+ return particleSpec;
+ });
+ };
+
+ const update = (elapsedTime, entities, _changedIds) => {
+ for (let id in entities) {
+ const entity = entities[id];
+ if (entity.hasComponent("particles")) {
+ if (!particleSpawners[entity.id]) {
+ particleSpawners[entity.id] = particleSpawner(entity.components.particles.spec.spec);
+ entities[id].particleSprite = game.graphics.Sprite({
+ drawFunction: (elapsedTime, {x, y, width, height}, context) => {
+ let particleSpawner = particleSpawners[entity.id];
+ particleSpawner.map((particleSpec) => particleSpec.elapsed += elapsedTime);
+ particleSpawners[id] = particleSpawner.filter((particleSpec) => particleSpec.lifetime > particleSpec.elapsed);
+ particleSpawner = particleSpawners[id];
+ if (particleSpawner.length === 0) {
+ entities[id].removeComponent("alive");
+ }
+ particleSpawner.map((particleSpec) => {
+ const position = {x: (particleSpec.x * width) + x + particleSpec.dx * particleSpec.elapsed, y: (particleSpec.y * height) + y + particleSpec.dy * particleSpec.elapsed};
+ const fill = context.fillStyle;
+ context.fillStyle = particleSpec.color;
+ context.beginPath();
+ context.arc(position.x, position.y, particleSpec.radius, 0, 2 * Math.PI);
+ context.fill();
+ context.fillStyle = fill;
+ });
+ }
+ });
+ }
+ }
+ }
+ return new Set();
+ };
+
+ return { update };
+};
diff --git a/Homework/cs5410/bbiy/src/systems/physics.js b/Homework/cs5410/bbiy/src/systems/physics.js
new file mode 100644
index 0000000..18c3cb3
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/physics.js
@@ -0,0 +1,16 @@
+game.system.Physics = () => {
+ const update = (elapsedTime, entities, changedIds) => {
+ for (let id in entities) {
+ const entity = entities[id];
+ if (entity.hasComponent("momentum") && entity.hasComponent("appearance")) {
+ const {dx, dy} = entity.components.momentum;
+ entity.components.position.x += dx * elapsedTime;
+ entity.components.position.y += dy * elapsedTime;
+ entity.components.position = clamp(entity.components.position, game.canvas.width - entity.components.appearance.width, game.canvas.height - entity.components.appearance.height);
+ }
+ }
+
+ return new Set();
+ };
+ return { update };
+};
diff --git a/Homework/cs5410/bbiy/src/systems/render.js b/Homework/cs5410/bbiy/src/systems/render.js
new file mode 100644
index 0000000..e0db7e3
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/render.js
@@ -0,0 +1,26 @@
+game.system.Render = (graphics) => {
+ const update = (elapsedTime, entities, _changedIds) => {
+ graphics.clear();
+
+ const entitiesArray = Object.keys(entities).map(key => entities[key]);
+ const sortedEntities = entitiesArray.sort((a, b) => {
+ const aprior = a.hasComponent("loadPriority") ? a.components.loadPriority.priority : 0;
+ const bprior = b.hasComponent("loadPriority") ? b.components.loadPriority.priority : 0;
+ return bprior - aprior;
+ });
+
+ sortedEntities.forEach((entity) => {
+ if (entity.hasComponent("position") && entity.hasComponent("appearance") && entity.hasComponent("alive")) {
+ const drawSpec = {...entity.components.position, ...entity.components.appearance};
+ if (entity.hasComponent("sprite")) {
+ game.sprites[entity.components.sprite.spriteName].draw(elapsedTime, drawSpec);
+ } else if (entity.hasComponent("particles") && entity.particleSprite) {
+ entity.particleSprite.draw(elapsedTime, drawSpec);
+ }
+ }
+ });
+
+ return new Set();
+ };
+ return { update };
+};
diff --git a/Homework/cs5410/bbiy/src/systems/system.js b/Homework/cs5410/bbiy/src/systems/system.js
new file mode 100644
index 0000000..f331401
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/system.js
@@ -0,0 +1 @@
+game.system = {}; \ No newline at end of file
diff --git a/Homework/cs5410/bbiy/src/systems/undo.js b/Homework/cs5410/bbiy/src/systems/undo.js
new file mode 100644
index 0000000..086d6b4
--- /dev/null
+++ b/Homework/cs5410/bbiy/src/systems/undo.js
@@ -0,0 +1,32 @@
+game.system.Undo = (entitiesGrid, logicSystem, gridSystem) => {
+ const states = [];
+
+ const update = (elapsedTime, entities, changedIds) => {
+ if (changedIds.size) {
+ const state = {};
+ for (let id in entities) {
+ if (entities[id].hasComponent("gridPosition")) {
+ state[id] = JSON.parse(JSON.stringify(entities[id].components));
+ }
+ }
+ states.push(state);
+ }
+ return new Set();
+ };
+
+ const undo = (entities) => {
+ let state = states.slice(0, -1).pop();
+ if (states.length > 1) {
+ states.pop();
+ }
+ for (let id in state) {
+ for (let componentName in state[id]) {
+ entities[id].addComponent({name: componentName, ...state[id][componentName]});
+ }
+ }
+ gridSystem.update(0, entities, new Set());
+ logicSystem.parseRules(entities);
+ };
+
+ return { update, undo };
+};