Connect four initial
ci/woodpecker/push/tools-wasm-pack-plugin Pipeline was successful
Details
ci/woodpecker/push/wasm-o-x-rust Pipeline was successful
Details
ci/woodpecker/push/wasm-connect-four-rust Pipeline was successful
Details
ci/woodpecker/push/frontend Pipeline failed
Details
ci/woodpecker/manual/tools-wasm-pack-plugin Pipeline failed
Details
ci/woodpecker/manual/wasm-o-x-rust unknown status
Details
ci/woodpecker/manual/wasm-connect-four-rust unknown status
Details
ci/woodpecker/manual/frontend unknown status
Details
ci/woodpecker/push/tools-wasm-pack-plugin Pipeline was successful
Details
ci/woodpecker/push/wasm-o-x-rust Pipeline was successful
Details
ci/woodpecker/push/wasm-connect-four-rust Pipeline was successful
Details
ci/woodpecker/push/frontend Pipeline failed
Details
ci/woodpecker/manual/tools-wasm-pack-plugin Pipeline failed
Details
ci/woodpecker/manual/wasm-o-x-rust unknown status
Details
ci/woodpecker/manual/wasm-connect-four-rust unknown status
Details
ci/woodpecker/manual/frontend unknown status
Details
This commit is contained in:
parent
ca29e826f1
commit
f5da6c01aa
|
@ -8,7 +8,7 @@
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@game-algorithms/connect-four-rust": "^0.0.1",
|
"@game-algorithms/connect-four-rust": "^0.0.2",
|
||||||
"@game-algorithms/o-x-rust": "^0.1.0"
|
"@game-algorithms/o-x-rust": "^0.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -82,9 +82,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@game-algorithms/connect-four-rust": {
|
"node_modules/@game-algorithms/connect-four-rust": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fconnect-four-rust/-/0.0.1/connect-four-rust-0.0.1.tgz",
|
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fconnect-four-rust/-/0.0.2/connect-four-rust-0.0.2.tgz",
|
||||||
"integrity": "sha512-XFVCupzmxi6lL9jfrETenwQwo7JDvGby8PB2jOfRuRpmGBZwgx9GQvc9euw+LwlGCXUhN8zYqKIdoU7IDfd6ng==",
|
"integrity": "sha512-wJ10cBbrMFVqv6gjrWEGNt0kBk1BoDtz8x3VFpiZjcwvarIGh0y1x0F+VnKJiYNoDh3ozdtaeiOFViDluH3xOw==",
|
||||||
"license": "GNU GPLv3"
|
"license": "GNU GPLv3"
|
||||||
},
|
},
|
||||||
"node_modules/@game-algorithms/o-x-rust": {
|
"node_modules/@game-algorithms/o-x-rust": {
|
||||||
|
@ -2935,9 +2935,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@game-algorithms/connect-four-rust": {
|
"@game-algorithms/connect-four-rust": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fconnect-four-rust/-/0.0.1/connect-four-rust-0.0.1.tgz",
|
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fconnect-four-rust/-/0.0.2/connect-four-rust-0.0.2.tgz",
|
||||||
"integrity": "sha512-XFVCupzmxi6lL9jfrETenwQwo7JDvGby8PB2jOfRuRpmGBZwgx9GQvc9euw+LwlGCXUhN8zYqKIdoU7IDfd6ng=="
|
"integrity": "sha512-wJ10cBbrMFVqv6gjrWEGNt0kBk1BoDtz8x3VFpiZjcwvarIGh0y1x0F+VnKJiYNoDh3ozdtaeiOFViDluH3xOw=="
|
||||||
},
|
},
|
||||||
"@game-algorithms/o-x-rust": {
|
"@game-algorithms/o-x-rust": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
|
@ -33,6 +33,6 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@game-algorithms/o-x-rust": "^0.1.0",
|
"@game-algorithms/o-x-rust": "^0.1.0",
|
||||||
"@game-algorithms/connect-four-rust": "^0.0.1"
|
"@game-algorithms/connect-four-rust": "^0.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { type SvelteComponent, createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
export let board: number[];
|
||||||
|
export let w = 7;
|
||||||
|
export let iconMap: Record<number, typeof SvelteComponent>;
|
||||||
|
export let disabled = false;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const gridTemplate = Array(w).fill("1fr").join(" ");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if top item of column is empty
|
||||||
|
*
|
||||||
|
* @param idx Index of any cell in the column
|
||||||
|
* @returns Whether the column has an empty top cell
|
||||||
|
*/
|
||||||
|
function columnTopEmpty(idx: number): boolean {
|
||||||
|
const col = idx % w;
|
||||||
|
return board[col] === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate handler function for board cell click
|
||||||
|
*
|
||||||
|
* @param idx Index of cell for event
|
||||||
|
* @returns Handler function
|
||||||
|
*/
|
||||||
|
function handleClick(idx: number) {
|
||||||
|
return () => dispatch("click", {
|
||||||
|
col: idx % w
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="grid" style={`grid-template-columns: ${gridTemplate};`}>
|
||||||
|
{#each board as b, idx}
|
||||||
|
<button disabled={b !== 0 || disabled || !columnTopEmpty(idx)} class="item" on:click={handleClick(idx)}>
|
||||||
|
<svelte:component this="{iconMap[b]}"/>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
max-width: min(100%, 56rem);
|
||||||
|
border: 1px solid var(--cds-interactive-03);
|
||||||
|
}
|
||||||
|
button.item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
background: none;
|
||||||
|
border: 2px solid var(--cds-interactive-03);
|
||||||
|
color: var(--cds-interactive-03);
|
||||||
|
transition: background 70ms cubic-bezier(0, 0, 0.38, 0.9),
|
||||||
|
color 70ms cubic-bezier(0, 0, 0.38, 0.9);
|
||||||
|
}
|
||||||
|
button.item:enabled:hover {
|
||||||
|
background-color: var(--cds-hover-tertiary);
|
||||||
|
color: var(--cds-inverse-01);
|
||||||
|
}
|
||||||
|
button.item:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,8 +0,0 @@
|
||||||
<div></div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -29,6 +29,7 @@
|
||||||
<HeaderNav>
|
<HeaderNav>
|
||||||
<HeaderNavItem href="/" text="About"/>
|
<HeaderNavItem href="/" text="About"/>
|
||||||
<HeaderNavItem href="/o-x" text="Noughts & Crosses"/>
|
<HeaderNavItem href="/o-x" text="Noughts & Crosses"/>
|
||||||
|
<HeaderNavItem href="/connect-four" text="Connect Four"/>
|
||||||
</HeaderNav>
|
</HeaderNav>
|
||||||
<HeaderUtilities>
|
<HeaderUtilities>
|
||||||
<HeaderGlobalAction icon={dark ? Moon : Sun} on:click={() => dark = !dark}/>
|
<HeaderGlobalAction icon={dark ? Moon : Sun} on:click={() => dark = !dark}/>
|
||||||
|
|
|
@ -0,0 +1,323 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Column,
|
||||||
|
DataTable,
|
||||||
|
Grid,
|
||||||
|
ListItem,
|
||||||
|
NumberInput,
|
||||||
|
RadioButton,
|
||||||
|
RadioButtonGroup,
|
||||||
|
Row,
|
||||||
|
Toggle,
|
||||||
|
UnorderedList
|
||||||
|
} from "carbon-components-svelte";
|
||||||
|
import wasmInit, { count_empty, find_winner, get_score, predict } from "@game-algorithms/connect-four-rust";
|
||||||
|
import ConnectFourBoard from "$lib/components/connect-four/ConnectFourBoard.svelte";
|
||||||
|
import ConnectFourRed from "$lib/components/connect-four/ConnectFourRed.svelte";
|
||||||
|
import ConnectFourYellow from "$lib/components/connect-four/ConnectFourYellow.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let board: number[] = [];
|
||||||
|
|
||||||
|
let turn = -1;
|
||||||
|
let playAs = "red";
|
||||||
|
let algorithm = "mm+d";
|
||||||
|
let w = 7;
|
||||||
|
let h = 6;
|
||||||
|
let d = 3;
|
||||||
|
let min = 4;
|
||||||
|
let human = 0;
|
||||||
|
let bot = 0;
|
||||||
|
let loaded = false;
|
||||||
|
let showPreviousScores = false;
|
||||||
|
let showCurrentScores = false;
|
||||||
|
let showScoresFor = "human";
|
||||||
|
let status = "Press 'Start' to play";
|
||||||
|
// step: col: [human, bot]
|
||||||
|
let scores: [number | null, number | null][][] = [];
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await wasmInit();
|
||||||
|
loaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete a turn logic
|
||||||
|
*/
|
||||||
|
function completeTurn() {
|
||||||
|
turn %= 2;
|
||||||
|
turn += 1;
|
||||||
|
const convertedBoard = Uint8Array.from(board);
|
||||||
|
console.log("1");
|
||||||
|
scores = [...scores, getBoardScore(board, w, min, human, bot, algorithm)];
|
||||||
|
console.log("2");
|
||||||
|
const winner = find_winner(w, convertedBoard, min);
|
||||||
|
if (winner !== 0 || count_empty(convertedBoard) === 0) {
|
||||||
|
turn = -1;
|
||||||
|
switch (winner) {
|
||||||
|
case 0:
|
||||||
|
status = "You drew!";
|
||||||
|
break;
|
||||||
|
case human:
|
||||||
|
status = "You won!";
|
||||||
|
break;
|
||||||
|
case bot:
|
||||||
|
status = `The ${algorithm} algorithm defeated you!`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
status = "How did we get here?";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
alert(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (turn === human) {
|
||||||
|
status = "Your turn!";
|
||||||
|
} else {
|
||||||
|
status = "Algorithm's turn!";
|
||||||
|
const p = predict(bot, human, bot === 1, w, convertedBoard, min, d, algorithm);
|
||||||
|
const r = getLowestEmptyRow(board, w, p);
|
||||||
|
board[r*w + p] = bot;
|
||||||
|
completeTurn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get lowest row with no item.
|
||||||
|
*
|
||||||
|
* @param board The board
|
||||||
|
* @param w Board width
|
||||||
|
* @param col Column to use
|
||||||
|
*
|
||||||
|
* @returns The row index
|
||||||
|
*/
|
||||||
|
function getLowestEmptyRow(board: number[], w: number, col: number): number {
|
||||||
|
let r = board.length / w - 1;
|
||||||
|
while (r >= 0 && board[r*w + col] !== 0) {
|
||||||
|
r--;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the scores for all possible moves on the board for each player
|
||||||
|
*
|
||||||
|
* @param board The board
|
||||||
|
* @param w Board width
|
||||||
|
* @param min Min in a row to win
|
||||||
|
* @param human The human player ID
|
||||||
|
* @param bot The bot player ID
|
||||||
|
* @param algorithm The algorithm to use for scoring
|
||||||
|
* @returns The scores for the moves for each cell and player
|
||||||
|
*/
|
||||||
|
function getBoardScore(
|
||||||
|
board: number[],
|
||||||
|
w: number,
|
||||||
|
min: number,
|
||||||
|
human: number,
|
||||||
|
bot: number,
|
||||||
|
algorithm: string
|
||||||
|
): [number | null, number | null][] {
|
||||||
|
let scores: [number | null, number | null][] = [];
|
||||||
|
for (let col = 0;col < w;col++) {
|
||||||
|
if (getLowestEmptyRow(board, w, col) === -1) {
|
||||||
|
scores.push([null, null]);
|
||||||
|
} else {
|
||||||
|
let i = getLowestEmptyRow(board, w, col)*w + col;
|
||||||
|
const humanCopy = board.slice();
|
||||||
|
humanCopy[i] = human;
|
||||||
|
const hUint8Array = Uint8Array.from(humanCopy);
|
||||||
|
const botCopy = board.slice();
|
||||||
|
botCopy[i] = bot;
|
||||||
|
const bUint8Array = Uint8Array.from(humanCopy);
|
||||||
|
scores.push([
|
||||||
|
get_score(human, bot, human === 1, w, hUint8Array, min, d, algorithm),
|
||||||
|
get_score(bot, human, bot === 1, w, bUint8Array, min, d, algorithm)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scores;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to add up to <dp> decimal places to a number
|
||||||
|
*
|
||||||
|
* @param n Number
|
||||||
|
* @param dp Maximum decimal places
|
||||||
|
* @returns String formated number
|
||||||
|
*/
|
||||||
|
function toFixedOrLess(n: number, dp: number) {
|
||||||
|
const f = n.toFixed(dp);
|
||||||
|
const [u, d] = f.split(".");
|
||||||
|
const minD = d.slice(0, d.indexOf("0"));
|
||||||
|
if (minD.length === 0) {
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
return u + "." + minD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a table row from an array of scores
|
||||||
|
*
|
||||||
|
* @param player Player to get scores for
|
||||||
|
* @param moveOverride Override the move key value
|
||||||
|
* @returns The generated table row
|
||||||
|
*/
|
||||||
|
function scoreToRows(player: string, moveOverride?: string) {
|
||||||
|
return (score: [number | null, number | null][], i: number) => {
|
||||||
|
const rowScores: Record<string, string> = {};
|
||||||
|
for (let cell = 0;cell < score.length;cell++) {
|
||||||
|
const s = score[cell][player === "human" ? 0 : 1];
|
||||||
|
rowScores[cell.toString()] = s === null ? "-" : toFixedOrLess(s, 3);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: i,
|
||||||
|
move: moveOverride || i + 1,
|
||||||
|
...rowScores
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Connect Four</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<h1>Connect Four</h1>
|
||||||
|
<p>
|
||||||
|
This is a connect four bot, with custom board and game options using various algorithms.
|
||||||
|
</p>
|
||||||
|
<UnorderedList>
|
||||||
|
<ListItem>mm: minimax</ListItem>
|
||||||
|
<ListItem>mm+d: minimax with depth</ListItem>
|
||||||
|
</UnorderedList>
|
||||||
|
<div class="selection">
|
||||||
|
<RadioButtonGroup disabled={turn !== -1} legendText="Algorithm" bind:selected={algorithm}>
|
||||||
|
<RadioButton labelText="mm" value="mm" />
|
||||||
|
<RadioButton labelText="mm+d" value="mm+d" />
|
||||||
|
</RadioButtonGroup>
|
||||||
|
</div>
|
||||||
|
<div class="selection">
|
||||||
|
<RadioButtonGroup disabled={turn !== -1} legendText="Play as" bind:selected={playAs}>
|
||||||
|
<RadioButton labelText="Red" value="red" />
|
||||||
|
<RadioButton labelText="Yellow" value="yellow" />
|
||||||
|
</RadioButtonGroup>
|
||||||
|
</div>
|
||||||
|
<div class="selection numeric">
|
||||||
|
<NumberInput
|
||||||
|
bind:value={w}
|
||||||
|
label="Board width"
|
||||||
|
min={min}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
bind:value={h}
|
||||||
|
label="Board height"
|
||||||
|
min={min}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
bind:value={min}
|
||||||
|
label="How many in a row to win?"
|
||||||
|
min={2}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
bind:value={d}
|
||||||
|
label="Max depth to use for minimax"
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button disabled={!loaded || turn !== -1} on:click={() => {
|
||||||
|
board = Array(w * h).fill(0);
|
||||||
|
scores = [];
|
||||||
|
turn = 2;
|
||||||
|
human = Number(playAs === "yellow") + 1;
|
||||||
|
bot = human % 2 + 1;
|
||||||
|
completeTurn();
|
||||||
|
}}>Start</Button>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
<hr/>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<h2>Board</h2>
|
||||||
|
{#if loaded && board.length !== 0}
|
||||||
|
<ConnectFourBoard
|
||||||
|
w={w}
|
||||||
|
disabled={false}
|
||||||
|
board={board}
|
||||||
|
iconMap={{
|
||||||
|
1: ConnectFourRed,
|
||||||
|
2: ConnectFourYellow
|
||||||
|
}}
|
||||||
|
on:click={event => {
|
||||||
|
const r = getLowestEmptyRow(board, w, event.detail.col);
|
||||||
|
board[r*w + event.detail.col] = human;
|
||||||
|
setTimeout(completeTurn);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<h3>{loaded ? status : "Loading..."}</h3>
|
||||||
|
{/if}
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
<hr/>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<h2>Scores</h2>
|
||||||
|
<p>
|
||||||
|
View scores for each potential move here.
|
||||||
|
Cells are numbered going left-to-right, top-to-bottom.
|
||||||
|
The table headers show the cell numbers.
|
||||||
|
</p>
|
||||||
|
<Toggle labelText="Show previous scores" bind:toggled={showPreviousScores}/>
|
||||||
|
<Toggle labelText="Show current scores" bind:toggled={showCurrentScores}/>
|
||||||
|
<div class="selection">
|
||||||
|
<RadioButtonGroup legendText="Shows scores for" bind:selected={showScoresFor}>
|
||||||
|
<RadioButton labelText="You" value="human" />
|
||||||
|
<RadioButton labelText="The algorithm" value="bot" />
|
||||||
|
</RadioButtonGroup>
|
||||||
|
</div>
|
||||||
|
{#if showPreviousScores || showCurrentScores}
|
||||||
|
<DataTable headers={[
|
||||||
|
{
|
||||||
|
key: "move",
|
||||||
|
value: "Move"
|
||||||
|
},
|
||||||
|
...board.filter((_, i) => i < w )
|
||||||
|
.map((_, c) => {
|
||||||
|
return {
|
||||||
|
key: c.toString(),
|
||||||
|
value: (c + 1).toString()
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]}
|
||||||
|
rows={[
|
||||||
|
...(showPreviousScores ?
|
||||||
|
scores.slice(0, -1).map(scoreToRows(showScoresFor)) : []
|
||||||
|
),
|
||||||
|
...(showCurrentScores && scores.length !== 0 ?
|
||||||
|
[scoreToRows(showScoresFor, "Now")(scores.slice(-1)[0], scores.length)] : []
|
||||||
|
)
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1, h2, h3 {
|
||||||
|
margin-bottom: var(--cds-layout-01);
|
||||||
|
}
|
||||||
|
p, .selection {
|
||||||
|
margin-top: var(--cds-layout-01);
|
||||||
|
margin-bottom: var(--cds-layout-01);
|
||||||
|
}
|
||||||
|
.selection.numeric {
|
||||||
|
max-width: min(100%, 32rem);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -13,15 +13,11 @@
|
||||||
} from "carbon-components-svelte";
|
} from "carbon-components-svelte";
|
||||||
import wasmInit, { count_empty, find_winner, get_score, predict } from "@game-algorithms/o-x-rust";
|
import wasmInit, { count_empty, find_winner, get_score, predict } from "@game-algorithms/o-x-rust";
|
||||||
import OXBoard from "$lib/components/o-x/OXBoard.svelte";
|
import OXBoard from "$lib/components/o-x/OXBoard.svelte";
|
||||||
import OXNoneIcon from "$lib/components/o-x/OXNoneIcon.svelte";
|
|
||||||
import OXOIcon from "$lib/components/o-x/OXOIcon.svelte";
|
import OXOIcon from "$lib/components/o-x/OXOIcon.svelte";
|
||||||
import OXXIcon from "$lib/components/o-x/OXXIcon.svelte";
|
import OXXIcon from "$lib/components/o-x/OXXIcon.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let board: number[] = [];
|
let board = Array(9).fill(0);
|
||||||
for (let i = 0;i < 9;i++) {
|
|
||||||
board.push(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let turn = -1;
|
let turn = -1;
|
||||||
let playAs = "X";
|
let playAs = "X";
|
||||||
|
@ -94,7 +90,7 @@
|
||||||
bot: number,
|
bot: number,
|
||||||
algorithm: string
|
algorithm: string
|
||||||
): [number | null, number | null][] {
|
): [number | null, number | null][] {
|
||||||
return board.flat().map((v, i) => {
|
return board.map((v, i) => {
|
||||||
if (v === 0) {
|
if (v === 0) {
|
||||||
const humanCopy = board.slice();
|
const humanCopy = board.slice();
|
||||||
humanCopy[i] = human;
|
humanCopy[i] = human;
|
||||||
|
@ -139,7 +135,7 @@
|
||||||
function scoreToRows(player: string, moveOverride?: string) {
|
function scoreToRows(player: string, moveOverride?: string) {
|
||||||
return (score: [number | null, number | null][], i: number) => {
|
return (score: [number | null, number | null][], i: number) => {
|
||||||
const rowScores: Record<string, string> = {};
|
const rowScores: Record<string, string> = {};
|
||||||
for (let cell = 0;cell < 9;cell++) {
|
for (let cell = 0;cell < score.length;cell++) {
|
||||||
const s = score[cell][player === "human" ? 0 : 1];
|
const s = score[cell][player === "human" ? 0 : 1];
|
||||||
rowScores[cell.toString()] = s === null ? "-" : toFixedOrLess(s, 3);
|
rowScores[cell.toString()] = s === null ? "-" : toFixedOrLess(s, 3);
|
||||||
}
|
}
|
||||||
|
@ -186,11 +182,8 @@
|
||||||
</RadioButtonGroup>
|
</RadioButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
<Button disabled={!loaded || turn !== -1} on:click={() => {
|
<Button disabled={!loaded || turn !== -1} on:click={() => {
|
||||||
board = [];
|
board = Array(9).fill(0);
|
||||||
scores = [];
|
scores = [];
|
||||||
for (let i = 0;i < 9;i++) {
|
|
||||||
board.push(0);
|
|
||||||
}
|
|
||||||
turn = 2;
|
turn = 2;
|
||||||
human = Number(playAs === "O") + 1;
|
human = Number(playAs === "O") + 1;
|
||||||
bot = human % 2 + 1;
|
bot = human % 2 + 1;
|
||||||
|
@ -208,7 +201,6 @@
|
||||||
disabled={turn !== human}
|
disabled={turn !== human}
|
||||||
board={board}
|
board={board}
|
||||||
iconMap={{
|
iconMap={{
|
||||||
0: OXNoneIcon,
|
|
||||||
1: OXXIcon,
|
1: OXXIcon,
|
||||||
2: OXOIcon
|
2: OXOIcon
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import wasmPack from "vite-plugin-wasm-pack";
|
||||||
const config = {
|
const config = {
|
||||||
plugins: [
|
plugins: [
|
||||||
sveltekit(),
|
sveltekit(),
|
||||||
wasmPack([], ["@game-algorithms/o-x-rust"])
|
wasmPack([], ["@game-algorithms/o-x-rust", "@game-algorithms/connect-four-rust"])
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue