mirror of
https://github.com/danbulant/slightlyComplicatedTicTacToe
synced 2026-06-24 17:21:44 +00:00
clean, working singleplayer
This commit is contained in:
parent
21a3ae9ea3
commit
fba7bf1c99
3 changed files with 274 additions and 30 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Move from "./move.svelte";
|
||||||
|
|
||||||
var classes = [
|
var classes = [
|
||||||
'top left',
|
'top left',
|
||||||
'top middle',
|
'top middle',
|
||||||
|
|
@ -11,60 +13,250 @@
|
||||||
'bottom right'
|
'bottom right'
|
||||||
];
|
];
|
||||||
|
|
||||||
var containers: HTMLDivElement[] = new Array(9);
|
|
||||||
|
|
||||||
var hoveredPiece: null | { i: number, j: number } = null;
|
var hoveredPiece: null | { i: number, j: number } = null;
|
||||||
|
|
||||||
var highlightedContainer: null | number = null;
|
var highlightedContainer: null | number = null;
|
||||||
|
|
||||||
$: highlightedContainer = hoveredPiece?.j ?? null;
|
$: highlightedContainer = hoveredPiece ? highlightContainerByPiece(hoveredPiece.j) : null;
|
||||||
|
|
||||||
|
function highlightContainerByPiece(j: number) {
|
||||||
|
if(!containerStates[j]) return j;
|
||||||
|
return getCurrentContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
function backtrack() {
|
||||||
|
for(let i = moves.length - 1; i >= 0; i--) {
|
||||||
|
if(containerStates[moves[i].i] == 0) {
|
||||||
|
return moves[i].i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var currentContainer: number = 4;
|
var currentContainer: number = 4;
|
||||||
|
var currentPlayer: 1 | 2 = 1;
|
||||||
|
|
||||||
var moves = [
|
var moves: { p: 1 | 2, i: number, j: number }[] = [];
|
||||||
{ p: 1, i: 4, j: 1 },
|
|
||||||
{ p: 2, i: 1, j: 2 },
|
$: currentContainer = getCurrentContainer(moves);
|
||||||
{ p: 1, i: 2, j: 4 }
|
$: currentPlayer = moves[moves.length - 1]?.p == 1 ? 2 : 1;
|
||||||
];
|
|
||||||
|
function getCurrentContainer(_moves?: any) {
|
||||||
|
let last = moves[moves.length - 1]?.j;
|
||||||
|
if(last == null || last == undefined) return 4;
|
||||||
|
|
||||||
|
if(containerStates[last] == 0) return last;
|
||||||
|
return backtrack() ?? -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let containerStates = new Array(9).fill(0);
|
||||||
|
let overallState = 0;
|
||||||
|
|
||||||
|
function addMove(i: number, j: number) {
|
||||||
|
if(moves.find(move => move.i == i && move.j == j))
|
||||||
|
return;
|
||||||
|
if(currentContainer !== i) return;
|
||||||
|
moves.push({ p: currentPlayer, i, j });
|
||||||
|
moves = moves;
|
||||||
|
|
||||||
|
updateContainerStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContainerStates() {
|
||||||
|
for(var i in containerStates) {
|
||||||
|
if(containerStates[i]) continue;
|
||||||
|
var containerMoves = moves.filter(move => move.i === Number(i));
|
||||||
|
var state: number[][] = new Array(3).fill(0).map(t => new Array(3).fill(0));
|
||||||
|
for(var move of containerMoves) {
|
||||||
|
state[Math.floor(move.j / 3)][move.j % 3] = move.p;
|
||||||
|
}
|
||||||
|
|
||||||
|
var winner = 0;
|
||||||
|
for(let num = 0; num < 3; num++) {
|
||||||
|
if(state[num][0] == state[num][1] && state[num][1] == state[num][2] && state[num][0] != 0) {
|
||||||
|
winner = state[num][0];
|
||||||
|
}
|
||||||
|
if(state[0][num] == state[1][num] && state[1][num] == state[2][num] && state[0][num] != 0) {
|
||||||
|
winner = state[0][num];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(state[0][0] == state[1][1] && state[1][1] == state[2][2] && state[0][0] != 0) {
|
||||||
|
winner = state[0][0];
|
||||||
|
}
|
||||||
|
if(state[0][2] == state[1][1] && state[1][1] == state[2][0] && state[0][2] != 0) {
|
||||||
|
winner = state[0][2];
|
||||||
|
}
|
||||||
|
if(!winner && containerMoves.length == 9) {
|
||||||
|
winner = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
containerStates[i] = winner;
|
||||||
|
}
|
||||||
|
|
||||||
|
overallState = 0;
|
||||||
|
|
||||||
|
if(containerStates[0] == containerStates[1] && containerStates[1] == containerStates[2] && containerStates[0] != 0) {
|
||||||
|
overallState = containerStates[0];
|
||||||
|
}
|
||||||
|
if(containerStates[3] == containerStates[4] && containerStates[4] == containerStates[5] && containerStates[3] != 0) {
|
||||||
|
overallState = containerStates[3];
|
||||||
|
}
|
||||||
|
if(containerStates[6] == containerStates[7] && containerStates[7] == containerStates[8] && containerStates[6] != 0) {
|
||||||
|
overallState = containerStates[6];
|
||||||
|
}
|
||||||
|
if(containerStates[0] == containerStates[3] && containerStates[3] == containerStates[6] && containerStates[0] != 0) {
|
||||||
|
overallState = containerStates[0];
|
||||||
|
}
|
||||||
|
if(containerStates[1] == containerStates[4] && containerStates[4] == containerStates[7] && containerStates[1] != 0) {
|
||||||
|
overallState = containerStates[1];
|
||||||
|
}
|
||||||
|
if(containerStates[2] == containerStates[5] && containerStates[5] == containerStates[8] && containerStates[2] != 0) {
|
||||||
|
overallState = containerStates[2];
|
||||||
|
}
|
||||||
|
if(containerStates[0] == containerStates[4] && containerStates[4] == containerStates[8] && containerStates[0] != 0) {
|
||||||
|
overallState = containerStates[0];
|
||||||
|
}
|
||||||
|
if(containerStates[2] == containerStates[4] && containerStates[4] == containerStates[6] && containerStates[2] != 0) {
|
||||||
|
overallState = containerStates[2];
|
||||||
|
}
|
||||||
|
if(!overallState && moves.length == 81) {
|
||||||
|
overallState = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
moves = [];
|
||||||
|
containerStates = new Array(9).fill(0);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="flex">
|
<a href="/" class="arrow-back fixed top-0 left-0 w-4 h-4 m-4 p-2 transform transition-transform hover:-translate-x-1">
|
||||||
<div class="board">
|
<svg width="16" height="16">
|
||||||
|
<line y1="50%" x1="0" y2="50%" x2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line y1="50%" x1="0" y2="100%" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line y1="50%" x1="0" y2="0" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div on:click={reset} class="reload fixed top-0 left-10 w-4 h-4 m-4 p-2 transform transition-transform rotate-180 hover:rotate-360 active:rotate-540">
|
||||||
|
<svg fill="currentColor" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 489.645 489.645" xml:space="preserve" class="w-full h-full">
|
||||||
|
<g>
|
||||||
|
<path d="M460.656,132.911c-58.7-122.1-212.2-166.5-331.8-104.1c-9.4,5.2-13.5,16.6-8.3,27c5.2,9.4,16.6,13.5,27,8.3
|
||||||
|
c99.9-52,227.4-14.9,276.7,86.3c65.4,134.3-19,236.7-87.4,274.6c-93.1,51.7-211.2,17.4-267.6-70.7l69.3,14.5
|
||||||
|
c10.4,2.1,21.8-4.2,23.9-15.6c2.1-10.4-4.2-21.8-15.6-23.9l-122.8-25c-20.6-2-25,16.6-23.9,22.9l15.6,123.8
|
||||||
|
c1,10.4,9.4,17.7,19.8,17.7c12.8,0,20.8-12.5,19.8-23.9l-6-50.5c57.4,70.8,170.3,131.2,307.4,68.2
|
||||||
|
C414.856,432.511,548.256,314.811,460.656,132.911z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class:disabled={overallState} class="flex flex-wrap min-h-100vh min-w-full items-center">
|
||||||
|
<div class="board relative">
|
||||||
{#each classes as className, i}
|
{#each classes as className, i}
|
||||||
<div bind:this={containers[i]} class:current={currentContainer === i} class:highlighted={highlightedContainer === i} class="squares-container {className}">
|
<div class:disabled={containerStates[i]} class:current={currentContainer === i} class:highlighted={highlightedContainer === i} class="squares-container {className}">
|
||||||
{#each (new Array(9)) as _, j}
|
{#each (new Array(9)) as _, j}
|
||||||
<div class="square" on:click={() => console.log(i, j)} on:mouseover={() => hoveredPiece = { i, j }} on:mouseleave={() => { if(hoveredPiece?.i == i && hoveredPiece.j == j) hoveredPiece = null; }}>
|
{@const move = moves.find(move => move.i == i && move.j == j)}
|
||||||
{#if moves.find(move => move.i == i && move.j == j)}
|
<div on:click={() => addMove(i, j)} class="square" class:move class:preview={!move} class:cross={move && move.p==1} class:circle={move && move.p==2} on:mouseover={() => hoveredPiece = { i, j }} on:mouseleave={() => { if(hoveredPiece?.i == i && hoveredPiece.j == j) hoveredPiece = null; }}>
|
||||||
{#if moves.find(move => move.i == i && move.j == j)?.p == 1}
|
{#if move}
|
||||||
|
{#if move.p == 1}
|
||||||
<svg width="16" height="16">
|
<svg width="16" height="16">
|
||||||
<line x1="0" y1="0" x2="100%" y2="100%" stroke="black" stroke-width="2" />
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
<line x1="100%" y1="0" x2="0" y2="100%" stroke="black" stroke-width="2" />
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
</svg>
|
</svg>
|
||||||
{:else}
|
{:else}
|
||||||
<svg width="16" height="16">
|
<svg width="16" height="16">
|
||||||
<circle cx="50%" cy="50%" r="7" stroke="black" stroke-width="2" fill="none" />
|
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
{#if currentPlayer == 1}
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if containerStates[i] == 1}
|
||||||
|
<div class="winner winner-1">
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{:else if containerStates[i] == 2}
|
||||||
|
<div class="winner winner-2">
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{:else if containerStates[i] == 3}
|
||||||
|
<div class="winner winner-3">
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if overallState == 1}
|
||||||
|
<div class="winner winner-1">
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{:else if overallState == 2}
|
||||||
|
<div class="winner winner-2">
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{:else if overallState == 3}
|
||||||
|
<div class="winner winner-3">
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="moves">
|
<div class="info min-w-60 px-4 min-h-full">
|
||||||
{#each moves as move}
|
<div class="moves">
|
||||||
<div class="move">{move.p == 1 ? "X" : "O"} B{move.i} #{move.j}</div>
|
{#each moves as move}
|
||||||
{/each}
|
<Move player={move.p} board={move.i} piece={move.j} />
|
||||||
|
{/each}
|
||||||
|
<Move latest player={currentPlayer} board={hoveredPiece?.i ?? "?"} piece={hoveredPiece?.j ?? "?"} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div class="chat">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.winner {
|
||||||
|
@apply absolute inset-4 pointer-events-none;
|
||||||
|
}
|
||||||
|
.winner svg {
|
||||||
|
@apply w-full h-full;
|
||||||
|
}
|
||||||
|
.winner-2 {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
||||||
|
.winner-1 {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
.moves {
|
.moves {
|
||||||
@apply p-4 font-mono;
|
@apply p-4 font-mono;
|
||||||
}
|
}
|
||||||
|
|
@ -72,27 +264,35 @@
|
||||||
@apply grid grid-cols-3 grid-rows-3 gap-10 w-max h-max m-auto my-5;
|
@apply grid grid-cols-3 grid-rows-3 gap-10 w-max h-max m-auto my-5;
|
||||||
}
|
}
|
||||||
.squares-container {
|
.squares-container {
|
||||||
@apply grid grid-cols-3 grid-rows-3 gap-5 w-max h-max;
|
@apply grid grid-cols-3 grid-rows-3 gap-5 w-max h-max bg-black/5 opacity-35 relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.square {
|
.square {
|
||||||
@apply border-black border-solid border w-6 h-6 p-4 cursor-pointer flex items-center justify-center;
|
@apply border-black border-solid border w-6 h-6 p-4 cursor-pointer flex items-center justify-center;
|
||||||
}
|
}
|
||||||
|
.square.preview svg {
|
||||||
|
@apply hidden opacity-20;
|
||||||
|
}
|
||||||
|
.square.preview:hover svg {
|
||||||
|
@apply block;
|
||||||
|
}
|
||||||
|
.square.move, .disabled .square {
|
||||||
|
@apply cursor-not-allowed;
|
||||||
|
}
|
||||||
.square svg {
|
.square svg {
|
||||||
@apply w-full h-full;
|
@apply w-full h-full;
|
||||||
}
|
}
|
||||||
.square:hover {
|
.square:hover {
|
||||||
@apply bg-black/20;
|
@apply bg-black/5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlighted {
|
.highlighted {
|
||||||
@apply bg-red-700/20;
|
@apply opacity-75;
|
||||||
}
|
}
|
||||||
.current {
|
.current {
|
||||||
@apply bg-green-700/20;
|
@apply opacity-100;
|
||||||
}
|
}
|
||||||
.highlighted.current {
|
.highlighted.current {
|
||||||
@apply bg-black/20;
|
@apply bg-yellow-600/5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top.left, .middle-middle, .bottom.middle, .bottom.right, .middle.right, .top.left .square:first-child, .middle-middle .square:first-child, .bottom.middle .square:first-child, .bottom.right .square:first-child, .middle.right .square:first-child {
|
.top.left, .middle-middle, .bottom.middle, .bottom.right, .middle.right, .top.left .square:first-child, .middle-middle .square:first-child, .bottom.middle .square:first-child, .bottom.right .square:first-child, .middle.right .square:first-child {
|
||||||
|
|
|
||||||
44
client/src/lib/move.svelte
Normal file
44
client/src/lib/move.svelte
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export var player: number;
|
||||||
|
export var board: number | string;
|
||||||
|
export var piece: number | string;
|
||||||
|
export var latest: boolean = false;
|
||||||
|
</script>
|
||||||
|
<div class="move" class:latest>
|
||||||
|
<span class="player player-{player}">
|
||||||
|
{#if player == 1}
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg width="16" height="16">
|
||||||
|
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<span class="board">B<b>{board}</b></span>
|
||||||
|
<span class="piece">#<b>{piece}</b></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.move {
|
||||||
|
@apply flex gap-2 p-1 items-center leading-none;
|
||||||
|
}
|
||||||
|
.latest {
|
||||||
|
@apply opacity-35;
|
||||||
|
}
|
||||||
|
.player {
|
||||||
|
@apply inline-block w-1em h-1em;
|
||||||
|
margin-top: -0.225em;
|
||||||
|
}
|
||||||
|
.player svg {
|
||||||
|
@apply w-full h-full;
|
||||||
|
}
|
||||||
|
.player-1 {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
|
.player-2 {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue