Skip to content

Commit

Permalink
workers work?
Browse files Browse the repository at this point in the history
  • Loading branch information
netanel-haber committed May 27, 2023
1 parent d5c3cd0 commit 48b539e
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 63 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ module.exports = {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/promise-function-async": "off",
},
};
36 changes: 36 additions & 0 deletions src/ai/bestScore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type BoardState } from "../classes/BoardState";
import { forEachCell, gridValToColor } from "../utils";

const valueToScore = [1, 1.2, 1, 1.2, 0] as const;
const calculateScore = ({ grid, turn }: BoardState) => {
let score = 0;
forEachCell((r, c) => {
const cell = grid[r][c];
const color = gridValToColor[cell];
if (!color) return;
const value = valueToScore[cell];
score += color === turn ? value : -value;
});
return score;
};

const ODD_DEPTH = 5;
if (ODD_DEPTH % 2 === 0) throw new Error("Depth must be odd");

export function bestScore(state: BoardState, depth = ODD_DEPTH) {
if (!depth) return calculateScore(state);

let max = 0;
for (const [, potentialMoves] of state.getAllLegalMovesForColor()) {
for (const { updates } of potentialMoves) {
const score = bestScore(
state.updatedGrid(updates).updateCurrentTurn(),
depth - 1
);
if (score >= max) {
max = score;
}
}
}
return max;
}
98 changes: 35 additions & 63 deletions src/ai/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,38 @@ import { type BoardState } from "../classes/BoardState";
import { COMPUTER } from "../consts";
import { type StateControllers } from "../types";

import { forEachCell, gridValToColor } from "../utils";

const valueToScore = [1, 1.2, 1, 1.2, 0] as const;

export const calculateScore = ({ grid, turn }: BoardState) => {
let score = 0;
forEachCell((r, c) => {
const cell = grid[r][c];
const color = gridValToColor[cell];
if (!color) return;
const value = valueToScore[cell];
score += color === turn ? value : -value;
});
return score;
};

const ODD_DEPTH = 5;
if (ODD_DEPTH % 2 === 0) {
throw new Error("Depth must be odd");
}
import { enqueue } from "./workers";

const defaultBest = {
score: 0,
move: { finalColumn: 0, finalRow: 0, startColumn: 0, startRow: 0 },
};
function bestMove(state: BoardState) {
function bestScore(state: BoardState, depth = ODD_DEPTH) {
if (!depth) {
return calculateScore(state);
}

let max = 0;
for (const [, potentialMoves] of state.getAllLegalMovesForColor()) {
for (const { updates } of potentialMoves) {
const score = bestScore(
state.updatedGrid(updates).updateCurrentTurn(),
depth - 1
);
if (score >= max) {
max = score;
}
}
}
return max;
}
let best = defaultBest;
for (const [cell, potentialMoves] of state.getAllLegalMovesForColor()) {
for (const { updates, finalCell } of potentialMoves) {
const score = bestScore(state.updatedGrid(updates).updateCurrentTurn());
if (score >= best.score) {
best = {
score,
move: {
startRow: cell.row,
startColumn: cell.column,
finalRow: finalCell.row,
finalColumn: finalCell.column,
},
};
}
}
}
return best.move;
async function bestMove(state: BoardState) {
const candidates = await Promise.all(
state
.getAllLegalMovesForColor()
.flatMap(([{ row, column }, potentialMoves]) =>
potentialMoves.map(
({ updates, finalCell: { row: finalRow, column: finalColumn } }) =>
enqueue(state.updatedGrid(updates).updateCurrentTurn()).then(
(score) => ({
move: {
startRow: row,
startColumn: column,
finalRow,
finalColumn,
},
score,
})
)
)
)
);
return candidates.reduce(
(acc, cur) => (cur.score >= acc.score ? cur : acc),
defaultBest
).move;
}

export const doAiMove = (
Expand All @@ -74,11 +43,14 @@ export const doAiMove = (
if (state.turn !== COMPUTER) return;
setTimeout(() => {
const t0 = performance.now();
const { finalRow, finalColumn, startRow, startColumn } = bestMove(state);
const elapsed = performance.now() - t0;
const maxiumum400 = Math.max(250 - elapsed, 0);
setTimeout(() => {
handleMove(finalRow, finalColumn, startRow, startColumn);
}, maxiumum400);
bestMove(state)
.then(({ finalRow, finalColumn, startRow, startColumn }) => {
const elapsed = performance.now() - t0;
const maxiumum400 = Math.max(250 - elapsed, 0);
setTimeout(() => {
handleMove(finalRow, finalColumn, startRow, startColumn);
}, maxiumum400);
})
.catch(console.error);
}, 50);
};
15 changes: 15 additions & 0 deletions src/ai/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BoardState } from "../classes/BoardState";

import { bestScore } from "./bestScore";
import { type FinishedWork } from "./workers";

self.addEventListener("message", (e) => {
const state = BoardState.deserialize(e.data);
const stringGrid = String(state.grid);
const score = bestScore(state);
const final: FinishedWork = {
score,
stringGrid,
};
postMessage(final);
});
35 changes: 35 additions & 0 deletions src/ai/workers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type BoardState } from "../classes/BoardState";

import Worker from "./worker?worker";

const WORKERS = window.navigator.hardwareConcurrency + 2;
const pool = Array.from({ length: WORKERS }).map(() => new Worker());

let i = 0;
const getWorker = () => {
const next = pool[i];
i = (i + 1) % WORKERS;
return next;
};

export type FinishedWork = {
score: number;
stringGrid: string;
};

export const enqueue = (state: BoardState): Promise<number> =>
new Promise((resolve) => {
const worker = getWorker();
const initialStringGrid = String(state.grid);
worker.addEventListener("message", onMessage);
worker.postMessage(state);

function onMessage({
data: { score, stringGrid },
}: MessageEvent<FinishedWork>) {
if (initialStringGrid === stringGrid) {
resolve(score);
worker.removeEventListener("message", onMessage);
}
}
});
11 changes: 11 additions & 0 deletions src/classes/BoardState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,15 @@ export class BoardState {
turn: this.turn,
};
}

/**
* From serialized state, in worker
*/
static deserialize(serialized: {
[K in keyof BoardState]: BoardState[K];
}): BoardState {
return new BoardState(serialized.grid, serialized.turn, {
flaggedCell: serialized.flaggedCell,
});
}
}

0 comments on commit 48b539e

Please sign in to comment.