From 7d0b953fbea78b684f9b12ec60880e0bf23d6f10 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Mon, 25 Jul 2016 21:30:55 -0500 Subject: [PATCH] SpikeWars: refactor rendering out into a separate module. Finished with basic game loop. --- spike-wars/src/main/html/index.html | 1 + spike-wars/src/main/js/game-logic.js | 7 +- spike-wars/src/main/js/game-types.js | 2 +- spike-wars/src/main/js/spike-wars-artist.js | 52 +++++++++++ spike-wars/src/main/js/spike-wars.js | 99 +++++++++++++++------ 5 files changed, 129 insertions(+), 32 deletions(-) create mode 100644 spike-wars/src/main/js/spike-wars-artist.js diff --git a/spike-wars/src/main/html/index.html b/spike-wars/src/main/html/index.html index 132bfae..445241a 100644 --- a/spike-wars/src/main/html/index.html +++ b/spike-wars/src/main/html/index.html @@ -16,6 +16,7 @@

Spike Wars!

+
Pause diff --git a/spike-wars/src/main/js/game-logic.js b/spike-wars/src/main/js/game-logic.js index 375557d..e9aff19 100644 --- a/spike-wars/src/main/js/game-logic.js +++ b/spike-wars/src/main/js/game-logic.js @@ -1,15 +1,12 @@ // @flow import type Board from './game-types'; import type GameState from './game-types'; +import SpikeWarsArtist from './spike-wars-artist'; -function render(state: GameState, g: CanvasRenderingContext2D): void { - var space = g.measureText(state.count + ''); - g.clearRect(100, 100, space.width, 50); - g.fillText(state.count + '', 100, 100); +function render(state: GameState, artist: SpikeWarsArtist): void { } function update(state: GameState): GameState { - state.count++; return state; } diff --git a/spike-wars/src/main/js/game-types.js b/spike-wars/src/main/js/game-types.js index 3bf5044..7468f8f 100644 --- a/spike-wars/src/main/js/game-types.js +++ b/spike-wars/src/main/js/game-types.js @@ -1,5 +1,5 @@ +export type Piece = "WALL" | "PLAYER" | "DEBUG"; export type Board = Array; export type GameState = { board: Board, - count: number }; diff --git a/spike-wars/src/main/js/spike-wars-artist.js b/spike-wars/src/main/js/spike-wars-artist.js new file mode 100644 index 0000000..5d514c9 --- /dev/null +++ b/spike-wars/src/main/js/spike-wars-artist.js @@ -0,0 +1,52 @@ +// @flow +type ConstructorOptions = { + canvasSelector?: string, + rows: number, + columns: number }; + +export default class SpikeWarsArtist { + canvas: HTMLCanvasElement; + + // This is a reference to the drawing API: how we tell the computer to draw + // on the canvas. It is a 2D context, meaning we draw in two-dimensions, like + // on paper. + canvas2d: CanvasRenderingContext2D; + + rows: number; // How many rows are on our game board? + cols: number; // How many columns are on our game board? + tileWidth: number; // How wide is one of our game board spots? + tileHeight: number; // How tall is one of our game bouard spots? + + constructor({canvasSelector = 'canvas', rows, columns}: ConstructorOptions) { + this.canvas = ((document.querySelector(canvasSelector): any): HTMLCanvasElement); + + var c2d: ?CanvasRenderingContext2D = this.canvas.getContext('2d'); + if (c2d) this.canvas2d = c2d; + else throw "Cannot get 2D rendering context!"; // TODO: better handling + + this.rows = rows; + this.cols = columns; + + this.tileHeight = Math.ceil(this.canvas.height / this.rows); + this.tileWidth = Math.ceil(this.canvas.width / this.cols); + + console.log("SpikeWarsArtist: \n\trows: ", this.rows, "\tcols: ", this.cols, + "\n\ttileWidth: ", this.tileWidth, "\ttileHeight: ", this.tileHeight); + } + + coord2idx(row: number, col: number): number { return (this.cols * row) + col; } + idx2row(idx: number): number { return Math.floor(idx/this.cols); } + idx2col(idx: number): number { return idx % this.cols; } + + drawSolidColor(row: number, col: number, color: string): void { + this.drawSolidColorIdx(this.coord2idx(row, col), color); } + + drawSolidColorIdx(idx: number, color: string): void { + this.canvas2d.fillStyle = color; + + this.canvas2d.fillRect( + (idx % this.cols) * this.tileWidth, + Math.floor(idx / this.cols) * this.tileHeight, + this.tileWidth, this.tileHeight); } + +} diff --git a/spike-wars/src/main/js/spike-wars.js b/spike-wars/src/main/js/spike-wars.js index f302427..922e849 100644 --- a/spike-wars/src/main/js/spike-wars.js +++ b/spike-wars/src/main/js/spike-wars.js @@ -1,15 +1,20 @@ // @flow +import Stats from 'stats.js'; +import SpikeWarsArtist from './spike-wars-artist'; import {update, render} from './game-logic'; import type Board from './game-types'; import type GameState from './game-types'; +import type Piece from './game-types'; -type SpikeWarsOptions = {fps?: number}; +const WALL = "WALL"; +const DEBUG = "DEBUG"; + +type SpikeWarsOptions = {fps?: number, stats?: Object}; export default class SpikeWars { - ROWS: number; // How many rows does our board have? - COLS: number; // How many columns does our board have? - TILE_SIZE: number; // How big is each square on our board? + rows: number; + cols: number; // This is where we keep track of all the things that are happening in the // game. @@ -18,46 +23,64 @@ export default class SpikeWars { nextGameTick: number; // At what time does the next game update happen? skipTicks: number; // How long does the game wait between updates? - // This is where we keep a reference to our canvas, what we are drawing on. - canvas: HTMLCanvasElement; + // This is the thing that knows how to draw stuff. + artist: SpikeWarsArtist; - // This is a reference to the drawing API: how we tell the computer to draw - // on the canvas. It is a 2D context, meaning we draw in two-dimensions, like - // on paper. - canvas2d: CanvasRenderingContext2D; + stats: ?Object; + pauseCheckbox: HTMLInputElement; + paused: boolean; // The constructor is the function that is run when somebody first creates a // new SpikeWars instance. This is where we set stuff up for the first time. - constructor({fps = 30}: SpikeWarsOptions) { + constructor({fps = 30, stats}: SpikeWarsOptions) { - // TODO: detect/set automatically instead of hard-coding - this.ROWS = 8; - this.COLS = 20; - this.TILE_SIZE = 48; + // Get our artist + this.rows = 16; + this.cols = 40; + this.artist = new SpikeWarsArtist({rows: this.rows, columns: this.cols}); this.state = {board: this.newBoard(), count: 0}; // create a new board - // Find our canvas and ask for the reference to the drawing API. - this.canvas = window.document.getElementsByTagName('canvas')[0]; - this.canvas2d = this.canvas.getContext('2d'); + this.stats = stats; + this.pauseCheckbox = window.document.getElementsByTagName('input')[0]; + this.pauseCheckbox.checked = false; + this.pauseCheckbox.addEventListener('change', () => { this.toggleGamePaused() }); + this.paused = false; + + this.skipTicks = Math.ceil(1000 / fps); // TODO: register event handlers on the canvas element. - } // This function creates a new board. - newBoard(): Board { return new Array(this.ROWS * this.COLS); } + newBoard(): Board { + var board = new Array(this.rows * this.cols); + for (var i = 0; i < board.length; i++) { + var col = i % this.cols; + var row = Math.floor(i / this.cols); + if (col === 0 || col === (this.cols - 1) || + row === 0 || row === (this.rows - 1)) board[i] = WALL; } + return board; } + + toggleGamePaused(): void { + this.paused = this.pauseCheckbox.checked; + + if (!this.paused) { + this.nextGameTick = new Date().getTime(); + this.gameLoop(); } + } // The game loop is where the game actually runs. It is called a loop because // it runs over and over and over, in a loop until it is time to stop the // game. gameLoop(): void { - // TODO: break the game loop upon exit conditions + if (this.paused) return; // We are going to ask the browser to call us again next time we have a // chance to draw. We're doing this first because we want to make sure we // reserve our place in line before we get busy doing stuff. - window.requestAnimationFrame(this.gameLoop); + window.requestAnimationFrame(() => { this.gameLoop(); }); + //window.setTimeout(() => { this.gameLoop(); }); // Now we need to check if it is time to do stuff. If our computer is // really fast the game loop might run really fast, or if our computer is @@ -68,6 +91,8 @@ export default class SpikeWars { // // Is it time for the next game tick? if ((new Date()).getTime() > this.nextGameTick) { + if (this.stats) this.stats.begin(); + // Yes! So the first thing we need to do is figure out when the next tick // should be. this.nextGameTick += this.skipTicks; @@ -76,13 +101,35 @@ export default class SpikeWars { this.state = update(this.state); // And we re-draw the world. - render(this.state, this.canvas2d); } + render(this.state, this.artist); + if (this.stats) this.stats.end(); } } - startGame(): void { this.gameLoop(); } + startGame(): void { + this.nextGameTick = new Date().getTime(); + this.drawBoard(this.artist, this.state.board); + this.gameLoop(); } + + drawBoard(artist: SpikeWarsArtist, board: Board): void { + for (var i = 0; i < board.length; i++) { + var color: string; + switch (board[i]) { + case WALL: color = 'black'; break; + case DEBUG: color = 'red'; break; + default: color = 'white'; + } + this.artist.drawSolidColorIdx(i, color); } } } // instantiate the instance of SpikeWars -window.spikeWars = new SpikeWars({fps: 30}); -window.spikeWars.startGame(); +if (false) { + var stats = new Stats(); + stats.showPanel(0); + document.body.appendChild(stats.dom); + window.spikeWars = new SpikeWars({fps: 30, stats: stats}); } + +else { window.spikeWars = new SpikeWars({fps: 30}); } + +//window.spikeWars.startGame(); +