ssg, !ssr, classes, board init
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
.vscode
|
11
app/package-lock.json
generated
11
app/package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
"@sveltejs/adapter-static": "^3.0.4",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint": "^9.6.0",
|
||||||
@@ -988,6 +989,16 @@
|
|||||||
"@sveltejs/kit": "^2.0.0"
|
"@sveltejs/kit": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sveltejs/adapter-static": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-Qm4GAHCnRXwfWG9/AtnQ7mqjyjTs7i0Opyb8H2KH9rMR7fLxqiPx/tXeoE6HHo66+72CjyOb4nFH3lrejY4vzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@sveltejs/kit": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.5.26",
|
"version": "2.5.26",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.26.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.26.tgz",
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
"@sveltejs/adapter-static": "^3.0.4",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint": "^9.6.0",
|
||||||
|
@@ -1,21 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Cell from '$lib/cell.svelte';
|
import { Board } from '$lib/state.svelte';
|
||||||
import type { Board } from '$lib/state.svelte';
|
import { Crosshair, Ship } from 'lucide-svelte';
|
||||||
|
|
||||||
const callback = (i: number, j: number) => {
|
let { board, callback }: { board: Board; callback: (i: number, j: number) => void } = $props();
|
||||||
if (!isOpponent) return;
|
|
||||||
console.log(`Cell clicked at row ${i}, column ${j}`);
|
|
||||||
board[i][j] = 'hit';
|
|
||||||
};
|
|
||||||
let board: Board = $state(Array(10).fill(Array(10).fill('empty')));
|
|
||||||
|
|
||||||
let { isOpponent } = $props<{ isOpponent: boolean }>();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid grid-cols-10 gap-1 bg-blue-100 p-2 rounded-lg">
|
<div class="grid grid-cols-10 gap-1 bg-blue-100 p-2 rounded-lg">
|
||||||
{#each board as row, i}
|
{#each board.board as row, i}
|
||||||
{#each row as cell, j}
|
{#each row as cell, j}
|
||||||
<Cell {cell} {isOpponent} callback={() => callback(i, j)} />
|
<button
|
||||||
|
class="aspect-square bg-blue-200 flex items-center justify-center {!board.isOpponent
|
||||||
|
? 'cursor-default'
|
||||||
|
: ''}"
|
||||||
|
onclick={() => callback(i, j)}
|
||||||
|
>
|
||||||
|
{#if cell === 's'}
|
||||||
|
<Ship class="size-3/5 text-blue-500" />
|
||||||
|
{:else if cell === 'h'}
|
||||||
|
<Crosshair class="size-3/5 text-red-500" />
|
||||||
|
{:else if cell === 'm'}
|
||||||
|
<div class="size-3/5 bg-gray-300 rounded-full"></div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Ship, Crosshair } from 'lucide-svelte';
|
|
||||||
|
|
||||||
let { cell, isOpponent, callback } = $props<{
|
|
||||||
cell: string;
|
|
||||||
isOpponent: boolean;
|
|
||||||
callback: () => void;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button class="aspect-square bg-blue-200 flex items-center justify-center" onclick={callback}>
|
|
||||||
{#if cell === 'ship' && !isOpponent}
|
|
||||||
<Ship class="size-3/5 text-blue-500" />
|
|
||||||
{:else if cell === 'hit'}
|
|
||||||
<Crosshair class="size-3/5 text-red-500" />
|
|
||||||
{:else if cell === 'miss'}
|
|
||||||
<div class="size-3/5 bg-gray-300 rounded-full"></div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
@@ -2,7 +2,9 @@
|
|||||||
import { Anchor } from 'lucide-svelte';
|
import { Anchor } from 'lucide-svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="text-center mb-8">
|
<header
|
||||||
<Anchor class="w-16 h-16 text-blue-600 mx-auto mb-4" />
|
class="mb-8 flex items-center w-fit rounded-full px-6 py-3 drop-shadow-md bg-gray-50 mx-auto"
|
||||||
|
>
|
||||||
|
<Anchor class="size-16 mr-8 text-blue-600" />
|
||||||
<h1 class="text-4xl font-bold text-gray-900">Battleship Online</h1>
|
<h1 class="text-4xl font-bold text-gray-900">Battleship Online</h1>
|
||||||
</header>
|
</header>
|
||||||
|
@@ -1,8 +1,53 @@
|
|||||||
|
|
||||||
export type Phase = 'placement' | 'battle' | 'gameover';
|
export type Phase = 'placement' | 'battle' | 'gameover';
|
||||||
export type CellType = 'empty' | 'ship' | 'hit' | 'miss';
|
export type CellType = 'e' | 's' | 'h' | 'm'; // empty, ship, hit, miss
|
||||||
export type Board = Array<Array<CellType>>;
|
|
||||||
|
|
||||||
export class State {
|
export class State {
|
||||||
phase: Phase = $state('placement');
|
phase: Phase = $state('placement');
|
||||||
|
playerBoard = $state(new Board(false));
|
||||||
|
opponentBoard = $state(new Board(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Board {
|
||||||
|
static shipTypes = [5, 4, 3, 3, 2];
|
||||||
|
board: Array<Array<CellType>> = $state(Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => 'e')));
|
||||||
|
isOpponent: boolean = false;
|
||||||
|
|
||||||
|
constructor(isOpponent: boolean) {
|
||||||
|
this.isOpponent = isOpponent;
|
||||||
|
if (!isOpponent) this.randomize();
|
||||||
|
}
|
||||||
|
|
||||||
|
set(x: number, y: number, type: CellType) {
|
||||||
|
this.board[x][y] = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
randomize() {
|
||||||
|
this.board = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => 'e'));
|
||||||
|
for (const shipLength of Board.shipTypes) {
|
||||||
|
while (true) {
|
||||||
|
const dir = Math.round(Math.random());
|
||||||
|
const x = Math.floor(Math.random() * (dir ? 10 : 11 - shipLength));
|
||||||
|
const y = Math.floor(Math.random() * (dir ? (11 - shipLength) : 10));
|
||||||
|
if (this.isOverlapping(x, y, shipLength, dir)) continue;
|
||||||
|
for (let i = 0; i < shipLength; i++) {
|
||||||
|
this.board[dir ? x : x + i][dir ? y + i : y] = 's';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOverlapping(x: number, y: number, length: number, dir: number): boolean {
|
||||||
|
for (let i = -1; i < 2; i++) {
|
||||||
|
for (let j = -1; j < length + 1; j++) {
|
||||||
|
let [tx, ty] = [x + (dir ? i : j), y + (dir ? j : i)];
|
||||||
|
if (tx < 0 || tx >= 10 || ty < 0 || ty >= 10) continue;
|
||||||
|
if (this.board[tx][ty] != 'e') return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
2
app/src/routes/+layout.ts
Normal file
2
app/src/routes/+layout.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const prerender = true;
|
||||||
|
export const ssr = false;
|
@@ -25,17 +25,22 @@
|
|||||||
<div class="grid md:grid-cols-2 gap-8">
|
<div class="grid md:grid-cols-2 gap-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-medium text-gray-700 mb-2">Your Board</h3>
|
<h3 class="text-lg font-medium text-gray-700 mb-2">Your Board</h3>
|
||||||
<Board isOpponent={false} />
|
<Board board={gameState.playerBoard} callback={() => {}} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-medium text-gray-700 mb-2">Opponent's Board</h3>
|
<h3 class="text-lg font-medium text-gray-700 mb-2">Opponent's Board</h3>
|
||||||
<Board isOpponent={true} />
|
<Board
|
||||||
|
board={gameState.opponentBoard}
|
||||||
|
callback={(i, j) => gameState.opponentBoard.set(i, j, 'h')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center space-x-4">
|
<div class="flex justify-center space-x-4">
|
||||||
{#if gameState.phase === 'placement'}
|
{#if gameState.phase === 'placement'}
|
||||||
<button class="btn btn-primary">Rotate Ship</button>
|
<button class="btn btn-primary" onclick={() => gameState.playerBoard.randomize()}
|
||||||
|
>Randomize</button
|
||||||
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="btn btn-primary">Fire!</button>
|
<button class="btn btn-primary">Fire!</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import adapter from '@sveltejs/adapter-auto';
|
import adapter from '@sveltejs/adapter-static';
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
@@ -6,12 +6,16 @@ const config = {
|
|||||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
adapter: adapter({
|
||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
// default options are shown. On some platforms
|
||||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
// these options are set automatically — see below
|
||||||
adapter: adapter()
|
pages: 'build',
|
||||||
|
assets: 'build',
|
||||||
|
fallback: undefined,
|
||||||
|
precompress: false,
|
||||||
|
strict: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
8
src/game.rs
Normal file
8
src/game.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Board([[Option<char>; 10]; 10]);
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
// pub async fn new
|
||||||
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
use axum::{routing::get, Router};
|
mod game;
|
||||||
|
|
||||||
|
use axum::{routing::get, Json, Router};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
Reference in New Issue
Block a user