From 35bc54eaac674fcd5a9c1ef1281e35bd2fb2bd1e Mon Sep 17 00:00:00 2001 From: KingOfDog Date: Tue, 11 Jun 2019 18:46:14 +0200 Subject: [PATCH] initial commit --- DOM.js | 10 ++ Game.js | 114 ++++++++++++++++++ GameMap.js | 47 ++++++++ GameObject.js | 10 ++ Ghost.js | 278 +++++++++++++++++++++++++++++++++++++++++++ MapLoader.js | 87 ++++++++++++++ MapTile.js | 79 ++++++++++++ MovingObject.js | 98 +++++++++++++++ Player.js | 96 +++++++++++++++ helper-methods.js | 5 + index.html | 28 +++++ levels/level1.pacmap | 21 ++++ main.js | 2 + 13 files changed, 875 insertions(+) create mode 100644 DOM.js create mode 100644 Game.js create mode 100644 GameMap.js create mode 100644 GameObject.js create mode 100644 Ghost.js create mode 100644 MapLoader.js create mode 100644 MapTile.js create mode 100644 MovingObject.js create mode 100644 Player.js create mode 100644 helper-methods.js create mode 100644 index.html create mode 100644 levels/level1.pacmap create mode 100644 main.js diff --git a/DOM.js b/DOM.js new file mode 100644 index 0000000..507796c --- /dev/null +++ b/DOM.js @@ -0,0 +1,10 @@ +Node.prototype.createChild = function(name, className) { + const el = document.createElement(name); + + if(typeof className === 'string') { + el.classList.add(className); + } + + this.appendChild(el); + return el; +}; \ No newline at end of file diff --git a/Game.js b/Game.js new file mode 100644 index 0000000..440d132 --- /dev/null +++ b/Game.js @@ -0,0 +1,114 @@ +class Game { + constructor() { + this.canvas = document.body.createChild('canvas', 'pacman-game'); + this.ctx = this.canvas.getContext('2d'); + + this.loadMap(); + this.onResize(); + + this.gameObjects = []; + + this.paused = false; + this.gameOver = false; + + this.init(); + } + + init() { + this.gameObjects.push(new Player(this, this.map.spawn.x + 0.5, this.map.spawn.y + 0.5)); + + this.gameObjects.push(new Ghost(this, 5.5, 6.5)); + // this.gameObjects.push(new Ghost(this, 3.5, 20.5)); + // this.gameObjects.push(new Ghost(this, 10.5, 7.5)); + // this.gameObjects.push(new Ghost(this, 19.5, 12.5)); + + this.registerKeyListeners(); + this.registerWindowListeners(); + } + + registerKeyListeners() { + window.addEventListener('keydown', (event) => { + const key = event.key; + + switch (key) { + case 'ArrowLeft': + this.getPlayer().changeDir({x: -1, y: 0}); + break; + case 'ArrowUp': + this.getPlayer().changeDir({x: 0, y: -1}); + break; + case 'ArrowRight': + this.getPlayer().changeDir({x: 1, y: 0}); + break; + case 'ArrowDown': + this.getPlayer().changeDir({x: 0, y: 1}); + break; + } + }); + } + + registerWindowListeners() { + window.addEventListener('resize', (event) => { + this.onResize(); + }); + } + + onResize() { + this.calculateConf(window.innerWidth, window.innerHeight); + + this.canvas.width = this.conf.tileSize * this.map.width; + this.canvas.height = this.conf.tileSize * this.map.height; + + this.canvas.style.width = this.canvas.width + 'px'; + this.canvas.style.height = this.canvas.height + 'px'; + } + + loadMap() { + const mapLoader = new MapLoader("levels/level1.pacmap"); + mapLoader.start(); + this.map = mapLoader.getMap(); + } + + calculateConf(width, height) { + this.conf = { + tileSize: calculateTileSize(width, height, this.map.width, this.map.height), + }; + } + + getGameObjectsByType(type) { + return this.gameObjects.filter(object => object instanceof type); + } + + getPlayer() { + return this.getGameObjectsByType(Player)[0]; + } + + draw() { + this.ctx.clearRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight); + + this.map.draw(this.ctx, this.conf); + + this.gameObjects.forEach(object => { + object.draw(this.ctx, this.conf); + }); + + requestAnimationFrame(() => this.draw()); + } + + update(lastUpdate) { + const updateTime = Date.now(); + const deltaTime = updateTime - lastUpdate || 16; + + this.gameObjects.forEach(object => { + object.update(deltaTime); + }); + + if (!this.paused && !this.gameOver) + requestAnimationFrame(() => this.update(updateTime)); + } + + start() { + this.draw(); + this.update(); + } +} \ No newline at end of file diff --git a/GameMap.js b/GameMap.js new file mode 100644 index 0000000..3458e09 --- /dev/null +++ b/GameMap.js @@ -0,0 +1,47 @@ +class GameMap { + constructor(tiles, spawn) { + this.width = tiles.length; + this.height = tiles[0].length; + this.tiles = tiles; + this.spawn = spawn; + + this.init(); + } + + init() { + this.tiles.forEach(row => { + row.forEach(tile => { + if(tile.solid) { + tile.lookAtNeighbours(this.getSurroundingTiles(tile.x, tile.y)); + } + }); + }); + } + + draw(ctx, conf) { + this.tiles.forEach(col => { + col.forEach(tile => { + tile.draw(ctx, conf); + }); + }); + } + + getTile(x, y) { + try { + return this.tiles[x][y]; + } catch (e) { + return null; + } + } + + getSurroundingTiles(x, y) { + const left = this.getTile(x - 1, y); + const right = this.getTile(x + 1, y); + const top = this.getTile(x, y - 1); + const bottom = this.getTile(x, y + 1); + + return { + left, top, right, bottom + }; + } +} \ No newline at end of file diff --git a/GameObject.js b/GameObject.js new file mode 100644 index 0000000..3762fc6 --- /dev/null +++ b/GameObject.js @@ -0,0 +1,10 @@ +class GameObject { + constructor(game, x, y) { + this.game = game; + this.x = x; + this.y = y; + } + + draw(ctx, conf) {} + update() {} +} \ No newline at end of file diff --git a/Ghost.js b/Ghost.js new file mode 100644 index 0000000..45344b6 --- /dev/null +++ b/Ghost.js @@ -0,0 +1,278 @@ +function dirNameToValue(dirName) { + switch (dirName) { + case 'left': + return {x: -1, y: 0}; + case 'right': + return {x: 1, y: 0}; + case 'top': + return {x: 0, y: -1}; + case 'bottom': + return {x: 0, y: 1}; + default: + return null; + } +} + +class Ghost extends MovingObject { + constructor(game, x, y) { + super(game, x, y); + this.speed = 2; + this.dir = {x: 0, y: 0}; + + this.lastDirChange = {x: Infinity, y: Infinity}; + } + + draw(ctx, conf) { + const size = conf.tileSize; + const cx = this.x * size; + const cy = this.y * size; + + const left = -0.5 * size; + const top = -0.5 * size; + const right = 0.5 * size; + const base = 0.5 * size - 3; + + const inc = size / 10; + const high = 3; + const low = -3; + + ctx.save(); + ctx.translate(cx, cy); + ctx.fillStyle = "#0f0"; + ctx.beginPath(); + + // Draw body + ctx.moveTo(left, base); + + ctx.quadraticCurveTo(left, top, 0, top); + ctx.quadraticCurveTo(right, top, right, base); + + // Wavy things at the bottom + ctx.quadraticCurveTo(right - (inc * 1), base + high, right - (inc * 2), base); + ctx.quadraticCurveTo(right - (inc * 3), base + low, right - (inc * 4), base); + ctx.quadraticCurveTo(right - (inc * 5), base + high, right - (inc * 6), base); + ctx.quadraticCurveTo(right - (inc * 7), base + low, right - (inc * 8), base); + ctx.quadraticCurveTo(right - (inc * 9), base + high, right - (inc * 10), base); + + ctx.closePath(); + ctx.fill(); + + // Draw eye background + ctx.fillStyle = "#fff"; + ctx.beginPath(); + + ctx.arc(left + size * .3, top + size * .3, size / 6, 0, 2 * Math.PI); + ctx.arc(right - size * .3, top + size * .3, size / 6, 0, 2 * Math.PI); + + ctx.closePath(); + ctx.fill(); + + // Draw eyes + const f = size / 12; + const offset = { + x: this.dir.x * f, + y: this.dir.y * f, + }; + + ctx.fillStyle = "#000"; + ctx.beginPath(); + + ctx.arc(left + size * .3 + offset.x, top + size * .3 + offset.y, size / 15, 0, 2 * Math.PI); + ctx.arc(right - size * .3 + offset.x, top + size * .3 + offset.y, size / 15, 0, 2 * Math.PI); + + ctx.closePath(); + ctx.fill(); + + ctx.restore(); + } + + update(deltaTime) { + const nextTile = this.getNextTile(0, 0); + if (nextTile.distanceX <= 0.5 && nextTile.distanceY <= 0.5 && !(nextTile.tile.x === this.lastDirChange.x && nextTile.tile.y === this.lastDirChange.y)) { + const surroundings = this.game.map.getSurroundingTiles(nextTile.tile.x, nextTile.tile.y); + const nonSolid = Object.entries(surroundings).filter(surrounding => !surrounding[1].solid); + + if (nonSolid.length === 1) { + this.changeDir({x: -this.dir.x, y: -this.dir.y}); + this.lastDirChange = nextTile.tile; + } else { + const possibleDirections = []; + + nonSolid.forEach(entry => { + const dir = dirNameToValue(entry[0]); + if (dir.x === -this.dir.x && dir.y === -this.dir.y) + return; + possibleDirections.push(dir); + }); + + if (possibleDirections.length === 1) { + this.changeDir(possibleDirections[0]); + this.lastDirChange = nextTile.tile; + } else if (possibleDirections.length === 2) { + const newDirection = possibleDirections.find(dir => dir.x !== -this.dir.x || dir.y !== -this.dir.y); + this.changeDir(newDirection); + this.lastDirChange = nextTile.tile; + } else { + let bestScore = -Infinity; + let bestDir; + + const player = this.game.getPlayer().getNextTile(0, 0).tile; + possibleDirections.forEach(dir => { + const score = this.getDirectionValue(nextTile.tile, dir, player); + if (score > bestScore) { + bestDir = dir; + bestScore = score; + } + }); + + if (bestDir) { + this.changeDir(bestDir); + this.lastDirChange = nextTile.tile; + } + } + } + } + + super.update(deltaTime); + } + + getDirectionValue(tile, dir, goal) { + const newTile = this.game.map.getTile(tile.x + dir.x, tile.y + dir.y); + + if (!newTile || newTile.solid) { + return -Infinity; + } + + const pathFinder = new Pathfinder(this.game.map.tiles); + const path = pathFinder.findShortestPath(newTile, goal); + + // const curDistance = (tile.x - goal.x) ** 2 + (tile.y - goal.y) ** 2; + // const newDistance = (newTile.x - goal.x) ** 2 + (newTile.y - goal.y) ** 2; + // + // let score = curDistance - newDistance; + let score = 1 / path.length * 10; + + if (this.dir.x === -dir.x || this.dir.y === -dir.y) { + score -= Math.abs(score) / 2; + } + + return score; + } +} + +class Pathfinder { + constructor(grid) { + this.grid = []; + this.queue = []; + + grid.forEach((col, x) => { + this.grid.push([]); + col.forEach(tile => { + this.grid[x].push(tile.solid ? 2 : 0); + }); + }); + } + + findShortestPath(start, end) { + this.grid[end.x][end.y] = 10; + + const location = { + x: start.x, + y: start.y, + path: [], + status: 1, + }; + + this.queue = [location]; + + return this.loop(); + } + + loop() { + while (this.queue.length > 0) { + const current = this.queue.shift(); + + let newLocation; + + // Explore north + newLocation = this.exploreInDirection(current, {x: 0, y: -1}); + if (newLocation.status === 10) { + return newLocation.path; + } else if (newLocation.status === 5) { + this.queue.push(newLocation); + } + + // Explore east + newLocation = this.exploreInDirection(current, {x: 1, y: 0}); + if (newLocation.status === 10) { + return newLocation.path; + } else if (newLocation.status === 5) { + this.queue.push(newLocation); + } + + // Explore south + newLocation = this.exploreInDirection(current, {x: 0, y: 1}); + if (newLocation.status === 10) { + return newLocation.path; + } else if (newLocation.status === 5) { + this.queue.push(newLocation); + } + + // Explore west + newLocation = this.exploreInDirection(current, {x: -1, y: 0}); + if (newLocation.status === 10) { + return newLocation.path; + } else if (newLocation.status === 5) { + this.queue.push(newLocation); + } + } + + return false; + } + + locationStatus(location) { + const width = this.grid.length, + height = this.grid[0].length; + const x = location.x, + y = location.y; + + if (x < 0 || x >= width || y < 0 || y >= height) { + return 7; // Invalid + } + if (this.grid[x][y] === 10) { + return 10; // Goal + } + if (this.grid[x][y] !== 0) { + return 6; // Blocked: location is either an obstacle or has already been visited + } else { + return 5; // Valid + } + } + + exploreInDirection(location, direction) { + const newPath = location.path.slice(); + + const x = location.x + direction.x, + y = location.y + direction.y; + + newPath.push({ + x: x, + y: y, + direction: direction, + }); + + const newLocation = { + x: x, + y: y, + path: newPath, + status: -1, // unknown status + }; + newLocation.status = this.locationStatus(newLocation); + + if (newLocation.status === 5) { + this.grid[x][y] = 4; // Set as visited + } + + return newLocation; + } +} \ No newline at end of file diff --git a/MapLoader.js b/MapLoader.js new file mode 100644 index 0000000..5c6c5ea --- /dev/null +++ b/MapLoader.js @@ -0,0 +1,87 @@ +class MapLoader { + constructor(fileName) { + this.fileName = fileName; + this.fileContent = null; + } + + start() { + this.readFile(); + } + + readFile() { + const rawFile = new XMLHttpRequest(); + rawFile.open("GET", this.fileName, false); + rawFile.addEventListener('readystatechange', () => { + if(rawFile.readyState === 4) { + if(rawFile.status === 200 || rawFile.status === 0) { + const content = rawFile.responseText; + console.log(content); + this.fileContent = content; + + this.loadMap(); + } + } + }); + rawFile.send(null); + } + + loadMap() { + const tiles = []; + const mapRaw = this.fileContent; + + const rows = mapRaw.split(/\r?\n/g); + + if(rows.length === 0) { + throw LoadingException; + } + + const width = rows[0].length, + height = rows.length; + let spawn; + + for(let x = 0; x < width; x++) { + tiles.push([]); + } + + rows.forEach((row, y) => { + if(row.length !== width) { + throw `Row length not consistent. Expected ${width}, got ${row.length} at row ${y}`; + } + + for(let x = 0; x < row.length; x++) { + const tileRaw = row[x]; + let tile; + + switch (tileRaw) { + case 'X': + tile = new MapTile(x, y, true); + break; + case '.': + tile = new MapTile(x, y, false, true); + break; + case '-': + tile = new MapTile(x, y, false); + break; + case 'S': + tile = new MapTile(x, y, false); + spawn = {x, y}; + break; + case 'O': + tile = new MapTile(x, y, false); + break; + } + + tiles[x][y] = tile; + } + }); + + this.map = new GameMap(tiles, spawn); + } + + getMap() { + while(!this.map) { + + } + return this.map; + } +} \ No newline at end of file diff --git a/MapTile.js b/MapTile.js new file mode 100644 index 0000000..69566a9 --- /dev/null +++ b/MapTile.js @@ -0,0 +1,79 @@ +class MapTile { + constructor(x, y, isSolid, hasCoin) { + this.x = x; + this.y = y; + this.solid = isSolid; + this.coin = hasCoin; + } + + lookAtNeighbours(neighbours) { + this.borders = {}; + + if(neighbours.left && neighbours.left.solid) { + this.borders.left = true; + } + + if(neighbours.top && neighbours.top.solid) { + this.borders.top = true; + } + + if(neighbours.right && neighbours.right.solid) { + this.borders.right = true; + } + + if(neighbours.bottom && neighbours.bottom.solid) { + this.borders.bottom = true; + } + } + + draw(ctx, conf) { + if (this.solid) { + this.drawSolid(ctx, conf); + } + + if(this.coin) { + ctx.save(); + ctx.translate((this.x + 0.5) * conf.tileSize, (this.y + 0.5) * conf.tileSize); + ctx.fillStyle = "#fff"; + + ctx.beginPath(); + ctx.arc(0, 0, 0.15 * conf.tileSize, 0, 2 * Math.PI); + ctx.fill(); + + ctx.restore(); + } + } + + drawSolid(ctx, conf) { + ctx.save(); + ctx.translate((this.x + 0.5) * conf.tileSize, (this.y + 0.5) * conf.tileSize); + ctx.strokeStyle = "#00f"; + ctx.lineWidth = conf.tileSize * .5; + + ctx.beginPath(); + + if (this.borders.left) { + ctx.moveTo(0.25 * conf.tileSize, 0); + ctx.lineTo(-0.5 * conf.tileSize, 0); + } + + if (this.borders.top) { + ctx.moveTo(0, 0.25 * conf.tileSize); + ctx.lineTo(0, -0.5 * conf.tileSize); + } + + if (this.borders.right) { + ctx.moveTo(-0.25 * conf.tileSize, 0); + ctx.lineTo(0.5 * conf.tileSize, 0); + } + + if (this.borders.bottom) { + ctx.moveTo(0, -0.25 * conf.tileSize); + ctx.lineTo(0, 0.5 * conf.tileSize); + } + + ctx.stroke(); + + ctx.restore(); + } +} \ No newline at end of file diff --git a/MovingObject.js b/MovingObject.js new file mode 100644 index 0000000..b31fce4 --- /dev/null +++ b/MovingObject.js @@ -0,0 +1,98 @@ +class MovingObject extends GameObject { + constructor(game, x, y) { + super(game, x, y); + this.dir = {x: 0, y: 0}; + this.speed = 1; + + this.turnTimeout = null; + } + + changeDir(dir) { + if (this.dir.x === dir.x && this.dir.y === dir.y) { + return; + } + + if (this.dir.x === dir.x || this.dir.y === dir.y) { + clearTimeout(this.turnTimeout); + this.dir = dir; + return; + } + + const isX = this.dir.x !== 0; + let curPos; + let dirPositive; + + if (isX) { + curPos = this.x; + dirPositive = this.dir.x > 0; + } else if (this.dir.y !== 0) { + curPos = this.y; + dirPositive = this.dir.y > 0; + } + + let targetSwitch; + if (dirPositive) { + targetSwitch = Math.ceil(curPos + 0.5) - 0.5; + } else { + targetSwitch = Math.floor(curPos - 0.5) + 0.5; + } + + const distance = Math.abs(curPos - targetSwitch); + const duration = distance / this.speed * 1000; + + clearTimeout(this.turnTimeout); + this.turnTimeout = setTimeout(() => { + if (isX) { + this.x = targetSwitch; + } else { + this.y = targetSwitch; + } + this.dir = dir; + }, duration); + } + + update(deltaTime) { + const ticks = Math.floor(deltaTime / 16); + const tickLength = deltaTime / ticks; + + for(let i = 0; i < ticks; i++) { + this.x += this.dir.x * (this.speed * tickLength / 1000); + this.y += this.dir.y * (this.speed * tickLength / 1000); + + const nextTile = this.getNextTile(this.dir.x, this.dir.y); + + if (!nextTile.tile) { + if (nextTile.distanceX <= 0.5 || nextTile.distanceY <= 0.5) { + + return; + } + } + + if (nextTile.tile.solid) { + if (nextTile.distanceX <= 0.5) { + this.x = nextTile.tile.x - this.dir.x + 0.5; + } + if (nextTile.distanceY <= 0.5) { + this.y = nextTile.tile.y - this.dir.y + 0.5; + } + } + } + } + + getNextTile(offsetX, offsetY) { + const tileX = Math.round(this.x - 0.5); + const tileY = Math.round(this.y - 0.5); + + const nextX = tileX + offsetX; + const nextY = tileY + offsetY; + + const distanceX = Math.abs(this.x - (offsetX >= 0 ? nextX : nextX + 1)); + const distanceY = Math.abs(this.y - (offsetY >= 0 ? nextY : nextY + 1)); + + return { + tile: this.game.map.getTile(nextX, nextY), + distanceX: distanceX, + distanceY: distanceY, + }; + } +} \ No newline at end of file diff --git a/Player.js b/Player.js new file mode 100644 index 0000000..430a4cf --- /dev/null +++ b/Player.js @@ -0,0 +1,96 @@ +class Player extends MovingObject { + constructor(game, x, y) { + super(game, x, y); + this.speed = 3; + + this.score = 0; + + this.mouthAngle = 90; // = 90° + this.mouthDecreasing = true; + } + + draw(ctx, conf) { + const radius = conf.tileSize * 0.5; + const cx = this.x * conf.tileSize; + const cy = this.y * conf.tileSize; + const mouthAngle = this.mouthAngle / 180 * Math.PI; + + ctx.fillStyle = '#ff0'; + + ctx.save(); + ctx.translate(cx, cy); + ctx.rotate(this.getRotateDir()); + + ctx.beginPath(); + + ctx.moveTo(0, 0); + ctx.arc(0, 0, radius, mouthAngle / 2, 2 * Math.PI - mouthAngle / 2, false); + ctx.lineTo(0, 0); + + ctx.fill(); + + ctx.restore(); + } + + update(deltaTime) { + super.update(deltaTime); + + this.checkForCollisions(); + + // Collect coins + const currentTile = this.getNextTile(0, 0); + + if(currentTile.tile.coin) { + if(currentTile.distanceX <= 0.5 || currentTile.distanceY <= 0.5) { + currentTile.tile.coin = false; + this.score += 10; + } + } + + if(this.mouthDecreasing) { + this.mouthAngle -= this.speed * 4; + if(this.mouthAngle <= 0) { + this.mouthAngle = 0; + this.mouthDecreasing = false; + } + } else { + this.mouthAngle += this.speed * 4; + if(this.mouthAngle >= 90) { + this.mouthAngle = 90; + this.mouthDecreasing = true; + } + } + } + + checkForCollisions() { + this.game.gameObjects.forEach(object => { + if(object === this) { + return; + } + + if(Math.abs(object.x - this.x) < 1 && Math.abs(object.y - this.y) < 1) { + if(object instanceof Ghost) { + this.gameOver(); + } + } + }); + } + + getRotateDir() { + if(this.dir.x === -1) { + return Math.PI; + } + if(this.dir.y === 1) { + return Math.PI / 2; + } + if(this.dir.y === -1) { + return 3 / 2 * Math.PI; + } + + return 0; + } + + gameOver() { + this.game.gameOver = true; + } +} \ No newline at end of file diff --git a/helper-methods.js b/helper-methods.js new file mode 100644 index 0000000..1452807 --- /dev/null +++ b/helper-methods.js @@ -0,0 +1,5 @@ +function calculateTileSize(width, height, tilesX, tilesY) { + const tileWidth = width / tilesX, + tileHeight = height / tilesY; + return Math.floor(Math.min(tileWidth, tileHeight)); +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..5c65bfc --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ + + + + + Pacman + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/levels/level1.pacmap b/levels/level1.pacmap new file mode 100644 index 0000000..2b4cb19 --- /dev/null +++ b/levels/level1.pacmap @@ -0,0 +1,21 @@ +XXXXXXXXXXXXXXXXXXXXXXXXXXX +X............X............X +X.XXX.XXXXXX.X.XXXXXX.XXX.X +XOX-X.X-X.........X-X.X-XOX +X.XXX.XXX.XXXXXXX.XXX.XXX.X +X............X............X +X.XXX.XXXXXX-X-XXXXXX.XXX.X +X.......X---------X.......X +XXXXXXX.X-XXXXXXX-X.XXXXXXX +XXXXXXX.X-X-----X-X.XXXXXXX +-------.--X-----X--.------- +XXXXXXX.X-X-----X-X.XXXXXXX +XXXXXXX.X-XXXXXXX-X.XXXXXXX +X............X............X +XOXXX.XXXXXX.X.XXXXXX.XXXOX +X...X........S........X...X +XXX.XXX.X.XXXXXXX.X.XXX.XXX +X.......X....X....X.......X +X.XXXXXXXXXX.X.XXXXXXXXXX.X +X.........................X +XXXXXXXXXXXXXXXXXXXXXXXXXXX \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..0155513 --- /dev/null +++ b/main.js @@ -0,0 +1,2 @@ +const game = new Game(16, 8); +game.start(); \ No newline at end of file