Don't use component library for game boards...
ci/woodpecker/push/tools-wasm-pack-plugin Pipeline was successful Details
ci/woodpecker/push/wasm-connect-four-rust Pipeline was successful Details
ci/woodpecker/push/wasm-o-x-rust Pipeline was successful Details
ci/woodpecker/push/frontend Pipeline was successful Details

This commit is contained in:
Gleb Koval 2022-08-09 21:10:51 +00:00
parent 9fd2b4d507
commit 677221603c
Signed by: cyclane
GPG Key ID: 15E168A8B332382C
8 changed files with 109 additions and 43 deletions

View File

@ -8,6 +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/o-x-rust": "^0.1.0" "@game-algorithms/o-x-rust": "^0.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -80,6 +81,12 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@game-algorithms/connect-four-rust": {
"version": "0.0.1",
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fconnect-four-rust/-/0.0.1/connect-four-rust-0.0.1.tgz",
"integrity": "sha512-XFVCupzmxi6lL9jfrETenwQwo7JDvGby8PB2jOfRuRpmGBZwgx9GQvc9euw+LwlGCXUhN8zYqKIdoU7IDfd6ng==",
"license": "GNU GPLv3"
},
"node_modules/@game-algorithms/o-x-rust": { "node_modules/@game-algorithms/o-x-rust": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fo-x-rust/-/0.1.0/o-x-rust-0.1.0.tgz", "resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fo-x-rust/-/0.1.0/o-x-rust-0.1.0.tgz",
@ -2927,6 +2934,11 @@
"strip-json-comments": "^3.1.1" "strip-json-comments": "^3.1.1"
} }
}, },
"@game-algorithms/connect-four-rust": {
"version": "0.0.1",
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fconnect-four-rust/-/0.0.1/connect-four-rust-0.0.1.tgz",
"integrity": "sha512-XFVCupzmxi6lL9jfrETenwQwo7JDvGby8PB2jOfRuRpmGBZwgx9GQvc9euw+LwlGCXUhN8zYqKIdoU7IDfd6ng=="
},
"@game-algorithms/o-x-rust": { "@game-algorithms/o-x-rust": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fo-x-rust/-/0.1.0/o-x-rust-0.1.0.tgz", "resolved": "https://git.koval.net/api/packages/cyclane/npm/%40game-algorithms%2Fo-x-rust/-/0.1.0/o-x-rust-0.1.0.tgz",

View File

@ -32,6 +32,7 @@
}, },
"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"
} }
} }

View File

