feat: first MVP
This commit is contained in:
23
.dockerignore
Normal file
23
.dockerignore
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.wrangler
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FROM node:18.19-alpine AS builder
|
||||||
|
WORKDIR /app-builder
|
||||||
|
|
||||||
|
COPY ./package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx:1.25.1
|
||||||
|
WORKDIR /app
|
||||||
|
ARG VERSION=next
|
||||||
|
# NGINX redirect and cache control
|
||||||
|
RUN sed -i '8 a \ \ \ \ \ \ \ \ try_files $uri $uri /index.html;' /etc/nginx/conf.d/default.conf
|
||||||
|
# RUN sed -i "10 a \ \ \ \ \ \ \ \ location /env.js { add_header Cache-Control 'no-store'; add_header Cache-Control 'no-cache'; expires 0;}" /etc/nginx/conf.d/default.conf
|
||||||
|
# https://pumpingco.de/blog/environment-variables-angular-docker/
|
||||||
|
# RUN echo -n 'envsubst < /usr/share/nginx/html/env.template.js > /usr/share/nginx/html/env.js\nrm /usr/share/nginx/html/env.template.js' > /docker-entrypoint.d/100-set-env.sh && chmod +x /docker-entrypoint.d/100-set-env.sh
|
||||||
|
|
||||||
|
COPY --from=builder /app-builder/build /usr/share/nginx/html
|
31
package-lock.json
generated
31
package-lock.json
generated
@ -20,6 +20,7 @@
|
|||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^3.0.0",
|
"eslint-plugin-svelte": "^3.0.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
|
"js-confetti": "^0.12.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
@ -3411,6 +3412,13 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-confetti": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-confetti/-/js-confetti-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -3888,6 +3896,19 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/micromatch/node_modules/picomatch": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime-db": {
|
||||||
"version": "1.52.0",
|
"version": "1.52.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
@ -4115,13 +4136,15 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
@ -24,19 +24,20 @@
|
|||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/svelte": "^5.2.4",
|
"@testing-library/svelte": "^5.2.4",
|
||||||
"eslint": "^9.18.0",
|
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^3.0.0",
|
"eslint-plugin-svelte": "^3.0.0",
|
||||||
|
"eslint": "^9.18.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
|
"js-confetti": "^0.12.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"prettier": "^3.4.2",
|
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"svelte": "^5.0.0",
|
"prettier": "^3.4.2",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
|
"svelte": "^5.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"typescript": "^5.0.0",
|
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
"vitest": "^3.0.0"
|
"vitest": "^3.0.0"
|
||||||
}
|
}
|
||||||
|
79
src/lib/Board.svelte
Normal file
79
src/lib/Board.svelte
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import JSConfetti from 'js-confetti';
|
||||||
|
import Cell from './Cell.svelte';
|
||||||
|
import ValueSelector from './ValueSelector.svelte';
|
||||||
|
import { Board, Difficulty } from './core/board';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import GameCommands from './GameCommands.svelte';
|
||||||
|
import SumIndicator from './SumIndicator.svelte';
|
||||||
|
|
||||||
|
let selectedI: number | undefined = undefined;
|
||||||
|
let selectedJ: number | undefined = undefined;
|
||||||
|
let status: 'incomplete' | 'error' | 'won' = 'incomplete';
|
||||||
|
|
||||||
|
const onSelect = (i: number, j: number) => () => {
|
||||||
|
if (i === selectedI && j === selectedJ) {
|
||||||
|
selectedI = undefined;
|
||||||
|
selectedJ = undefined;
|
||||||
|
} else {
|
||||||
|
selectedI = i;
|
||||||
|
selectedJ = j;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSetValue = (number: number) => {
|
||||||
|
if (selectedI !== undefined && selectedJ !== undefined) {
|
||||||
|
board.grid[selectedI][selectedJ] = number;
|
||||||
|
if (board.isComplete()) {
|
||||||
|
status = board.isValid() ? 'won' : 'error';
|
||||||
|
if (status === 'won') {
|
||||||
|
jsConfetti.addConfetti();
|
||||||
|
selectedI = undefined;
|
||||||
|
selectedJ = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGameCommand = (mode: string) => {
|
||||||
|
if (mode === 'reset') {
|
||||||
|
board.reset();
|
||||||
|
} else {
|
||||||
|
board = new Board();
|
||||||
|
if (mode === 'easy') board.prepare(Difficulty.Easy);
|
||||||
|
else if (mode === 'medium') board.prepare(Difficulty.Medium);
|
||||||
|
else if (mode === 'hard') board.prepare(Difficulty.Hard);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let board: Board;
|
||||||
|
let jsConfetti: JSConfetti;
|
||||||
|
|
||||||
|
onGameCommand('easy');
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
jsConfetti = new JSConfetti();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<GameCommands onClick={onGameCommand} />
|
||||||
|
<p class="py-4 text-center">STATUS: {status}</p>
|
||||||
|
|
||||||
|
<div class="mx-auto grid max-w-xl grid-cols-[1fr_1fr_1fr_auto] gap-4">
|
||||||
|
{#each board.grid as row, i (row)}
|
||||||
|
{#each row as cell, j}
|
||||||
|
<Cell
|
||||||
|
value={cell}
|
||||||
|
onclick={onSelect(i, j)}
|
||||||
|
selected={selectedI === i && selectedJ === j}
|
||||||
|
disabled={!board.lockedCells[i][j]}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<SumIndicator value={board.horizontalSum[i]} />
|
||||||
|
{/each}
|
||||||
|
{#each board.verticalSum as s}
|
||||||
|
<SumIndicator value={s} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ValueSelector onclick={onSetValue} used={board.getPlacedNumbers()} />
|
14
src/lib/Cell.svelte
Normal file
14
src/lib/Cell.svelte
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { value, selected, disabled, onclick } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="aspect-square rounded-4xl border"
|
||||||
|
class:bg-amber-600={selected}
|
||||||
|
class:bg-gray-200={disabled}
|
||||||
|
class:cursor-pointer={!disabled}
|
||||||
|
{onclick}
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
{value || '-'}
|
||||||
|
</button>
|
22
src/lib/GameCommands.svelte
Normal file
22
src/lib/GameCommands.svelte
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { onClick } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex justify-center space-x-2">
|
||||||
|
<button
|
||||||
|
class="cursor-pointer rounded-full bg-cyan-500 px-4 py-2 text-sm text-white"
|
||||||
|
onclick={() => onClick('reset')}>Reset</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="cursor-pointer rounded-full bg-cyan-500 px-4 py-2 text-sm text-white"
|
||||||
|
onclick={() => onClick('easy')}>Easy</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="cursor-pointer rounded-full bg-cyan-500 px-4 py-2 text-sm text-white"
|
||||||
|
onclick={() => onClick('medium')}>Medium</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="cursor-pointer rounded-full bg-cyan-500 px-4 py-2 text-sm text-white"
|
||||||
|
onclick={() => onClick('hard')}>Hard</button
|
||||||
|
>
|
||||||
|
</div>
|
9
src/lib/SumIndicator.svelte
Normal file
9
src/lib/SumIndicator.svelte
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { value } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex content-center items-center justify-center">
|
||||||
|
<div class="flex h-9 w-9 items-center justify-center rounded-full border border-sky-400">
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</div>
|
23
src/lib/ValueSelector.svelte
Normal file
23
src/lib/ValueSelector.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { onclick, used } = $props();
|
||||||
|
|
||||||
|
const numbers = Array.from({ length: 9 }, (_, i) => i + 1);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap justify-center space-x-2 py-8">
|
||||||
|
{#each numbers as number (number)}
|
||||||
|
<button
|
||||||
|
class="mr-1 cursor-pointer rounded-full bg-emerald-300 px-4 py-2 disabled:bg-gray-200"
|
||||||
|
onclick={() => onclick(number)}
|
||||||
|
disabled={used.includes(number)}
|
||||||
|
>
|
||||||
|
{number}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
<button
|
||||||
|
class="mr-1 cursor-pointer rounded-full bg-emerald-300 px-4 py-2 disabled:bg-gray-200"
|
||||||
|
onclick={() => onclick()}
|
||||||
|
>
|
||||||
|
sup
|
||||||
|
</button>
|
||||||
|
</div>
|
58
src/lib/core/board.test.ts
Normal file
58
src/lib/core/board.test.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import '@testing-library/jest-dom/vitest';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { Board, Difficulty } from './board';
|
||||||
|
|
||||||
|
describe('Board', () => {
|
||||||
|
test('shape', () => {
|
||||||
|
const board = new Board();
|
||||||
|
|
||||||
|
expect(board.grid.length).toBe(3);
|
||||||
|
expect(board.grid[0].length).toBe(3);
|
||||||
|
expect(board.horizontalSum.length).toBe(3);
|
||||||
|
expect(board.verticalSum.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isComplete', () => {
|
||||||
|
const board = new Board();
|
||||||
|
expect(board.isComplete()).toBe(true);
|
||||||
|
|
||||||
|
// remove one number
|
||||||
|
board.setByPos(3);
|
||||||
|
expect(board.isComplete()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isValid', () => {
|
||||||
|
const board = new Board();
|
||||||
|
expect(board.isValid()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isValid ko', () => {
|
||||||
|
const board = new Board();
|
||||||
|
board.horizontalSum[1] = -1;
|
||||||
|
expect(board.isValid()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setByPos', () => {
|
||||||
|
const board = new Board();
|
||||||
|
// set board from 9 to 1
|
||||||
|
for (let index = 0; index < 9; index++) {
|
||||||
|
board.setByPos(index, 9 - index);
|
||||||
|
}
|
||||||
|
expect(board.grid[0][0]).toBe(9);
|
||||||
|
expect(board.grid[0][2]).toBe(7);
|
||||||
|
expect(board.grid[1][0]).toBe(6);
|
||||||
|
expect(board.grid[1][1]).toBe(5);
|
||||||
|
expect(board.grid[2][2]).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prepare', () => {
|
||||||
|
const board = new Board();
|
||||||
|
board.prepare(Difficulty.Easy);
|
||||||
|
|
||||||
|
let emptyCellNb = 0;
|
||||||
|
for (const cell of board) {
|
||||||
|
if (cell === undefined) emptyCellNb++;
|
||||||
|
}
|
||||||
|
expect(emptyCellNb).toBe(9 - 3);
|
||||||
|
});
|
||||||
|
});
|
130
src/lib/core/board.ts
Normal file
130
src/lib/core/board.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
export enum Difficulty {
|
||||||
|
Easy,
|
||||||
|
Medium,
|
||||||
|
Hard,
|
||||||
|
Last,
|
||||||
|
Done
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Case = number | undefined;
|
||||||
|
|
||||||
|
function shuffleArray<T>(array: Array<T>): void {
|
||||||
|
for (let i = array.length - 1; i >= 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNumberToKeepFromDifficulty(difficulty: Difficulty) {
|
||||||
|
const rules = {
|
||||||
|
// 0: 8,
|
||||||
|
0: 3,
|
||||||
|
1: 2,
|
||||||
|
2: 1,
|
||||||
|
3: 8,
|
||||||
|
4: 9
|
||||||
|
};
|
||||||
|
return rules[difficulty];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Board {
|
||||||
|
grid: Case[][];
|
||||||
|
verticalSum: number[];
|
||||||
|
horizontalSum: number[];
|
||||||
|
lockedCells: boolean[][];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const numbers = Array.from({ length: 9 }, (_, i) => i + 1);
|
||||||
|
shuffleArray(numbers);
|
||||||
|
|
||||||
|
this.grid = [];
|
||||||
|
this.verticalSum = new Array(3).fill(0);
|
||||||
|
this.horizontalSum = [];
|
||||||
|
this.lockedCells = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const row: number[] = [];
|
||||||
|
for (let j = 0; j < 3; j++) {
|
||||||
|
row[j] = numbers.pop() || 0;
|
||||||
|
this.verticalSum[j] += row[j];
|
||||||
|
}
|
||||||
|
this.grid.push(row);
|
||||||
|
this.horizontalSum[i] = row.reduce((partialSum, a) => partialSum + a, 0);
|
||||||
|
|
||||||
|
this.lockedCells[i] = [false, false, false];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
let index = 0;
|
||||||
|
return {
|
||||||
|
next: () => {
|
||||||
|
if (index < 9) {
|
||||||
|
const [row, col] = this.positionToRowCol(index++);
|
||||||
|
return { value: this.grid[row][col], done: false };
|
||||||
|
} else {
|
||||||
|
return { done: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isComplete(): boolean {
|
||||||
|
for (const cell of this) {
|
||||||
|
if (cell === undefined) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid(): boolean {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const rowSum = this.grid[i].reduce((partialSum, a) => (partialSum || 0) + (a || 0), 0);
|
||||||
|
const colSum = (this.grid[0][i] || 0) + (this.grid[1][i] || 0) + (this.grid[2][i] || 0);
|
||||||
|
if (rowSum !== this.horizontalSum[i] || colSum !== this.verticalSum[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare(difficulty: Difficulty) {
|
||||||
|
const numbers = Array.from({ length: 9 }, (_, i) => i);
|
||||||
|
shuffleArray(numbers);
|
||||||
|
const numberToKeep = getNumberToKeepFromDifficulty(difficulty);
|
||||||
|
const posToHide = numbers.slice(numberToKeep, 9);
|
||||||
|
|
||||||
|
posToHide.forEach((pos) => {
|
||||||
|
this.setByPos(pos);
|
||||||
|
// lock visible cells
|
||||||
|
const [row, col] = this.positionToRowCol(pos);
|
||||||
|
this.lockedCells[row][col] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
console.log('ici');
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
for (let j = 0; j < 3; j++) {
|
||||||
|
if (this.lockedCells[i][j]) this.grid[i][j] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setByPos(pos: number, value: Case = undefined) {
|
||||||
|
if (pos < 0 || pos > 8) throw new Error('position must be between 0 and 8!');
|
||||||
|
if (value && (value < 1 || value > 9)) throw new Error('value must be between 1 and 9!');
|
||||||
|
|
||||||
|
const [row, col] = this.positionToRowCol(pos);
|
||||||
|
this.grid[row][col] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
positionToRowCol(position: number) {
|
||||||
|
return [Math.floor(position / 3), position % 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlacedNumbers() {
|
||||||
|
const res = [];
|
||||||
|
for (const pos of this) {
|
||||||
|
if (pos !== undefined) res.push(pos);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
// place files you want to import through the `$lib` alias in this folder.
|
|
@ -1,2 +1,7 @@
|
|||||||
<h1>Welcome to SvelteKit</h1>
|
<script>
|
||||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
import Board from '$lib/Board.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1 class="mb-5 text-center text-6xl font-medium">Fubuki</h1>
|
||||||
|
|
||||||
|
<Board />
|
||||||
|
@ -6,9 +6,10 @@ const config = {
|
|||||||
// Consult https://svelte.dev/docs/kit/integrations
|
// Consult https://svelte.dev/docs/kit/integrations
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter()
|
adapter: adapter({
|
||||||
|
fallback: 'index.html'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user