frontend with build and publish ci
This commit is contained in:
		
							
								
								
									
										13
									
								
								frontend/.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/.eslintignore
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										44
									
								
								frontend/.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										8
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| .DS_Store | ||||
| node_modules | ||||
| /build | ||||
| /.svelte-kit | ||||
| /package | ||||
| .env | ||||
| .env.* | ||||
| !.env.example | ||||
							
								
								
									
										1
									
								
								frontend/.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/.npmrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| engine-strict=true | ||||
							
								
								
									
										12
									
								
								frontend/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -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" ] | ||||
							
								
								
									
										38
									
								
								frontend/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								frontend/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										4820
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4820
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										37
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										11
									
								
								frontend/src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								frontend/src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 {} | ||||
| } | ||||
							
								
								
									
										12
									
								
								frontend/src/app.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/src/app.html
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
							
								
								
									
										8
									
								
								frontend/src/lib/components/OXNoneIcon.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/src/lib/components/OXNoneIcon.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <div></div> | ||||
|  | ||||
| <style> | ||||
| 	div { | ||||
| 		width: 5rem; | ||||
| 		height: 5rem; | ||||
| 	} | ||||
| </style> | ||||
							
								
								
									
										10
									
								
								frontend/src/lib/components/OXOIcon.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/src/lib/components/OXOIcon.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <h1>○</h1> | ||||
|  | ||||
| <style> | ||||
| 	h1 { | ||||
| 		width: 5rem; | ||||
| 		height: 5rem; | ||||
| 		text-align: center; | ||||
| 		font-size: 4rem; | ||||
| 	} | ||||
| </style> | ||||
							
								
								
									
										10
									
								
								frontend/src/lib/components/OXXIcon.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/src/lib/components/OXXIcon.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <h1>⨯</h1> | ||||
|  | ||||
| <style> | ||||
| 	h1 { | ||||
| 		width: 5rem; | ||||
| 		height: 5rem; | ||||
| 		text-align: center; | ||||
| 		font-size: 4rem; | ||||
| 	} | ||||
| </style> | ||||
							
								
								
									
										38
									
								
								frontend/src/routes/__layout.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								frontend/src/routes/__layout.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
							
								
								
									
										37
									
								
								frontend/src/routes/index.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								frontend/src/routes/index.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
							
								
								
									
										280
									
								
								frontend/src/routes/o-x.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								frontend/src/routes/o-x.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
							
								
								
									
										
											BIN
										
									
								
								frontend/static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										19
									
								
								frontend/svelte.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								frontend/svelte.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
							
								
								
									
										14
									
								
								frontend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										11
									
								
								frontend/vite.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								frontend/vite.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
		Reference in New Issue
	
	Block a user