rules dialog, close #1 and close #18

This commit is contained in:
Daniel Bulant 2023-08-04 15:57:54 +02:00
parent 63d67c403e
commit 4fd808b0d3
4 changed files with 181 additions and 25 deletions

View file

@ -0,0 +1,34 @@
<script lang="ts">
import { fade, fly } from "svelte/transition";
import { DEFAULT_TRANSITION_DURATION } from "./config";
export var visible: boolean = false;
const duration = DEFAULT_TRANSITION_DURATION;
function keydown(e: KeyboardEvent) {
if(e.key === "Escape") visible = false;
}
</script>
{#if visible}
<div class="backdrop" transition:fade={{ duration: duration / 2 }} on:click|self={() => visible = false} on:keydown={keydown}>
</div>
<div class="dialog" in:fly={{ duration: duration / 1.5, y: 15 }} out:fly={{ duration, y: -15 }}>
<slot></slot>
</div>
{/if}
<style lang="postcss">
.backdrop {
@apply fixed top-0 left-0 w-full h-full bg-black/50 z-50;
}
.dialog {
@apply bg-white border-1 border-black/70 border-solid rounded-lg p-8
fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 max-h-80vh overflow-y-auto max-w-80vw;
}
:global(.dark) .dialog {
@apply bg-black border-white/50;
}
</style>

View file

@ -0,0 +1,96 @@
<script lang="ts">
import Dialog from "./Dialog.svelte";
import BackButton from "./backButton.svelte";
import Game from "./game.svelte";
export var visible: boolean = false;
let vsm = typeof window !== "undefined" ? Math.min(window.innerWidth, window.innerHeight) / 2.3 : 256;
function backClicked(e: MouseEvent) {
e.preventDefault();
visible = false;
}
</script>
<Dialog bind:visible>
<div class="absolute top-0 left-0">
<BackButton href="/" on:click={backClicked} />
</div>
<h1>Rules</h1>
<p>How do I play the game?</p>
<h2>You choose where your opponent will play</h2>
<p>The board is divided into sub-boards, and you can always only play in one of them. Based on where you place your
piece, the opponent will have to play in the corresponding sub-board.
</p>
<p class="desktop-only">On desktop, you can move your mouse over the field to see where the opponent "will be sent".</p>
<div class="flex" style:--vsm="{vsm*1.15}px">
<div class="game-container" >
<Game readonly showMoveList={false} autoCalculateState={false} innerWidthOverride={vsm} innerHeightOverride={vsm} />
</div>
<div class="game-container">
<Game readonly showMoveList={false} autoCalculateState={false} innerWidthOverride={vsm} innerHeightOverride={vsm}
moves={[{ p: 1, i: 4, j: 8 }]}
/>
</div>
</div>
<p>When you can't play in the target board, you'll stay in the current board. If you can't stay, you'll play in the board the previous move was made in.
You can see move list on the right to see where you might get sent. Mouse preview shows this correctly.
</p>
<h2>Three in a row to win a board</h2>
<p>Get 3 in a row/column/diagonally in a sub-board to with that sub-board. A larger symbol will be drawn.</p>
<div class="flex" style:--vsm="{vsm*1.15}px">
<div class="game-container" >
<Game readonly showMoveList={false} autoCalculateState={false} innerWidthOverride={vsm} innerHeightOverride={vsm}
moves={[{ p: 1, i: 4, j: 6}, { p: 1, i: 4, j: 4}]}
/>
</div>
<div class="game-container">
<Game readonly showMoveList={false} autoCalculateState={false} innerWidthOverride={vsm} innerHeightOverride={vsm}
moves={[{ p: 1, i: 4, j: 6}, { p: 1, i: 4, j: 4}]}
containerStates={[0,0,0,0,1,0,0,0,0]}
defaultHighlightedContainer={4}
/>
</div>
</div>
<h2>Three boards in a row to win the game</h2>
<p>Get 3 sub-boards in a row/column/diagonally to win the overall game.</p>
<div class="flex" style:--vsm="{vsm*1.15}px">
<div class="game-container" >
<Game readonly showMoveList={false} autoCalculateState={false} innerWidthOverride={vsm} innerHeightOverride={vsm}
moves={[{ p: 1, i: 2, j: 6}, { p: 1, i: 2, j: 4}, { p: 1, i: 2, j: 2}]}
containerStates={[0,0,0,0,1,0,1,0,0]}
/>
</div>
<div class="game-container">
<Game readonly showMoveList={false} autoCalculateState={false} innerWidthOverride={vsm} innerHeightOverride={vsm}
moves={[]}
containerStates={[0,0,1,0,1,0,1,0,0]}
overallState={1}
defaultHighlightedContainer={4}
/>
</div>
</div>
</Dialog>
<style lang="postcss">
.flex {
@apply flex -mx-5 gap-2;
}
.desktop-only {
display: none;
}
@media (pointer: fine) {
.desktop-only {
display: block;
}
}
.game-container {
width: var(--vsm);
height: var(--vsm);
overflow: hidden;
}
</style>

View file

@ -10,6 +10,13 @@
export var twoPlayer: boolean = false;
export var selfName: string | null = null;
export var opponentName: string | null = null;
export var readonly: boolean = false;
export var defaultHighlightedContainer: number | null = null;
export var defaultHoveredPiece: { i: number, j: number } | null = null;
export var autoCalculateState: boolean = true;
export var showMoveList: boolean = true;
export var innerWidthOverride: number | null = null;
export var innerHeightOverride: number | null = null;
const dispatch = createEventDispatcher();
@ -25,11 +32,11 @@
'bottom right'
];
var hoveredPiece: null | { i: number, j: number } = null;
var hoveredPiece: null | { i: number, j: number } = defaultHoveredPiece;
var highlightedContainer: null | number = null;
var highlightedContainer: null | number = defaultHighlightedContainer;
$: highlightedContainer = hoveredPiece ? highlightContainerByPiece(hoveredPiece.j) : null;
$: highlightedContainer = hoveredPiece ? highlightContainerByPiece(hoveredPiece.j) : defaultHighlightedContainer;
function highlightContainerByPiece(j: number) {
if(!containerStates[j]) return j;
@ -47,7 +54,7 @@
var currentContainer: number = 4;
var currentPlayer: 1 | 2 = 1;
var moves: { p: 1 | 2, i: number, j: number }[] = [];
export var moves: { p: 1 | 2, i: number, j: number }[] = [];
$: currentContainer = getCurrentContainer(moves);
$: currentPlayer = moves[moves.length - 1]?.p == 1 ? 2 : 1;
@ -61,10 +68,11 @@
return backtrack() ?? -1;
}
let containerStates = new Array(9).fill(0);
let overallState = 0;
export let containerStates = new Array(9).fill(0);
export let overallState = 0;
function addMove(i: number, j: number) {
if(readonly) return;
if(moves.find(move => move.i == i && move.j == j))
return;
if(currentContainer !== i) return;
@ -97,6 +105,7 @@
export { addPlayerMove };
function updateContainerStates() {
if(!autoCalculateState) return;
for(var i in containerStates) {
if(containerStates[i]) continue;
var containerMoves = moves.filter(move => move.i === Number(i));
@ -153,6 +162,7 @@
function reset() {
moves = [];
containerStates = new Array(9).fill(0);
overallState = 0;
}
function check(e: MouseEvent) {
@ -186,15 +196,24 @@
return () => clearTimeout(i);
});
let innerWidth = typeof window !== "undefined" ? window.innerWidth : 0;
let innerHeight = typeof window !== "undefined" ? window.innerHeight : 0;
let winnerWidth = typeof window !== "undefined" ? window.innerWidth : 0;
let winnerHeight = typeof window !== "undefined" ? window.innerHeight : 0;
let innerWidth = innerWidthOverride || winnerWidth;
let innerHeight = innerHeightOverride || winnerHeight;
$: innerWidth = innerWidthOverride || winnerWidth;
$: innerHeight = innerHeightOverride || winnerHeight;
updateContainerStates();
</script>
<svelte:window bind:innerWidth bind:innerHeight on:click={() => hoveredPiece = null} />
<svelte:window bind:innerWidth={winnerWidth} bind:innerHeight={winnerHeight} on:click={() => hoveredPiece = defaultHoveredPiece} />
{#if !readonly}
<BackButton href="/" on:click={check}/>
{/if}
{#if !twoPlayer}
{#if !twoPlayer && !readonly}
<!-- I have no idea why x is inverted here -->
<div transition:fly={{ duration, delay: duration, x: 120, opacity: 0 }} on:click={reset} on:keydown={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"
@ -210,7 +229,7 @@
</div>
{/if}
{#if innerWidth < 1024}
{#if innerWidth < 1024 && showMoveList}
<div transition:fly={{ duration, delay: 0, x: 60, opacity: 0 }} class="fixed top-0 right-0 w-4 h-4 m-4 p-2 menu cursor-pointer" on:click={() => movesShown = !movesShown} on:keydown={() => movesShown = !movesShown}>
<svg width=16 height=16>
<line y1="2" y2="2" x1="0" x2="100%" stroke="currentColor" stroke-width="2" />
@ -224,7 +243,7 @@
</div>
{/if}
<main class:disabled={overallState} class="flex flex-wrap h-100vh w-100vw overflow-hidden items-center" style:--vw={innerWidth} style:--vh={innerHeight} style:--vmin={Math.min(innerWidth, innerHeight)}>
<main class:disabled={overallState} class:fullsize={!innerHeightOverride && !innerWidthOverride} class="flex flex-wrap overflow-hidden items-center" style:--vw={innerWidth} style:--vh={innerHeight} style:--vmin={Math.min(innerWidth, innerHeight)}>
<div class="board relative p-8" style:translate="{Math.min(0, (1 - innerWidth / 768) * -50)}% {Math.min(0, (1 - innerHeight / 856) * -50)}%">
{#each classes as className, i}
<div
@ -249,8 +268,8 @@
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; }}
on:mouseleave={() => { if(hoveredPiece?.i == i && hoveredPiece.j == j) hoveredPiece = defaultHoveredPiece; }}
>
<!-- Focus breaks phones -->
<!-- on:focus={() => { if(currentContainer == i) hoveredPiece = { i, j }}} -->
@ -367,8 +386,8 @@
{/key}
</div>
{#if movesShown || innerWidth >= 1024 || innerWidth / innerHeight > 1.4}
{#if showMoveList && (movesShown || innerWidth >= 1024 || innerWidth / innerHeight > 1.4)}
<div transition:fade={{ duration }} class:hidden={innerWidth / innerHeight > 1.4} class="lg:hidden bg-black/40 fixed inset-0 z-10" on:click={() => movesShown = false} on:keydown={() => movesShown = false} />
<div transition:fly={{ delay: duration * moveDelayMultiplier * 3, duration, x: 160, opacity: 0 }} class="info z-11 min-w-38 px-4 flex-grow-0 flex-shrink overflow-y-auto h-100vh lt-lg:(absolute top-0 right-0 bg-black)">
@ -395,7 +414,7 @@
</div>
{/if}
{#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 }} />
<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 = defaultHoveredPiece }} />
{/each}
<Move latest player={currentPlayer} board={hoveredPiece?.i ?? "?"} piece={hoveredPiece?.j ?? "?"} />
</div>
@ -404,8 +423,10 @@
</main>
<style>
<style lang="postcss">
.fullsize {
@apply h-100vh w-100vw;
}
.info .moves {
@apply p-4 font-mono flex flex-col flex-wrap max-w-185 m-auto;
}

View file

@ -1,5 +1,6 @@
<script>
import DarkmodeIcon from "$lib/DarkmodeIcon.svelte";
import RulesDialog from "$lib/RulesDialog.svelte";
import { themeStore } from "$lib/themeStore";
import { onMount } from "svelte";
import { quadOut } from "svelte/easing";
@ -20,6 +21,8 @@
$themeStore = "dark";
}
}
let rulesVisible = false;
</script>
<svelte:head>
@ -44,7 +47,7 @@
<p>Play with 2 devices, even across the ocean.</p>
</a>
</div>
<div class="rules" in:fly={{ delay: duration, duration, opacity: 0, y: 100, easing: quadOut }}>
<div class="rules" on:click={() => rulesVisible = true} on:keydown={() => {}} in:fly={{ delay: duration, duration, opacity: 0, y: 100, easing: quadOut }}>
<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"
@ -75,7 +78,9 @@
</main>
{/if}
<style>
<RulesDialog bind:visible={rulesVisible} />
<style lang="postcss">
.computer {
@apply w-full bg-black;
aspect-ratio: 1/1;
@ -130,13 +135,13 @@
@apply bg-white/10;
}
.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;
@apply cursor-pointer flex justify-center items-center w-full my-8 p-4 border rounded-lg border-gray-400 border-solid transition-none;
}
.rules:hover {
@apply bg-red-500/3;
@apply bg-black/10 duration-150;
}
.rules:active {
@apply bg-red-500/10;
:global(.dark) .rules:hover {
@apply bg-white/10;
}
.icon {
@apply h-20 w-20 mr-4;