diff --git a/index.js b/index.js index c53b521..8b046c1 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,7 @@ app.route('/').get((req, res) => { }); app.route('/levels').get((req, res) => { - fs.readFile(path.join(__dirname, 'level-all.bbiy'), 'utf8', (err, data) => { + fs.readFile(path.join(__dirname, 'levels-all.bbiy'), 'utf8', (err, data) => { if (err) { console.log(err); return; diff --git a/level-all.bbiy b/levels-all.bbiy similarity index 77% rename from level-all.bbiy rename to levels-all.bbiy index 46f736a..a098f30 100644 --- a/level-all.bbiy +++ b/levels-all.bbiy @@ -1,3 +1,45 @@ +(almost) No Rules +20 x 20 +hhhhhhhhhhhhhhhhhhhh +h h +h h +h h +h h +h h +h h +h wwwwwwwwwwww h +h r h +h b r f h +h r h +h aaaaa vvvvv h +h h +h h +h h +h h +h h +h h +h h +hhhhhhhhhhhhhhhhhhhh + + + V IK A IN + + W R + IS IP + + + + + + + + BIY IX + F + + + + + Level-1 20 x 20 hhhhhhhhhhhhhhhhhhhh @@ -29,8 +71,8 @@ hhhhhhhhhhhhhhhhhhhh wwwwwwwwwwww r - r f - b r + b r f + r wwwwwwwwwwww BIY FIX @@ -67,8 +109,8 @@ hhhhhhhhhhhhhhhhhhhh wwwwwwww - w I w w w + w I w wwwww X w w w w F f b w @@ -131,14 +173,14 @@ h h h g h h g gh h h -h g h -h g h +h g g h h h h gg h h h h h h h h h +h h h g h h h hg h @@ -160,7 +202,7 @@ hhhhhhhhhhhhhhhhhhhh w w w w waaa w - waaa w + waaa FIX w wfaa w wwwwwwwwwwwwww @@ -203,7 +245,7 @@ hhhhhhhhhhhhhhhhhhhh wBIYw r F - I + RIP I X VIK diff --git a/src/bootstrap.js b/src/bootstrap.js index 70f1b87..c15bf53 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -17,7 +17,8 @@ game.bootstrap = (() => { 'src/components/position.js', 'src/components/momentum.js', 'src/components/gridPosition.js', 'src/components/appearence.js', 'src/components/controllable.js', 'src/components/pushable.js', 'src/components/loadPriority.js', 'src/components/stop.js', 'src/components/alive.js', - 'src/components/sprite.js', 'src/components/particles.js' + 'src/components/sprite.js', 'src/components/particles.js', 'src/components/noun.js', + 'src/components/name.js', 'src/components/verb.js', ], id: 'components' }, @@ -37,7 +38,7 @@ game.bootstrap = (() => { { src: [ 'src/systems/render.js', 'src/systems/grid.js', 'src/systems/physics.js', 'src/systems/keyboardInput.js', - 'src/systems/collision.js', 'src/systems/undo.js', 'src/systems/particle.js', 'src/systems/menu.js' + 'src/systems/collision.js', 'src/systems/undo.js', 'src/systems/particle.js', 'src/systems/menu.js', 'src/systems/logic.js', ], id: 'systems' }, { src: ['src/game.js'], id: 'game' }, diff --git a/src/components/component.js b/src/components/component.js index be59367..9b55a1f 100644 --- a/src/components/component.js +++ b/src/components/component.js @@ -4,5 +4,5 @@ game.Component = (name, spec) => { return { name, ...spec - } + }; }; diff --git a/src/components/name.js b/src/components/name.js new file mode 100644 index 0000000..d3bf14a --- /dev/null +++ b/src/components/name.js @@ -0,0 +1 @@ +game.components.Name = ({selector}) => game.Component('name', {selector}); diff --git a/src/components/noun.js b/src/components/noun.js new file mode 100644 index 0000000..4b01e06 --- /dev/null +++ b/src/components/noun.js @@ -0,0 +1 @@ +game.components.Noun = ({select}) => game.Component('noun', {select}); diff --git a/src/components/verb.js b/src/components/verb.js new file mode 100644 index 0000000..393e48c --- /dev/null +++ b/src/components/verb.js @@ -0,0 +1 @@ +game.components.Verb = ({action}) => game.Component('verb', {action}); diff --git a/src/entities/bigblue.js b/src/entities/bigblue.js index 5b6af0f..bcf80d5 100644 --- a/src/entities/bigblue.js +++ b/src/entities/bigblue.js @@ -3,10 +3,10 @@ game.createBigBlue = () => { bigBlue.addComponent(game.components.LoadPriority({priority: 1})); bigBlue.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); bigBlue.addComponent(game.components.Alive()); - bigBlue.addComponent(game.components.Sprite({spriteName: "bigBlue"})) + bigBlue.addComponent(game.components.Sprite({spriteName: "bigBlue"})); - // TODO: Remove this - bigBlue.addComponent(game.components.Controllable({controls: ['left', 'right', 'up', 'down']})); +// bigBlue.addComponent(game.components.Controllable({controls: ['left', 'right', 'up', 'down']})); + bigBlue.addComponent(game.components.Name({selector: "bigblue"})); return bigBlue; -} \ No newline at end of file +}; diff --git a/src/entities/rock.js b/src/entities/rock.js index 4d73f8d..6a28f6e 100644 --- a/src/entities/rock.js +++ b/src/entities/rock.js @@ -3,10 +3,11 @@ game.createRock = () => { rock.addComponent(game.components.LoadPriority({priority: 2})); rock.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); rock.addComponent(game.components.Alive()); - rock.addComponent(game.components.Sprite({spriteName: "rock"})) + rock.addComponent(game.components.Sprite({spriteName: "rock"}));; - //TODO: Remove this - rock.addComponent(game.components.Pushable()); + +// rock.addComponent(game.components.Pushable()); + rock.addComponent(game.components.Name({selector: "rock"})); return rock; -} +}; diff --git a/src/entities/wall.js b/src/entities/wall.js index af9aa1d..7b5c65f 100644 --- a/src/entities/wall.js +++ b/src/entities/wall.js @@ -2,8 +2,9 @@ game.createWall = () => { const wall = game.Entity(); wall.addComponent(game.components.LoadPriority({priority: 3})); wall.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); - wall.addComponent(game.components.Stop()); +// wall.addComponent(game.components.Stop()); + wall.addComponent(game.components.Name({selector: "wall"})); wall.addComponent(game.components.Alive()); - wall.addComponent(game.components.Sprite({spriteName: "wall"})) + wall.addComponent(game.components.Sprite({spriteName: "wall"})); return wall; -} +}; diff --git a/src/entities/wordBigBlue.js b/src/entities/wordBigBlue.js index b6dd661..15aaa0e 100644 --- a/src/entities/wordBigBlue.js +++ b/src/entities/wordBigBlue.js @@ -4,6 +4,7 @@ game.createWordBigBlue = () => { wordBigBlue.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordBigBlue.addComponent(game.components.Pushable({pushable: true})); wordBigBlue.addComponent(game.components.Alive()); - wordBigBlue.addComponent(game.components.Sprite({spriteName: "wordBigBlue"})) + wordBigBlue.addComponent(game.components.Sprite({spriteName: "wordBigBlue"})); + wordBigBlue.addComponent(game.components.Noun({select: "bigblue"})); return wordBigBlue; -} +}; diff --git a/src/entities/wordIs.js b/src/entities/wordIs.js index 15bcbea..041f10c 100644 --- a/src/entities/wordIs.js +++ b/src/entities/wordIs.js @@ -5,6 +5,7 @@ game.createWordIs = () => { // wordIs.addComponent(game.components.Stop({stop: true})); wordIs.addComponent(game.components.Pushable()); wordIs.addComponent(game.components.Alive()); - wordIs.addComponent(game.components.Sprite({spriteName: "wordIs"})) + wordIs.addComponent(game.components.Sprite({spriteName: "wordIs"})); + wordIs.addComponent(game.components.Verb({action: "Is"})); return wordIs; -} +}; diff --git a/src/entities/wordPush.js b/src/entities/wordPush.js index 09478f6..5594e46 100644 --- a/src/entities/wordPush.js +++ b/src/entities/wordPush.js @@ -4,6 +4,7 @@ game.createWordPush = () => { wordPush.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordPush.addComponent(game.components.Pushable({pushable: true})); wordPush.addComponent(game.components.Alive()); - wordPush.addComponent(game.components.Sprite({spriteName: "wordPush"})) + wordPush.addComponent(game.components.Sprite({spriteName: "wordPush"})); + wordPush.addComponent(game.components.Verb({action: "push"})); return wordPush; -} +}; diff --git a/src/entities/wordRock.js b/src/entities/wordRock.js index 0897e35..648f6ba 100644 --- a/src/entities/wordRock.js +++ b/src/entities/wordRock.js @@ -4,6 +4,7 @@ game.createWordRock = () => { wordRock.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordRock.addComponent(game.components.Pushable({pushable: true})); wordRock.addComponent(game.components.Alive()); - wordRock.addComponent(game.components.Sprite({spriteName: "wordRock"})) + wordRock.addComponent(game.components.Sprite({spriteName: "wordRock"})); + wordRock.addComponent(game.components.Noun({select: "rock"})); return wordRock; -} +}; diff --git a/src/entities/wordStop.js b/src/entities/wordStop.js index 6eb79ea..e2ad45c 100644 --- a/src/entities/wordStop.js +++ b/src/entities/wordStop.js @@ -4,6 +4,7 @@ game.createWordStop = () => { wordStop.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordStop.addComponent(game.components.Pushable({pushable: true})); wordStop.addComponent(game.components.Alive()); - wordStop.addComponent(game.components.Sprite({spriteName: "wordStop"})) + wordStop.addComponent(game.components.Sprite({spriteName: "wordStop"})); + wordStop.addComponent(game.components.Verb({action: "stop"})); return wordStop; -} +}; diff --git a/src/entities/wordWall.js b/src/entities/wordWall.js index ea7068f..277ab69 100644 --- a/src/entities/wordWall.js +++ b/src/entities/wordWall.js @@ -4,6 +4,7 @@ game.createWordWall = () => { wordWall.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordWall.addComponent(game.components.Pushable({pushable: true})); wordWall.addComponent(game.components.Alive()); - wordWall.addComponent(game.components.Sprite({spriteName: "wordWall"})) + wordWall.addComponent(game.components.Noun({select: "wall"})); + wordWall.addComponent(game.components.Sprite({spriteName: "wordWall"})); return wordWall; -} +}; diff --git a/src/entities/wordYou.js b/src/entities/wordYou.js index 89a3723..1bf698f 100644 --- a/src/entities/wordYou.js +++ b/src/entities/wordYou.js @@ -4,6 +4,7 @@ game.createWordYou = () => { wordYou.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordYou.addComponent(game.components.Pushable({pushable: true})); wordYou.addComponent(game.components.Alive()); - wordYou.addComponent(game.components.Sprite({spriteName: "wordYou"})) + wordYou.addComponent(game.components.Sprite({spriteName: "wordYou"})); + wordYou.addComponent(game.components.Verb({action: "you"})); return wordYou; -} +}; diff --git a/src/game.js b/src/game.js index b48ef19..4053d84 100644 --- a/src/game.js +++ b/src/game.js @@ -14,36 +14,37 @@ game.loop = (timeStamp) => { }); for (let id in game.entities) { - if (!game.entities[id].hasComponent("alive")) { + if (game.entities[id].hasComponent("particles") && !game.entities[id].hasComponent("alive")) { delete game.entities[id]; } } requestAnimationFrame(game.loop); -} +}; game.toggleRunning = () => { game.running = !game.running; -} +}; game.startLoop = () => { game.running = true; game.lastTimeStamp = performance.now(); requestAnimationFrame(game.loop); -} +}; game.loadSystems = () => { - game.systemOrder = ["grid", "collision", "physics", "keyboardInput", "undo", "particle", "render"]; + game.systemOrder = ["grid", "collision", "physics", "keyboardInput", "particle", "logic", "undo", "render"]; game.systems = { }; game.systems.physics = game.system.Physics(), game.systems.grid = game.system.Grid(game.entitiesGrid); game.systems.collision = game.system.Collision(game.entitiesGrid); game.systems.render = game.system.Render(game.graphics); - game.systems.undo = game.system.Undo(game.entitiesGrid); game.systems.particle = game.system.Particle(game.canvas.context); game.systems.keyboardInput = game.system.KeyboardInput(); + game.systems.logic = game.system.Logic(game.entitiesGrid); + game.systems.undo = game.system.Undo(game.entitiesGrid, game.systems.logic, game.systems.grid); game.systems.menu = game.system.Menu(); -} +}; game.loadLevelIndex = (level) => { game.level = level; @@ -52,9 +53,9 @@ game.loadLevelIndex = (level) => { // Maintained by grid system as a side-effect game.entitiesGrid = Array(game.config.yDim).fill(null).map(() => Array(game.config.xDim).fill(null).map(() => new Map())); game.loadSystems(); -} +}; game.initialize = () => { game.loadLevelIndex(0); game.startLoop(); -} +}; diff --git a/src/systems/collision.js b/src/systems/collision.js index 0070ee7..d769636 100644 --- a/src/systems/collision.js +++ b/src/systems/collision.js @@ -44,7 +44,7 @@ game.system.Collision = (entitiesGrid) => { 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})) + e.addComponent(game.components.Momentum({...momentum})); }); } } diff --git a/src/systems/grid.js b/src/systems/grid.js index 6add388..6d4cf84 100644 --- a/src/systems/grid.js +++ b/src/systems/grid.js @@ -32,7 +32,7 @@ game.system.Grid = (entitiesGrid) => { 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]); @@ -68,4 +68,4 @@ game.system.Grid = (entitiesGrid) => { return { gameCoordsToGrid, gridCoordsToGame, update, gridWidth, gridHeight }; -}; \ No newline at end of file +}; diff --git a/src/systems/logic.js b/src/systems/logic.js new file mode 100644 index 0000000..8e3bbc0 --- /dev/null +++ b/src/systems/logic.js @@ -0,0 +1,110 @@ +game.system.Logic = (entitiesGrid) => { + "use strict"; + let currentVerbRules = []; + 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']}), + + }; + + const nounsToEntityCreators = { + "rock": game.createRock, + "wall": game.createWall, + }; + + 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) { + if (entities[id].hasComponent("name") && entities[id].components.name.selector == entityName) { + changedEntityIds.push(id); + const component = verbActionsToComponent[verb]; + if (component) { + if (direction == "apply") { + entities[id].addComponent(component); + } else if (direction == "deapply") { + entities[id].removeComponent(component.name); + } + } + } + } + } + if (application.hasComponent("noun")) { + const applicationEntityName = application.components.noun.select; + for (let id in entities) { + if (entities[id].hasComponent("name") && entities[id].components.name.selector == entityName) { + const e = nounsToEntityCreators[applicationEntityName](); + entities[id].components.name = e.components.name; + entities[id].components.sprite = e.components.sprite; + } + } + } + }; + 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/src/systems/menu.js b/src/systems/menu.js index 17153d0..629a6a3 100644 --- a/src/systems/menu.js +++ b/src/systems/menu.js @@ -7,23 +7,23 @@ game.system.Menu = () => { if (e.key == "Escape") { setState('main'); } - } + }; const setState = (newState) => { state = newState; draw(); - } + }; const bringUpMenu = () => { game.running = false; window.addEventListener("keydown", escapeEventListener); setState("main"); - } + }; const hide = () => { menuElement.style.display = "none"; game.startLoop(); - } + }; const listenFor = (action, elementId) => { const element = document.getElementById(elementId); @@ -37,14 +37,14 @@ game.system.Menu = () => { 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"; @@ -73,7 +73,7 @@ game.system.Menu = () => { Reset:

- ` + `; } else if (state == "credits") { menuElement.innerHTML += `
@@ -85,23 +85,23 @@ game.system.Menu = () => { Developed by Logan Hunt, Ethan Payne

- ` + `; } else if (state == "levelSelect") { menuElement.innerHTML += `

Select a level to play:

${ game.levels.map((level, index) => { - return `` + return ``; }).join("") } `; } - menuElement.innerHTML += "" + menuElement.innerHTML += ""; if (state !== "main") { - menuElement.innerHTML += "" + menuElement.innerHTML += ""; } - } - + }; + return { bringUpMenu, setState, listenFor, hide, setLevel, state }; -} \ No newline at end of file +}; diff --git a/src/systems/particle.js b/src/systems/particle.js index e58c0a0..56d8c47 100644 --- a/src/systems/particle.js +++ b/src/systems/particle.js @@ -19,7 +19,7 @@ game.system.Particle = () => { } return particleSpec; }); - } + }; const update = (elapsedTime, entities, _changedIds) => { for (let id in entities) { diff --git a/src/systems/physics.js b/src/systems/physics.js index d72b1a0..18c3cb3 100644 --- a/src/systems/physics.js +++ b/src/systems/physics.js @@ -11,6 +11,6 @@ game.system.Physics = () => { } return new Set(); - } + }; return { update }; -} \ No newline at end of file +}; diff --git a/src/systems/render.js b/src/systems/render.js index adc8355..8a3e633 100644 --- a/src/systems/render.js +++ b/src/systems/render.js @@ -10,7 +10,7 @@ game.system.Render = (graphics) => { }); sortedEntities.forEach((entity) => { - if (entity.hasComponent("position") && entity.hasComponent("appearance")) { + 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); @@ -21,6 +21,6 @@ game.system.Render = (graphics) => { }); return new Set(); - } + }; return { update }; }; diff --git a/src/systems/undo.js b/src/systems/undo.js index 29f5bec..086d6b4 100644 --- a/src/systems/undo.js +++ b/src/systems/undo.js @@ -1,4 +1,4 @@ -game.system.Undo = (entitiesGrid) => { +game.system.Undo = (entitiesGrid, logicSystem, gridSystem) => { const states = []; const update = (elapsedTime, entities, changedIds) => { @@ -12,7 +12,7 @@ game.system.Undo = (entitiesGrid) => { states.push(state); } return new Set(); - } + }; const undo = (entities) => { let state = states.slice(0, -1).pop(); @@ -24,7 +24,9 @@ game.system.Undo = (entitiesGrid) => { entities[id].addComponent({name: componentName, ...state[id][componentName]}); } } - } + gridSystem.update(0, entities, new Set()); + logicSystem.parseRules(entities); + }; return { update, undo }; -} \ No newline at end of file +}; diff --git a/src/utils/clamp.js b/src/utils/clamp.js index b709ee5..b0da861 100644 --- a/src/utils/clamp.js +++ b/src/utils/clamp.js @@ -3,4 +3,4 @@ const clamp = (vector, maxX, maxY) => { newVector.x = Math.max(0, Math.min(maxX, vector.x)); newVector.y = Math.max(0, Math.min(maxY, vector.y)); return newVector; -} \ No newline at end of file +};