@ -0,0 +1,54 @@
<script lang="ts">
import { type SvelteComponent, createEventDispatcher } from "svelte";
export let board: number[];
export let iconMap: Record<number, typeof SvelteComponent>;
export let disabled = false;
const dispatch = createEventDispatcher();
/**
* Generate handler function for board cell click
*
* @param idx Index of cell for event
* @returns Handler function
*/
function handleClick(idx: number) {
return () => dispatch("click", {
idx
});
}
</script>
<div class="grid">
{#each board as b, idx}
<button disabled={b !== 0 || disabled} class="item" on:click={handleClick(idx)}>
<svelte:component this="{iconMap[b]}"/>
</button>
{/each}
</div>
<style>
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
max-width: min(100%, 24rem);
border: 1px solid var(--cds-interactive-03);
}
button.item {
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>

View File

@ -2,7 +2,7 @@
<style> <style>
div { div {
width: 5rem; width: 100%;
height: 5rem; height: 100%;
} }
</style> </style>

View File

@ -2,8 +2,7 @@
<style> <style>
h1 { h1 {
width: 5rem; width: 100%;
height: 5rem;
text-align: center; text-align: center;
font-size: 4rem; font-size: 4rem;
} }

View File

@ -2,8 +2,7 @@
<style> <style>
h1 { h1 {
width: 5rem; width: 100%;
height: 5rem;
text-align: center; text-align: center;
font-size: 4rem; font-size: 4rem;
} }

View File

@ -2,6 +2,10 @@
import { Column, Grid, Link, Row } from "carbon-components-svelte"; import { Column, Grid, Link, Row } from "carbon-components-svelte";
</script> </script>
<svelte:head>
<title>About</title>
</svelte:head>
<Grid> <Grid>
<Row> <Row>
<Column> <Column>

View File

@ -12,14 +12,15 @@
UnorderedList UnorderedList
} 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 OXNoneIcon from "$lib/components/OXNoneIcon.svelte"; import OXBoard from "$lib/components/o-x/OXBoard.svelte";
import OXOIcon from "$lib/components/OXOIcon.svelte"; import OXNoneIcon from "$lib/components/o-x/OXNoneIcon.svelte";
import OXXIcon from "$lib/components/OXXIcon.svelte"; import OXOIcon from "$lib/components/o-x/OXOIcon.svelte";
import OXXIcon from "$lib/components/o-x/OXXIcon.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
let board: number[][] = []; let board: number[] = [];
for (let i = 0;i < 3;i++) { for (let i = 0;i < 9;i++) {
board.push([0, 0, 0]); board.push(0);
} }
let turn = -1; let turn = -1;
@ -44,7 +45,7 @@
* Complete a turn logic * Complete a turn logic
*/ */
function completeTurn() { function completeTurn() {
const convertedBoard = Uint8Array.from(board.flat()); const convertedBoard = Uint8Array.from(board);
scores = [...scores, getBoardScore(board, human, bot, algorithm)]; scores = [...scores, getBoardScore(board, human, bot, algorithm)];
const winner = find_winner(convertedBoard); const winner = find_winner(convertedBoard);
if (winner !== 0 || count_empty(convertedBoard) === 0) { if (winner !== 0 || count_empty(convertedBoard) === 0) {
@ -73,7 +74,7 @@
} else { } else {
status = "Algorithm's turn!"; status = "Algorithm's turn!";
const p = predict(bot, human, bot === 1, convertedBoard, algorithm); const p = predict(bot, human, bot === 1, convertedBoard, algorithm);
board[Math.floor(p / 3)][p % 3] = bot; board[p] = bot;
completeTurn(); completeTurn();
} }
} }
@ -88,17 +89,17 @@
* @returns The scores for the moves for each cell and player * @returns The scores for the moves for each cell and player
*/ */
function getBoardScore( function getBoardScore(
board: number[][], board: number[],
human: number, human: number,
bot: number, bot: number,
algorithm: string algorithm: string
): [number | null, number | null][] { ): [number | null, number | null][] {
return board.flat().map((v, i) => { return board.flat().map((v, i) => {
if (v === 0) { if (v === 0) {
const humanCopy = board.flat(); const humanCopy = board.slice();
humanCopy[i] = human; humanCopy[i] = human;
const hUint8Array = Uint8Array.from(humanCopy); const hUint8Array = Uint8Array.from(humanCopy);
const botCopy = board.flat(); const botCopy = board.slice();
botCopy[i] = bot; botCopy[i] = bot;
const bUint8Array = Uint8Array.from(humanCopy); const bUint8Array = Uint8Array.from(humanCopy);
return [ return [
@ -151,6 +152,10 @@
} }
</script> </script>
<svelte:head>
<title>Noughts & Crosses</title>
</svelte:head>
<Grid> <Grid>
<Row> <Row>
<Column> <Column>
@ -166,7 +171,7 @@
<ListItem>mm+d: minimax with depth</ListItem> <ListItem>mm+d: minimax with depth</ListItem>
</UnorderedList> </UnorderedList>
<div class="selection"> <div class="selection">
<RadioButtonGroup disabled={turn !== -1} legendText="Algorithms" bind:selected={algorithm}> <RadioButtonGroup disabled={turn !== -1} legendText="Algorithm" bind:selected={algorithm}>
<RadioButton labelText="sa" value="sa" /> <RadioButton labelText="sa" value="sa" />
<RadioButton labelText="sa+rd" value="sa+rd" /> <RadioButton labelText="sa+rd" value="sa+rd" />
<RadioButton labelText="sa+r-d" value="sa+r-d" /> <RadioButton labelText="sa+r-d" value="sa+r-d" />
@ -183,8 +188,8 @@
<Button disabled={!loaded || turn !== -1} on:click={() => { <Button disabled={!loaded || turn !== -1} on:click={() => {
board = []; board = [];
scores = []; scores = [];
for (let i = 0;i < 3;i++) { for (let i = 0;i < 9;i++) {
board.push([0, 0, 0]); board.push(0);
} }
turn = 2; turn = 2;
human = Number(playAs === "O") + 1; human = Number(playAs === "O") + 1;
@ -199,27 +204,19 @@
<h2>Board</h2> <h2>Board</h2>
<p>{status}</p> <p>{status}</p>
{#if loaded} {#if loaded}
<Grid> <OXBoard
{#each board as row, r} disabled={turn !== human}
<Row> board={board}
{#each row as col, c} iconMap={{
<Column sm={.1} style="border: 1px solid white;"> 0: OXNoneIcon,
<Button 1: OXXIcon,
kind="tertiary" 2: OXOIcon
iconDescription={col === 0 ? "Available" : col === 1 ? "Cross" : "Nought"} }}
icon={col === 0 ? OXNoneIcon : col === 1 ? OXXIcon : OXOIcon} on:click={event => {
disabled={board[r][c] !== 0 || turn !== human} board[event.detail.idx] = human;
on:click={() => {
board[r][c] = human;
completeTurn(); completeTurn();
console.log(scores);
}} }}
/> />
</Column>
{/each}
</Row>
{/each}
</Grid>
{:else} {:else}
<h3>Loading...</h3> <h3>Loading...</h3>
{/if} {/if}
@ -248,7 +245,7 @@
key: "move", key: "move",
value: "Move" value: "Move"
}, },
...board.flat().map((_, c) => { ...board.map((_, c) => {
return { return {
key: c.toString(), key: c.toString(),
value: (c + 1).toString() value: (c + 1).toString()