simultaneous labs

This commit is contained in:
Michael Peters 2024-08-03 15:55:29 -07:00
parent 5444feee99
commit 66bab68e24

View File

@ -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,36 +47,33 @@ 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() {
for (const lab of trainer.labs) {
const { state, brain } = lab;
const { dead, snake, apple, history } = state; 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)); history.add(shallowCopySGS(state));
@ -68,7 +84,7 @@ export default function runCanvas(canvas: HTMLCanvasElement) {
// valid move options. it will return 'dead' if there are no valid moves // valid move options. it will return 'dead' if there are no valid moves
if (dir === 'dead') { if (dir === 'dead') {
state.dead = true; state.dead = true;
return; continue;
} }
const nextHead = getSnakeNextSquare(snake, dir); const nextHead = getSnakeNextSquare(snake, dir);
@ -82,8 +98,7 @@ export default function runCanvas(canvas: HTMLCanvasElement) {
// move the snake // move the snake
snake.push(nextHead); 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();
for (const lab of trainer.labs) {
const { state, brain } = lab;
const { dead, snake, apple } = state;
// tail
ui.setFillStyle('#277edb'); ui.setFillStyle('#277edb');
for (const square of snake.slice(0, snake.length - 1)) { for (const square of snake.slice(0, snake.length - 1)) {
drawSquare(square); drawSquare(square);
} }
ui.setFillStyle('#29d17a');
// head
if (dead) {
ui.setFillStyle('#ff0000');
} else {
ui.setFillStyle('#27b4db');
}
drawSquare(snake[snake.length - 1]!); drawSquare(snake[snake.length - 1]!);
// apple
ui.setFillStyle('#e01851'); ui.setFillStyle('#e01851');
drawSquare(apple); drawSquare(apple);
}
if (dead) { if (trainer.labs.findIndex(l => !l.state.dead) === -1) {
const text = 'You Died'; const text = 'All 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);
} }