mirror of
https://github.com/danbulant/slightlyComplicatedTicTacToe
synced 2026-06-20 06:51:40 +00:00
basic main menu, more style improvements
This commit is contained in:
parent
d30f029627
commit
eec2afb9da
13 changed files with 235 additions and 33 deletions
|
|
@ -10,16 +10,7 @@
|
|||
padding: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
font-family: "Square", "Squareo", monospace;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Square';
|
||||
src: url("/assets/Square.ttf") format("truetype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Squareo';
|
||||
src: url("/assets/Squareo.ttf") format("truetype");
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
</style>
|
||||
%sveltekit.head%
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import Move from "./move.svelte";
|
||||
|
||||
export var self: 1 | 2 = 1;
|
||||
export var twoPlayer: boolean = false;
|
||||
export var selfName: string | null = null;
|
||||
export var opponentName: string | null = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
var classes = [
|
||||
'top left',
|
||||
'top middle',
|
||||
|
|
@ -55,9 +63,12 @@
|
|||
if(moves.find(move => move.i == i && move.j == j))
|
||||
return;
|
||||
if(currentContainer !== i) return;
|
||||
if(twoPlayer && currentPlayer !== self) return;
|
||||
moves.push({ p: currentPlayer, i, j });
|
||||
moves = moves;
|
||||
|
||||
dispatch("move", { i, j, p: currentPlayer });
|
||||
|
||||
updateContainerStates();
|
||||
}
|
||||
|
||||
|
|
@ -151,12 +162,12 @@
|
|||
</div>
|
||||
|
||||
<main class:disabled={overallState} class="flex flex-wrap min-h-100vh min-w-full items-center">
|
||||
<div class="board relative">
|
||||
<div class="board relative p-8">
|
||||
{#each classes as className, i}
|
||||
<div class:hover={hoveredPiece?.i == i} class:disabled={containerStates[i]} class:current={currentContainer === i} class:highlighted={highlightedContainer === i} class="squares-container {className}">
|
||||
{#each (new Array(9)) as _, j}
|
||||
{@const move = moves.find(move => move.i == i && move.j == j)}
|
||||
<div on:click={() => addMove(i, j)} class:hover={hoveredPiece?.i == i && hoveredPiece.j == 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; }}>
|
||||
<div on:click={() => addMove(i, j)} class:hover={hoveredPiece?.i == i && hoveredPiece.j == j} class="square" class:move class:preview={!move} class:cross={move && move.p==1} class:circle={move && move.p==2} on:mouseover={() => { if(currentContainer == i) hoveredPiece = { i, j } }} on:mouseleave={() => { if(hoveredPiece?.i == i && hoveredPiece.j == j) hoveredPiece = null; }}>
|
||||
{#if move}
|
||||
{#if move.p == 1}
|
||||
<svg width="16" height="16">
|
||||
|
|
@ -230,9 +241,28 @@
|
|||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="absolute top-200 left-0 right-0 text-center">
|
||||
{#if currentPlayer == 1}
|
||||
<svg width="16" height="16" class="text-red-500">
|
||||
<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" class="text-blue-500">
|
||||
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
</svg>
|
||||
{/if}
|
||||
is on turn.
|
||||
{#if twoPlayer && self == currentPlayer}
|
||||
<b>It is <span class:text-red-500={currentPlayer == 1} class:text-blue-500={currentPlayer == 2}>YOUR</span> {selfName ? "(" + selfName + ")" : ""} turn.</b>
|
||||
{:else if twoPlayer && self != currentPlayer}
|
||||
Waiting for {opponentName || "opponent"}...
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info min-w-60 px-4 min-h-full">
|
||||
<div class="info min-w-38 px-4 h-full overflow-y-auto <md:w-full">
|
||||
<div class="moves">
|
||||
{#each moves as move}
|
||||
<Move player={move.p} board={move.i} piece={move.j} on:mouseover={() => hoveredPiece = { i: move.i, j: move.j }} on:mouseout={() => { if(hoveredPiece?.j == move.j && hoveredPiece.i == move.i) hoveredPiece = null }} />
|
||||
|
|
@ -245,6 +275,9 @@
|
|||
|
||||
|
||||
<style>
|
||||
.info .moves {
|
||||
columns: 9.5rem auto;
|
||||
}
|
||||
.winner {
|
||||
@apply absolute inset-4 pointer-events-none;
|
||||
}
|
||||
|
|
@ -264,7 +297,7 @@
|
|||
@apply grid grid-cols-3 grid-rows-3 gap-10 w-max h-max m-auto my-5;
|
||||
}
|
||||
.squares-container {
|
||||
@apply grid grid-cols-3 grid-rows-3 gap-5 w-max h-max bg-black/5 opacity-35 relative;
|
||||
@apply grid grid-cols-3 grid-rows-3 gap-5 w-max h-max opacity-35 relative;
|
||||
}
|
||||
|
||||
.square {
|
||||
|
|
@ -291,10 +324,10 @@
|
|||
.square:hover, .square.hover {
|
||||
@apply bg-black/5;
|
||||
}
|
||||
.square.hover.cross {
|
||||
.square.hover.cross, .square:hover.cross {
|
||||
@apply text-red-500;
|
||||
}
|
||||
.square.hover.circle {
|
||||
.square.hover.circle, .square:hover.circle {
|
||||
@apply text-blue-500;
|
||||
}
|
||||
.highlighted {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<line x1="0" y1="66%" x2="100%" y2="66%" stroke="currentColor" stroke-width="2" />
|
||||
|
||||
{#if board !== "?"}
|
||||
<rect x="{[0,33,66][Math.floor(board / 3)]}%" y="{[0,33,66][(board % 3)]}%" width="33%" height="33%" fill="currentColor" stroke="none" />
|
||||
<rect y="{[0,33,66][Math.floor(board / 3)]}%" x="{[0,33,66][(board % 3)]}%" width="33%" height="33%" fill="currentColor" stroke="none" />
|
||||
{/if}
|
||||
</svg></span>
|
||||
<span class="piece flex items-center">#{piece == "?" ? "?" : piece + 1}<svg width="16" height="16">
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<line x1="0" y1="66%" x2="100%" y2="66%" stroke="currentColor" stroke-width="2" />
|
||||
|
||||
{#if piece !== "?"}
|
||||
<rect x="{[0,33,66][Math.floor(piece / 3)]}%" y="{[0,33,66][(piece % 3)]}%" width="33%" height="33%" fill="currentColor" stroke="none" />
|
||||
<rect y="{[0,33,66][Math.floor(piece / 3)]}%" x="{[0,33,66][(piece % 3)]}%" width="33%" height="33%" fill="currentColor" stroke="none" />
|
||||
{/if}
|
||||
</svg></span>
|
||||
</div>
|
||||
|
|
|
|||
14
client/src/lib/pageTransition.svelte
Normal file
14
client/src/lib/pageTransition.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
import { fly } from "svelte/transition";
|
||||
export let url = "";
|
||||
</script>
|
||||
|
||||
{#key url}
|
||||
<div
|
||||
in:fly={{ x: -5, duration: 500, delay: 300 }}
|
||||
out:fly={{ x: 5, duration: 500 }}
|
||||
class="absolute bg-inherit w-full"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
{/key}
|
||||
|
|
@ -1,5 +1,20 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import PageTransition from "$lib/pageTransition.svelte";
|
||||
import "uno.css";
|
||||
|
||||
import type { load } from "./+layout";
|
||||
export let data: Awaited<ReturnType<typeof load>>;
|
||||
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<div class="overflow-hidden w-100vw">
|
||||
<PageTransition url={data.url}>
|
||||
<slot />
|
||||
</PageTransition>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(:root), :global(body) {
|
||||
@apply overflow-hidden w-100vw;
|
||||
}
|
||||
</style>
|
||||
6
client/src/routes/+layout.ts
Normal file
6
client/src/routes/+layout.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/** @type {import('./$types').PageLoad} */
|
||||
export const load = async ({ url }) => {
|
||||
return {
|
||||
url: url.pathname
|
||||
}
|
||||
};
|
||||
|
|
@ -1,14 +1,81 @@
|
|||
<script>
|
||||
import Game from "$lib/view/game/game.svelte";
|
||||
import List from "$lib/view/menu/list.svelte";
|
||||
import NameChoose from "$lib/view/menu/nameChoose.svelte";
|
||||
import { connection, room } from "$lib/Websocket";
|
||||
</script>
|
||||
|
||||
{#if !$connection}
|
||||
<NameChoose />
|
||||
{:else if !$room}
|
||||
<List />
|
||||
{:else}
|
||||
<Game />
|
||||
{/if}
|
||||
<main class="flex items-center justify-center flex-col">
|
||||
<div class="chooser">
|
||||
<a href="/localplay" class="single">
|
||||
<h1>Local multiplayer</h1>
|
||||
|
||||
<img src="/computer.svg" alt="">
|
||||
|
||||
<p>A game for two on a single device</p>
|
||||
</a>
|
||||
<a href="/multiplayer" class="multi">
|
||||
<h1>Online multiplayer</h1>
|
||||
|
||||
<img src="/network.svg" alt="">
|
||||
|
||||
<p>Play with 2 devices, even across the ocean.</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="rules">
|
||||
<div class="icon">
|
||||
<svg fill="currentColor" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="800px" height="800px" viewBox="0 0 973.1 973.1" xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<path d="M502.29,788.199h-47c-33.1,0-60,26.9-60,60v64.9c0,33.1,26.9,60,60,60h47c33.101,0,60-26.9,60-60v-64.9
|
||||
C562.29,815,535.391,788.199,502.29,788.199z"/>
|
||||
<path d="M170.89,285.8l86.7,10.8c27.5,3.4,53.6-12.4,63.5-38.3c12.5-32.7,29.9-58.5,52.2-77.3c31.601-26.6,70.9-40,117.9-40
|
||||
c48.7,0,87.5,12.8,116.3,38.3c28.8,25.6,43.1,56.2,43.1,92.1c0,25.8-8.1,49.4-24.3,70.8c-10.5,13.6-42.8,42.2-96.7,85.9
|
||||
c-54,43.7-89.899,83.099-107.899,118.099c-18.4,35.801-24.8,75.5-26.4,115.301c-1.399,34.1,25.8,62.5,60,62.5h49
|
||||
c31.2,0,57-23.9,59.8-54.9c2-22.299,5.7-39.199,11.301-50.699c9.399-19.701,33.699-45.701,72.699-78.1
|
||||
C723.59,477.8,772.79,428.4,795.891,392c23-36.3,34.6-74.8,34.6-115.5c0-73.5-31.3-138-94-193.4c-62.6-55.4-147-83.1-253-83.1
|
||||
c-100.8,0-182.1,27.3-244.1,82c-52.8,46.6-84.9,101.8-96.2,165.5C139.69,266.1,152.39,283.5,170.89,285.8z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Rules</h1>
|
||||
<p>How do I play the game?</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
@apply my-4 p-4 w-max m-auto h-100vh;
|
||||
}
|
||||
.chooser {
|
||||
@apply grid grid-cols-2 items-center justify-center gap-8;
|
||||
}
|
||||
.chooser > a {
|
||||
@apply text-black no-underline cursor-pointer w-full p-8 border rounded-lg border-gray-400 border-solid;
|
||||
}
|
||||
.chooser > .multi {
|
||||
@apply cursor-not-allowed text-gray-500;
|
||||
}
|
||||
.chooser > .multi img {
|
||||
@apply opacity-50;
|
||||
}
|
||||
.rules {
|
||||
@apply cursor-not-allowed text-gray-500 flex justify-center items-center w-full my-8 p-4 border rounded-lg border-gray-400 border-solid;
|
||||
}
|
||||
.icon {
|
||||
@apply h-20 w-20 mr-4;
|
||||
}
|
||||
.icon svg {
|
||||
@apply w-full h-full;
|
||||
}
|
||||
.chooser h1 {
|
||||
@apply text-4xl text-center font-bold;
|
||||
}
|
||||
.rules h1 {
|
||||
@apply text-4xl font-bold;
|
||||
}
|
||||
p {
|
||||
@apply text-gray-500 text-center;
|
||||
}
|
||||
|
||||
* {
|
||||
@apply box-border;
|
||||
}
|
||||
</style>
|
||||
14
client/src/routes/multiplayer/+page.svelte
Normal file
14
client/src/routes/multiplayer/+page.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
import Game from "$lib/view/game/game.svelte";
|
||||
import List from "$lib/view/menu/list.svelte";
|
||||
import NameChoose from "$lib/view/menu/nameChoose.svelte";
|
||||
import { connection, room } from "$lib/Websocket";
|
||||
</script>
|
||||
|
||||
{#if !$connection}
|
||||
<NameChoose />
|
||||
{:else if !$room}
|
||||
<List />
|
||||
{:else}
|
||||
<Game />
|
||||
{/if}
|
||||
3
client/static/circle.svg
Normal file
3
client/static/circle.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16">
|
||||
<circle cx="50%" cy="50%" r="45%" stroke="rgb(59,130,246)" stroke-width="2" fill="none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 130 B |
49
client/static/computer.svg
Normal file
49
client/static/computer.svg
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 1000 1000"
|
||||
enable-background="new 0 0 1000 1000"
|
||||
xml:space="preserve"
|
||||
id="svg52"
|
||||
sodipodi:docname="computer.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs56" /><sodipodi:namedview
|
||||
id="namedview54"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.472"
|
||||
inkscape:cx="-41.313559"
|
||||
inkscape:cy="298.72881"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg52" />
|
||||
<g
|
||||
id="g50"><path
|
||||
d="M920,62.5H80c-38.7,0-70,31.3-70,70v560c0,38.7,31.3,70,70,70h350v105H255c-19.3,0-35,15.7-35,35v35h560v-35c0-19.3-15.7-35-35-35H570v-105h350c38.7,0,70-31.3,70-70v-560C990,93.8,958.7,62.5,920,62.5L920,62.5L920,62.5z M465,692.5c0-19.3,15.7-35,35-35c19.3,0,35,15.7,35,35c0,19.3-15.7,35-35,35C480.7,727.5,465,711.8,465,692.5L465,692.5L465,692.5z M80,622.5v-490h840v490H80L80,622.5z"
|
||||
id="path48" /></g>
|
||||
<path
|
||||
style="fill:#000000"
|
||||
d="M 80.148756,622.27644 157.48856,561.60308"
|
||||
id="path216" /><path
|
||||
style="fill:#000000;stroke:#000000;stroke-width:23;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 74.905379,624.71086 925.83049,128.83725"
|
||||
id="path218"
|
||||
sodipodi:nodetypes="cc" /><g
|
||||
id="g3201"
|
||||
transform="matrix(111.02092,0,0,111.02092,201.23721,225.60162)" /></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
5
client/static/cross.svg
Normal file
5
client/static/cross.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
<svg width="16" height="16">
|
||||
<line x1="0" y1="0" x2="100%" y2="100%" stroke="rgb(239,68,68)" stroke-width="2" />
|
||||
<line x1="100%" y1="0" x2="0" y2="100%" stroke="rgb(239,68,68)" stroke-width="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
5
client/static/network.svg
Normal file
5
client/static/network.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<g><path d="M500,10C229.8,10,10,229.8,10,500c0,270.2,219.8,490,490,490c270.2,0,490-219.8,490-490C990,229.8,770.2,10,500,10z M855.6,294.6H694.2C657,205.3,602.8,136.5,563.4,94.3C688.3,113.7,794.6,189.3,855.6,294.6z M521.8,294.6V114.3c34.4,35.7,86.5,98,124.2,180.3H521.8z M663.7,338.3c15.3,42.6,25.6,89.5,28.1,139.8H521.8V338.3H663.7z M478.2,294.6H354.1c37.7-82.2,89.8-144.5,124.1-180.2V294.6z M478.2,338.3v139.8H308.3c2.5-50.3,12.8-97.2,28.1-139.8H478.2z M264.1,478.1H90c2.6-49.4,13.5-96.6,32.1-139.8h167.5C275.7,381.2,266.3,428,264.1,478.1z M264.1,521.8c2.2,50.2,11.6,97,25.5,139.8H122.1c-18.6-43.3-29.5-90.5-32.1-139.8H264.1z M308.3,521.8h169.9v139.8H336.3C321.1,619.1,310.7,572.2,308.3,521.8z M478.2,705.4v180.3c-34.4-35.7-86.5-98-124.2-180.3H478.2z M521.8,705.4h124.1c-37.7,82.2-89.8,144.5-124.1,180.2V705.4z M521.8,661.7V521.8h169.9c-2.5,50.3-12.8,97.2-28.1,139.8H521.8z M735.9,521.8H910c-2.6,49.4-13.5,96.6-32.1,139.8H710.4C724.3,618.8,733.7,572,735.9,521.8z M735.9,478.1c-2.2-50.2-11.6-97-25.5-139.8h167.5c18.6,43.3,29.5,90.5,32.1,139.8H735.9z M436.6,94.3c-39.4,42.3-93.6,111-130.8,200.3H144.4C205.5,189.3,311.7,113.7,436.6,94.3z M144.4,705.4h161.4c37.2,89.3,91.4,158,130.7,200.3C311.7,886.2,205.5,810.7,144.4,705.4z M563.5,905.7c39.4-42.3,93.5-111,130.7-200.3h161.4C794.6,810.7,688.3,886.2,563.5,905.7z"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
Loading…
Reference in a new issue