generated from michael/webpack-base
simultaneous labs
This commit is contained in:
parent
5444feee99
commit
66bab68e24
@ -1,4 +1,4 @@
|
|||||||
import { Engine, Keys, randint, UI, Vec2, vec2 } from './game-engine';
|
import { Engine, randint, UI, Vec2, vec2 } from './game-engine';
|
||||||
import { SnakeBrain } from './snake-brain';
|
import { SnakeBrain } from './snake-brain';
|
||||||
import { SGSHashSet, Snake, SnakeGameState, SnakeGameStateWithHistory } from './types';
|
import { SGSHashSet, Snake, SnakeGameState, SnakeGameStateWithHistory } from './types';
|
||||||
|
|
||||||
@ -10,8 +10,27 @@ const CENTER_Y = BOARD_SIZE / 2;
|
|||||||
|
|
||||||
export const BOARD_SQUARES = BOARD_SIZE / SQUARE_SIZE;
|
export const BOARD_SQUARES = BOARD_SIZE / SQUARE_SIZE;
|
||||||
|
|
||||||
|
interface SnakeGameTrainerLab {
|
||||||
|
id: number;
|
||||||
|
brain: SnakeBrain;
|
||||||
|
state: SnakeGameStateWithHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SnakeGameTrainerState {
|
||||||
|
labs: SnakeGameTrainerLab[];
|
||||||
|
}
|
||||||
|
|
||||||
// general functions -----------------------------------------------------------
|
// general functions -----------------------------------------------------------
|
||||||
|
|
||||||
|
function getStartSnakeGameState(): SnakeGameStateWithHistory {
|
||||||
|
const dead = false;
|
||||||
|
const snake = [vec2(1, 1), vec2(2, 1), vec2(3, 1)];
|
||||||
|
const apple = getRandApplePos();
|
||||||
|
const history = new SGSHashSet();
|
||||||
|
|
||||||
|
return { dead, snake, apple, history };
|
||||||
|
}
|
||||||
|
|
||||||
function shallowCopySGS(state: SnakeGameState): SnakeGameState {
|
function shallowCopySGS(state: SnakeGameState): SnakeGameState {
|
||||||
return {
|
return {
|
||||||
dead: state.dead,
|
dead: state.dead,
|
||||||
@ -28,62 +47,58 @@ function getSnakeNextSquare(snake: Snake, dir: Vec2) {
|
|||||||
return snake[snake.length - 1]!.add(dir);
|
return snake[snake.length - 1]!.add(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStartSnakeGameState(): SnakeGameStateWithHistory {
|
|
||||||
const dead = false;
|
|
||||||
const snake = [vec2(1, 1), vec2(2, 1), vec2(3, 1)];
|
|
||||||
const apple = getRandApplePos();
|
|
||||||
const history = new SGSHashSet();
|
|
||||||
|
|
||||||
return { dead, snake, apple, history };
|
|
||||||
}
|
|
||||||
|
|
||||||
// control code ----------------------------------------------------------------
|
// control code ----------------------------------------------------------------
|
||||||
|
|
||||||
export default function runCanvas(canvas: HTMLCanvasElement) {
|
export default function runCanvas(canvas: HTMLCanvasElement) {
|
||||||
const ui = new UI(canvas);
|
const ui = new UI(canvas);
|
||||||
const keys = new Keys();
|
|
||||||
const engine = new Engine({ updateDelay: 50 });
|
const engine = new Engine({ updateDelay: 50 });
|
||||||
|
|
||||||
// game logic --------------------------------------------------------------
|
// game logic --------------------------------------------------------------
|
||||||
|
|
||||||
const brain = SnakeBrain.fromRandom({ hiddenLayerNodes: 12 });
|
const trainer: SnakeGameTrainerState = {
|
||||||
const state = getStartSnakeGameState();
|
labs: Array.from({ length: 4 }).map((_, idx) => ({
|
||||||
|
id: idx + 1,
|
||||||
|
state: getStartSnakeGameState(),
|
||||||
|
brain: SnakeBrain.fromRandom({ hiddenLayerNodes: 12 }),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
const { dead, snake, apple, history } = state;
|
for (const lab of trainer.labs) {
|
||||||
|
const { state, brain } = lab;
|
||||||
|
const { dead, snake, apple, history } = state;
|
||||||
|
|
||||||
if (dead) return;
|
if (dead) continue;
|
||||||
|
|
||||||
// kill cycling snakes
|
// kill cycling snakes
|
||||||
if (history.has(state)) {
|
if (history.has(state)) {
|
||||||
state.dead = true;
|
state.dead = true;
|
||||||
return;
|
continue;
|
||||||
|
}
|
||||||
|
history.add(shallowCopySGS(state));
|
||||||
|
|
||||||
|
// perform ai
|
||||||
|
const dir = brain.think(state);
|
||||||
|
|
||||||
|
// NOTE: brain.think handles out-of-bounds/tail intersect checking when it identifies
|
||||||
|
// valid move options. it will return 'dead' if there are no valid moves
|
||||||
|
if (dir === 'dead') {
|
||||||
|
state.dead = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextHead = getSnakeNextSquare(snake, dir);
|
||||||
|
|
||||||
|
// check for snake hitting apple
|
||||||
|
if (nextHead.eq(apple)) {
|
||||||
|
state.apple = getRandApplePos();
|
||||||
|
} else {
|
||||||
|
snake.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the snake
|
||||||
|
snake.push(nextHead);
|
||||||
}
|
}
|
||||||
history.add(shallowCopySGS(state));
|
|
||||||
|
|
||||||
// perform ai
|
|
||||||
const dir = brain.think(state);
|
|
||||||
|
|
||||||
// NOTE: brain.think handles out-of-bounds/tail intersect checking when it identifies
|
|
||||||
// valid move options. it will return 'dead' if there are no valid moves
|
|
||||||
if (dir === 'dead') {
|
|
||||||
state.dead = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextHead = getSnakeNextSquare(snake, dir);
|
|
||||||
|
|
||||||
// check for snake hitting apple
|
|
||||||
if (nextHead.eq(apple)) {
|
|
||||||
state.apple = getRandApplePos();
|
|
||||||
} else {
|
|
||||||
snake.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
// move the snake
|
|
||||||
snake.push(nextHead);
|
|
||||||
|
|
||||||
keys.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rendering ---------------------------------------------------------------
|
// rendering ---------------------------------------------------------------
|
||||||
@ -93,23 +108,34 @@ export default function runCanvas(canvas: HTMLCanvasElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const { dead, snake, apple } = state;
|
|
||||||
|
|
||||||
ui.setFillStyle('#333333');
|
ui.setFillStyle('#333333');
|
||||||
ui.clearCanvas();
|
ui.clearCanvas();
|
||||||
|
|
||||||
ui.setFillStyle('#277edb');
|
for (const lab of trainer.labs) {
|
||||||
for (const square of snake.slice(0, snake.length - 1)) {
|
const { state, brain } = lab;
|
||||||
drawSquare(square);
|
const { dead, snake, apple } = state;
|
||||||
|
|
||||||
|
// tail
|
||||||
|
ui.setFillStyle('#277edb');
|
||||||
|
for (const square of snake.slice(0, snake.length - 1)) {
|
||||||
|
drawSquare(square);
|
||||||
|
}
|
||||||
|
|
||||||
|
// head
|
||||||
|
if (dead) {
|
||||||
|
ui.setFillStyle('#ff0000');
|
||||||
|
} else {
|
||||||
|
ui.setFillStyle('#27b4db');
|
||||||
|
}
|
||||||
|
drawSquare(snake[snake.length - 1]!);
|
||||||
|
|
||||||
|
// apple
|
||||||
|
ui.setFillStyle('#e01851');
|
||||||
|
drawSquare(apple);
|
||||||
}
|
}
|
||||||
ui.setFillStyle('#29d17a');
|
|
||||||
drawSquare(snake[snake.length - 1]!);
|
|
||||||
|
|
||||||
ui.setFillStyle('#e01851');
|
if (trainer.labs.findIndex(l => !l.state.dead) === -1) {
|
||||||
drawSquare(apple);
|
const text = 'All Died';
|
||||||
|
|
||||||
if (dead) {
|
|
||||||
const text = 'You Died';
|
|
||||||
ui.setFont('40px sans-serif');
|
ui.setFont('40px sans-serif');
|
||||||
const m = ui.measureText(text);
|
const m = ui.measureText(text);
|
||||||
|
|
||||||
@ -122,10 +148,9 @@ export default function runCanvas(canvas: HTMLCanvasElement) {
|
|||||||
ui.setFillStyle('#cc0000');
|
ui.setFillStyle('#cc0000');
|
||||||
ui.setTextAlign('center');
|
ui.setTextAlign('center');
|
||||||
ui.setTextBaseline('middle');
|
ui.setTextBaseline('middle');
|
||||||
ui.fillText('You Died', BOARD_SIZE / 2, BOARD_SIZE / 2);
|
ui.fillText(text, BOARD_SIZE / 2, BOARD_SIZE / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keys.bindKeys();
|
|
||||||
engine.run(update, render);
|
engine.run(update, render);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user