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 { SGSHashSet, Snake, SnakeGameState, SnakeGameStateWithHistory } from './types';
@ -10,8 +10,27 @@ const CENTER_Y = BOARD_SIZE / 2;
export const BOARD_SQUARES = BOARD_SIZE / SQUARE_SIZE;
interface SnakeGameTrainerLab {
id: number;
brain: SnakeBrain;
state: SnakeGameStateWithHistory;
}
interface SnakeGameTrainerState {
labs: SnakeGameTrainerLab[];
}
// 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 {
return {
dead: state.dead,
@ -28,62 +47,58 @@ function getSnakeNextSquare(snake: Snake, dir: Vec2) {
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 ----------------------------------------------------------------
export default function runCanvas(canvas: HTMLCanvasElement) {
const ui = new UI(canvas);
const keys = new Keys();
const engine = new Engine({ updateDelay: 50 });
// game logic --------------------------------------------------------------
const brain = SnakeBrain.fromRandom({ hiddenLayerNodes: 12 });
const state = getStartSnakeGameState();
const trainer: SnakeGameTrainerState = {
labs: Array.from({ length: 4 }).map((_, idx) => ({
id: idx + 1,
state: getStartSnakeGameState(),
brain: SnakeBrain.fromRandom({ hiddenLayerNodes: 12 }),
})),
};
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
if (history.has(state)) {
state.dead = true;
return;
// kill cycling snakes
if (history.has(state)) {
state.dead = true;
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 ---------------------------------------------------------------
@ -93,23 +108,34 @@ export default function runCanvas(canvas: HTMLCanvasElement) {
}
function render() {
const { dead, snake, apple } = state;
ui.setFillStyle('#333333');
ui.clearCanvas();
ui.setFillStyle('#277edb');
for (const square of snake.slice(0, snake.length - 1)) {
drawSquare(square);
for (const lab of trainer.labs) {
const { state, brain } = lab;
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');
drawSquare(apple);
if (dead) {
const text = 'You Died';
if (trainer.labs.findIndex(l => !l.state.dead) === -1) {
const text = 'All Died';
ui.setFont('40px sans-serif');
const m = ui.measureText(text);
@ -122,10 +148,9 @@ export default function runCanvas(canvas: HTMLCanvasElement) {
ui.setFillStyle('#cc0000');
ui.setTextAlign('center');
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);
}