frontend with build and publish ci
This commit is contained in:
parent
e5e066bdcc
commit
3afc775f62
|
@ -10,3 +10,4 @@ insert_final_newline = true
|
||||||
|
|
||||||
[*.yml]
|
[*.yml]
|
||||||
indent_style=space
|
indent_style=space
|
||||||
|
indent_size = 2
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
pipeline:
|
||||||
|
deps:
|
||||||
|
image: node:alpine
|
||||||
|
commands:
|
||||||
|
- cd frontend
|
||||||
|
- npm install --save-dev
|
||||||
|
eslint:
|
||||||
|
image: node:alpine
|
||||||
|
commands:
|
||||||
|
- cd frontend
|
||||||
|
- npm run lint
|
||||||
|
group: build
|
||||||
|
svelte:
|
||||||
|
image: node:alpine
|
||||||
|
commands:
|
||||||
|
- cd frontend
|
||||||
|
- npm run build
|
||||||
|
docker build:
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
dry_run: true
|
||||||
|
repo: git.koval.net/cyclane/game-algorithms/frontend
|
||||||
|
tags: latest
|
||||||
|
context: frontend
|
||||||
|
dockerfile: frontend/Dockerfile
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
exclude: [main]
|
||||||
|
path:
|
||||||
|
include:
|
||||||
|
- frontend/*
|
||||||
|
- .woodpecker/frontend.yml
|
||||||
|
docker build and publish:
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
registry: git.koval.net
|
||||||
|
username: cyclane
|
||||||
|
password:
|
||||||
|
from_secret: DEPLOY_TOKEN
|
||||||
|
repo: git.koval.net/cyclane/game-algorithms/frontend
|
||||||
|
tags: latest
|
||||||
|
context: frontend
|
||||||
|
dockerfile: frontend/Dockerfile
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
path:
|
||||||
|
include:
|
||||||
|
- frontend/*
|
||||||
|
- .woodpecker/frontend.yml
|
|
@ -0,0 +1,13 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
|
@ -0,0 +1,44 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jsdoc/recommended"],
|
||||||
|
plugins: ["svelte3", "@typescript-eslint", "jsdoc"],
|
||||||
|
ignorePatterns: ["*.cjs"],
|
||||||
|
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
|
||||||
|
settings: {
|
||||||
|
"svelte3/typescript": () => require("typescript")
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaVersion: 2020
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
semi: 2,
|
||||||
|
"semi-spacing": ["error", {before: false, after: false}],
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"sort-imports": 2,
|
||||||
|
indent: ["error", "tab"],
|
||||||
|
"eol-last": 2,
|
||||||
|
"max-len": ["error", {code: 120}],
|
||||||
|
"prefer-const": 2,
|
||||||
|
quotes: ["error", "double"],
|
||||||
|
|
||||||
|
"@typescript-eslint/no-explicit-any": 2,
|
||||||
|
"@typescript-eslint/no-unused-vars": 2,
|
||||||
|
|
||||||
|
"jsdoc/require-description": 2,
|
||||||
|
"jsdoc/require-jsdoc": 2,
|
||||||
|
"jsdoc/require-param-description": 2,
|
||||||
|
"jsdoc/require-param-name": 2,
|
||||||
|
"jsdoc/require-param-type": 0,
|
||||||
|
"jsdoc/require-returns": ["error", {forceReturnsWithAsync: true}],
|
||||||
|
"jsdoc/require-returns-check": 2,
|
||||||
|
"jsdoc/require-returns-description": 2,
|
||||||
|
"jsdoc/require-returns-type": 0
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM node:alpine
|
||||||
|
|
||||||
|
COPY build /app/build
|
||||||
|
COPY package.json /app/package.json
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --retries=3 --start-period=10s --timeout=1s \
|
||||||
|
CMD wget -q --tries=1 --spider http://localhost:3000/ || exit 1
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
ENTRYPOINT [ "node", "build" ]
|
|
@ -0,0 +1,38 @@
|
||||||
|
# create-svelte
|
||||||
|
|
||||||
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create a new project in the current directory
|
||||||
|
npm create svelte@latest
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npm create svelte@latest my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"package": "svelte-kit package",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint-fix": "eslint . --fix"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/adapter-node": "next",
|
||||||
|
"@sveltejs/kit": "next",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||||
|
"@typescript-eslint/parser": "^5.27.0",
|
||||||
|
"carbon-components-svelte": "^0.67.7",
|
||||||
|
"carbon-icons-svelte": "^11.2.0",
|
||||||
|
"carbon-preprocess-svelte": "^0.9.1",
|
||||||
|
"eslint": "^8.16.0",
|
||||||
|
"eslint-plugin-jsdoc": "^39.3.4",
|
||||||
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
|
"svelte": "^3.44.0",
|
||||||
|
"svelte-check": "^2.7.1",
|
||||||
|
"svelte-preprocess": "^4.10.6",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"typescript": "^4.7.4",
|
||||||
|
"vite": "^3.0.0",
|
||||||
|
"vite-plugin-wasm-pack": "0.1.11"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@game-algorithms/o-x-rust": "^0.0.9"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
// and what to do when importing types
|
||||||
|
declare namespace App {
|
||||||
|
// interface Locals {}
|
||||||
|
// interface Platform {}
|
||||||
|
// interface PrivateEnv {}
|
||||||
|
// interface PublicEnv {}
|
||||||
|
// interface Session {}
|
||||||
|
// interface Stuff {}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html theme="g100" lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
%sveltekit.body%
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<h1>○</h1>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<h1>⨯</h1>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import "carbon-components-svelte/css/all.css";
|
||||||
|
import {
|
||||||
|
Content,
|
||||||
|
Header,
|
||||||
|
HeaderGlobalAction,
|
||||||
|
HeaderNav,
|
||||||
|
HeaderNavItem,
|
||||||
|
HeaderUtilities,
|
||||||
|
SkipToContent,
|
||||||
|
Theme
|
||||||
|
} from "carbon-components-svelte";
|
||||||
|
import Moon from "carbon-icons-svelte/lib/Moon.svelte";
|
||||||
|
import Sun from "carbon-icons-svelte/lib/Sun.svelte";
|
||||||
|
|
||||||
|
let dark = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Theme
|
||||||
|
theme={dark ? "g100" : "white"}
|
||||||
|
on:update={update => dark = update.detail.theme === "g100"}
|
||||||
|
persist persistKey="__carbon-theme"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Header company="Cyclane" platformName="Game Algorithms">
|
||||||
|
<svelte:fragment slot="skip-to-content">
|
||||||
|
<SkipToContent />
|
||||||
|
</svelte:fragment>
|
||||||
|
<HeaderNav>
|
||||||
|
<HeaderNavItem href="/" text="About"/>
|
||||||
|
<HeaderNavItem href="/o-x" text="Noughts & Crosses"/>
|
||||||
|
</HeaderNav>
|
||||||
|
<HeaderUtilities>
|
||||||
|
<HeaderGlobalAction icon={dark ? Moon : Sun} on:click={() => dark = !dark}/>
|
||||||
|
</HeaderUtilities>
|
||||||
|
</Header>
|
||||||
|
|
||||||
|
<Content><slot/></Content>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Column, Grid, Link, Row } from "carbon-components-svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<h1>Game Algorithms</h1>
|
||||||
|
<img
|
||||||
|
class="link"
|
||||||
|
alt="CI badge"
|
||||||
|
src="https://woodpecker.koval.net/api/badges/cyclane/game-algorithms/status.svg"
|
||||||
|
on:click={() => window.location.href = "https://woodpecker.koval.net/cyclane/game-algorithms"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a learning project for various board-game alogrithms.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Visit the <Link inline href="https://git.koval.net/cyclane/game-algorithms">Git repo</Link> for source code.
|
||||||
|
</p>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
margin-bottom: var(--cds-layout-01);
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: var(--cds-layout-01);
|
||||||
|
margin-bottom: var(--cds-layout-01);
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,280 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Column,
|
||||||
|
DataTable,
|
||||||
|
Grid,
|
||||||
|
ListItem,
|
||||||
|
RadioButton,
|
||||||
|
RadioButtonGroup,
|
||||||
|
Row,
|
||||||
|
Toggle,
|
||||||
|
UnorderedList
|
||||||
|
} from "carbon-components-svelte";
|
||||||
|
import wasmInit, { count_empty, find_winner, get_score, predict } from "@game-algorithms/o-x-rust";
|
||||||
|
import OXNoneIcon from "$lib/components/OXNoneIcon.svelte";
|
||||||
|
import OXOIcon from "$lib/components/OXOIcon.svelte";
|
||||||
|
import OXXIcon from "$lib/components/OXXIcon.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let board: number[][] = [];
|
||||||
|
for (let i = 0;i < 3;i++) {
|
||||||
|
board.push([0, 0, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let turn = -1;
|
||||||
|
let playAs = "X";
|
||||||
|
let algorithm = "mm+d";
|
||||||
|
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: cell: [human, bot]
|
||||||
|
let scores: [number | null, number | null][][] = [];
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await wasmInit();
|
||||||
|
loaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete a turn logic
|
||||||
|
*/
|
||||||
|
function completeTurn() {
|
||||||
|
const convertedBoard = Uint8Array.from(board.flat());
|
||||||
|
scores = [...scores, getBoardScore(board, human, bot, algorithm)];
|
||||||
|
const winner = find_winner(convertedBoard);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
turn %= 2;
|
||||||
|
turn += 1;
|
||||||
|
if (turn === human) {
|
||||||
|
status = "Your turn!";
|
||||||
|
} else {
|
||||||
|
status = "Algorithm's turn!";
|
||||||
|
const p = predict(bot, human, bot === 1, convertedBoard, algorithm);
|
||||||
|
board[Math.floor(p / 3)][p % 3] = bot;
|
||||||
|
completeTurn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the scores for all possible moves on the board for each player
|
||||||
|
*
|
||||||
|
* @param board The board
|
||||||
|
* @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[][],
|
||||||
|
human: number,
|
||||||
|
bot: number,
|
||||||
|
algorithm: string
|
||||||
|
): [number | null, number | null][] {
|
||||||
|
return board.flat().map((v, i) => {
|
||||||
|
if (v === 0) {
|
||||||
|
const humanCopy = board.flat();
|
||||||
|
humanCopy[i] = human;
|
||||||
|
const hUint8Array = Uint8Array.from(humanCopy);
|
||||||
|
const botCopy = board.flat();
|
||||||
|
botCopy[i] = bot;
|
||||||
|
const bUint8Array = Uint8Array.from(humanCopy);
|
||||||
|
return [
|
||||||
|
get_score(human, bot, human === 1, hUint8Array, algorithm),
|
||||||
|
get_score(bot, human, bot === 1, bUint8Array, algorithm)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 < 9;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>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<h1>Noughts and Crosses</h1>
|
||||||
|
<p>
|
||||||
|
This is a noughts and crosses bot using various algorithms.
|
||||||
|
</p>
|
||||||
|
<UnorderedList>
|
||||||
|
<ListItem>sa: score adding without optimizations</ListItem>
|
||||||
|
<ListItem>sa+rd: score adding with ratio optimization including draw (score between win and loose)</ListItem>
|
||||||
|
<ListItem>sa+r-d: score adding with ratio optimization not including draw (draw = loss)</ListItem>
|
||||||
|
<ListItem>mm: minimax</ListItem>
|
||||||
|
<ListItem>mm+d: minimax with depth</ListItem>
|
||||||
|
</UnorderedList>
|
||||||
|
<div class="selection">
|
||||||
|
<RadioButtonGroup disabled={turn !== -1} legendText="Algorithms" bind:selected={algorithm}>
|
||||||
|
<RadioButton labelText="sa" value="sa" />
|
||||||
|
<RadioButton labelText="sa+rd" value="sa+rd" />
|
||||||
|
<RadioButton labelText="sa+r-d" value="sa+r-d" />
|
||||||
|
<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="Noughts" value="O" />
|
||||||
|
<RadioButton labelText="Crosses" value="X" />
|
||||||
|
</RadioButtonGroup>
|
||||||
|
</div>
|
||||||
|
<Button disabled={!loaded || turn !== -1} on:click={() => {
|
||||||
|
board = [];
|
||||||
|
scores = [];
|
||||||
|
for (let i = 0;i < 3;i++) {
|
||||||
|
board.push([0, 0, 0]);
|
||||||
|
}
|
||||||
|
turn = 2;
|
||||||
|
human = Number(playAs === "O") + 1;
|
||||||
|
bot = human % 2 + 1;
|
||||||
|
completeTurn();
|
||||||
|
}}>Start</Button>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
<hr/>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<h2>Board</h2>
|
||||||
|
<p>{status}</p>
|
||||||
|
{#if loaded}
|
||||||
|
<Grid>
|
||||||
|
{#each board as row, r}
|
||||||
|
<Row>
|
||||||
|
{#each row as col, c}
|
||||||
|
<Column sm={.1} style="border: 1px solid white;">
|
||||||
|
<Button
|
||||||
|
kind="tertiary"
|
||||||
|
iconDescription={col === 0 ? "Available" : col === 1 ? "Cross" : "Nought"}
|
||||||
|
icon={col === 0 ? OXNoneIcon : col === 1 ? OXXIcon : OXOIcon}
|
||||||
|
disabled={board[r][c] !== 0 || turn !== human}
|
||||||
|
on:click={() => {
|
||||||
|
board[r][c] = human;
|
||||||
|
completeTurn();
|
||||||
|
console.log(scores);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
{/each}
|
||||||
|
</Row>
|
||||||
|
{/each}
|
||||||
|
</Grid>
|
||||||
|
{:else}
|
||||||
|
<h3>Loading...</h3>
|
||||||
|
{/if}
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
<hr/>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<h2>Scores</h2>
|
||||||
|
<p>
|
||||||
|
View scores for each potential move here.
|
||||||
|
Cells are numbered from 1 to 9 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.flat().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);
|
||||||
|
}
|
||||||
|
</style>
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
import adapter from "@sveltejs/adapter-node";
|
||||||
|
import { optimizeImports } from "carbon-preprocess-svelte";
|
||||||
|
import preprocess from "svelte-preprocess";
|
||||||
|
|
||||||
|
/** @type {import("@sveltejs/kit").Config} */
|
||||||
|
const config = {
|
||||||
|
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: [
|
||||||
|
preprocess(),
|
||||||
|
optimizeImports(),
|
||||||
|
],
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "Node"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
|
import wasmPack from "vite-plugin-wasm-pack";
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
plugins: [
|
||||||
|
sveltekit(),
|
||||||
|
wasmPack([], ["@game-algorithms/o-x-rust"])
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
Loading…
Reference in New Issue