mirror of
https://github.com/thib8956/tic-tac-toe-ws.git
synced 2024-11-17 01:16:33 +00:00
integrate grid
This commit is contained in:
parent
774b193e7f
commit
0f705ee45a
225
client.mts
225
client.mts
@ -1,71 +1,80 @@
|
||||
import { Request, Response, Message, Hello, EndGame } from "common.mjs";
|
||||
|
||||
const CELL_SIZE = 150;
|
||||
const GRID_SIZE = CELL_SIZE * 3;
|
||||
const SHAPE_SIZE = 100;
|
||||
const ANIMATE_DURATION = 500; // ms
|
||||
|
||||
|
||||
const ws = new WebSocket("ws://localhost:1234");
|
||||
const grid = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
const canvas = document.getElementById("game") as HTMLCanvasElement | null;
|
||||
const ctx = canvas?.getContext("2d") as CanvasRenderingContext2D | null;
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface Shape {
|
||||
kind: "circle" | "cross";
|
||||
center: Point;
|
||||
hue: number;
|
||||
time: number;
|
||||
}
|
||||
|
||||
let grid = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
let circle = true;
|
||||
let pendingEvts: Point[] = [];
|
||||
let shapes: Shape[] = [];
|
||||
let myId: number | null = null;
|
||||
|
||||
ws.onopen = (e) => {
|
||||
console.log("connected to server");
|
||||
function drawGridBackground(ctx: CanvasRenderingContext2D, origin: Point) {
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.lineWidth = 5;
|
||||
|
||||
if (canvas) {
|
||||
if (ctx) {
|
||||
drawGrid(ctx, grid);
|
||||
canvas.addEventListener("click", (evt: any) => {
|
||||
const {clientX, clientY} = evt;
|
||||
const x = Math.floor(clientX / 90);
|
||||
const y = Math.floor(clientY / 90);
|
||||
if (x < 3 && y < 3) {
|
||||
const msg: Request = { x, y };
|
||||
ws.send(JSON.stringify(msg));
|
||||
}
|
||||
});
|
||||
for (let x = 1; x < 3; ++x) {
|
||||
ctx.moveTo(origin.x + x * CELL_SIZE, 0 + origin.y);
|
||||
ctx.lineTo(origin.x + x * CELL_SIZE, origin.y + GRID_SIZE);
|
||||
}
|
||||
|
||||
for (let y = 1; y < 3; ++y) {
|
||||
ctx.moveTo(origin.x + 0, origin.y + y * CELL_SIZE);
|
||||
ctx.lineTo(origin.x + GRID_SIZE, origin.y + y * CELL_SIZE);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
|
||||
function resizeCanvas(ctx: CanvasRenderingContext2D) {
|
||||
ctx.canvas.width = window.innerWidth;
|
||||
ctx.canvas.height = window.innerHeight;
|
||||
}
|
||||
|
||||
|
||||
function coordToGridIndex(origin: Point, clientPos: Point): [number, number] | undefined {
|
||||
// Coord relative to origin of the grid (origin)
|
||||
const pt = { x: clientPos.x - origin.x, y: clientPos.y - origin.y };
|
||||
const gridIndex: [number, number] = [Math.floor(3 * pt.x / GRID_SIZE), Math.floor(3 * pt.y / GRID_SIZE)];
|
||||
if (gridIndex[0] >= 0 && gridIndex[0] <= 2 && gridIndex[1] >= 0 && gridIndex[1] <= 2) {
|
||||
return gridIndex;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function handlePendingEvts(gridOrigin: Point, ws: WebSocket) {
|
||||
for (const evt of pendingEvts) {
|
||||
const gridIndex = coordToGridIndex(gridOrigin, evt);
|
||||
if (gridIndex) {
|
||||
const [x, y] = gridIndex;
|
||||
const msg: Request = { x, y };
|
||||
ws.send(JSON.stringify(msg));
|
||||
}
|
||||
}
|
||||
};
|
||||
pendingEvts = [];
|
||||
}
|
||||
|
||||
ws.onmessage = (evt) => {
|
||||
const msg: Message = JSON.parse(evt.data);
|
||||
console.log(msg);
|
||||
switch (msg.kind) {
|
||||
case "hello": {
|
||||
myId = (msg.data as Hello).id;
|
||||
const h1 = document.getElementById("title") as HTMLHeadingElement | null;
|
||||
if (h1) {
|
||||
h1.innerText = `connected to server with id ${myId}`
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "update": {
|
||||
if (ctx) {
|
||||
drawGrid(ctx, (msg.data as Response).grid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "endgame": {
|
||||
const h1 = document.getElementById("title") as HTMLHeadingElement | null;
|
||||
if (h1) {
|
||||
const issue = (msg.data as EndGame).issue;
|
||||
switch (issue){
|
||||
case "win": h1.innerText = "you won"; break;
|
||||
case "lose": h1.innerText = "you lose"; break;
|
||||
case "draw": h1.innerText = "it's a draw!"; break;
|
||||
default: throw new Error(`unexpected ${issue}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function drawGrid(ctx: CanvasRenderingContext2D, grid: number[]){
|
||||
/*function drawGrid(ctx: CanvasRenderingContext2D, grid: number[]){
|
||||
for (let y = 0; y < 3; ++y) {
|
||||
for (let x = 0; x < 3; ++x) {
|
||||
switch (grid[y*3+x]) {
|
||||
@ -79,5 +88,109 @@ function drawGrid(ctx: CanvasRenderingContext2D, grid: number[]){
|
||||
ctx.fillRect(rx, ry, 90, 90);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
function drawCircle(ctx: CanvasRenderingContext2D, center: Point) {
|
||||
const radius = SHAPE_SIZE/2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(center.x, center.y, radius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawCross(ctx: CanvasRenderingContext2D, center: Point) {
|
||||
const startPoint = { x: center.x-SHAPE_SIZE/2, y: center.y-SHAPE_SIZE/2 };
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startPoint.x, startPoint.y);
|
||||
ctx.lineTo(startPoint.x + 100, startPoint.y + 100);
|
||||
ctx.moveTo(startPoint.x + 100, startPoint.y);
|
||||
ctx.lineTo(startPoint.x, startPoint.y + 100);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function gridIndexToCoords(gridOrigin: Point, x: number, y: number): Point {
|
||||
const center = {
|
||||
x: gridOrigin.x + x * CELL_SIZE + CELL_SIZE/2,
|
||||
y: gridOrigin.y + y * CELL_SIZE + CELL_SIZE/2
|
||||
};
|
||||
return center;
|
||||
}
|
||||
|
||||
function updateGridState(ctx: CanvasRenderingContext2D, gridOrigin: Point) {
|
||||
for (let y = 0; y < 3; ++y) {
|
||||
for (let x = 0; x < 3; ++x) {
|
||||
switch (grid[y*3+x]) {
|
||||
case 0: break;
|
||||
case 1: {
|
||||
const p = gridIndexToCoords(gridOrigin, x, y);
|
||||
drawCircle(ctx, p);
|
||||
break;
|
||||
|
||||
}
|
||||
case 2: {
|
||||
const p = gridIndexToCoords(gridOrigin, x, y);
|
||||
drawCross(ctx, p);
|
||||
break;
|
||||
}
|
||||
default: throw new Error(`unhandled grid state ${grid[y*3+x]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function update(ctx: CanvasRenderingContext2D, time: number, ws: WebSocket) {
|
||||
const gridOrigin: Point = {
|
||||
x: ctx.canvas.width / 2 - GRID_SIZE / 2,
|
||||
y: ctx.canvas.height / 2 - GRID_SIZE / 2
|
||||
};
|
||||
|
||||
drawGridBackground(ctx, gridOrigin);
|
||||
handlePendingEvts(gridOrigin, ws);
|
||||
updateGridState(ctx, gridOrigin);
|
||||
|
||||
window.requestAnimationFrame(t => update(ctx, t, ws));
|
||||
}
|
||||
|
||||
function init() {
|
||||
// canvas stuff
|
||||
const canvas = document.getElementById("game") as HTMLCanvasElement | null;
|
||||
if (!canvas) throw new Error("unable to get canvas HTML element");
|
||||
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D | null;
|
||||
if (!ctx) throw new Error("unable to get canvas 2D context");
|
||||
resizeCanvas(ctx); // Init canvas
|
||||
|
||||
canvas.addEventListener("click", (evt) => {
|
||||
const {clientX, clientY} = evt;
|
||||
pendingEvts.push({x: clientX, y: clientY});
|
||||
});
|
||||
|
||||
// websocket stuff
|
||||
ws.onopen = (e) => {
|
||||
console.log("connected to websocket");
|
||||
};
|
||||
|
||||
ws.onmessage = (evt) => {
|
||||
const msg: Message = JSON.parse(evt.data);
|
||||
console.log(msg);
|
||||
switch (msg.kind) {
|
||||
case "hello": {
|
||||
myId = (msg.data as Hello).id;
|
||||
console.log(`connected to server with id ${myId}`);
|
||||
break;
|
||||
}
|
||||
case "update": {
|
||||
grid = (msg.data as Response).grid;
|
||||
console.log(grid);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn("unhandled message kind:", msg.kind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//window.addEventListener('resize', () => resizeCanvas(ctx));
|
||||
window.requestAnimationFrame(t => update(ctx, t, ws));
|
||||
}
|
||||
|
||||
init();
|
||||
|
@ -3,9 +3,8 @@
|
||||
<head>
|
||||
<title>Hello websockets</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="game" width=800 height=600></canvas>
|
||||
<h1 id="title">Offline</h1>
|
||||
<body style="margin: 0; padding 0; overflow: hidden; background-color: #000000">
|
||||
<canvas id="game" width="800" height="600"></canvas>
|
||||
</body>
|
||||
<script type="module" src="client.mjs"></script>
|
||||
</html>
|
||||
|
91
server.mts
91
server.mts
@ -10,12 +10,13 @@ console.log(`waiting for connection on ws://localhost:${port}`);
|
||||
|
||||
interface Client {
|
||||
id: number,
|
||||
symbol: "x" | "o",
|
||||
ws: WebSocket
|
||||
}
|
||||
|
||||
let id = 1;
|
||||
let clients: Client[] = [];
|
||||
let currentPlayerId: number | undefined = undefined;
|
||||
let currentPlayer: Client | undefined = undefined;
|
||||
|
||||
wss.on("connection", (ws) => {
|
||||
id += 1;
|
||||
@ -25,65 +26,65 @@ wss.on("connection", (ws) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPlayerId) {
|
||||
currentPlayerId = id;
|
||||
console.log(`current player is #${currentPlayerId}`);
|
||||
}
|
||||
|
||||
clients.push({id, ws});
|
||||
const symbol = clients.length == 0 ? "o" : "x";
|
||||
clients.push({id, ws, symbol});
|
||||
ws.send(JSON.stringify({kind: "hello", data: { id } as Hello}));
|
||||
console.log(`player #${id} connected`);
|
||||
|
||||
ws.addEventListener("message", (event: any) => {
|
||||
const message = JSON.parse(event.data);
|
||||
const {x, y} = message;
|
||||
const playerId = clients.find(x => x.ws === ws)?.id;
|
||||
console.assert(playerId);
|
||||
console.log(message, playerId, currentPlayerId);
|
||||
const player = clients.find(x => x.ws === ws);
|
||||
console.assert(player !== undefined);
|
||||
console.log(message, player!.id, currentPlayer?.id);
|
||||
|
||||
if (playerId != currentPlayerId) {
|
||||
return;
|
||||
}
|
||||
if (!currentPlayer) {
|
||||
currentPlayer = player;
|
||||
}
|
||||
|
||||
if (clients.length < 2 || player!.id != currentPlayer?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (grid[y*3+x] === 0) {
|
||||
grid[y*3+x] = playerId as number;
|
||||
grid[y*3+x] = player!.symbol == "o" ? 1 : 2;
|
||||
for (const c of clients) {
|
||||
const msg: Message = {
|
||||
kind: "update",
|
||||
data: { grid } as Response,
|
||||
}
|
||||
c.ws.send(JSON.stringify(msg));
|
||||
}
|
||||
data: { grid } as Response,
|
||||
}
|
||||
c.ws.send(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
const winnerId = checkWin(grid);
|
||||
if (winnerId == -1) {
|
||||
currentPlayerId = clients.find(x => x.id !== currentPlayerId)?.id; // change player
|
||||
console.assert(currentPlayerId);
|
||||
console.log(`current player is #${currentPlayerId}`);
|
||||
} else if (winnerId == 0) {
|
||||
for (const c of clients) {
|
||||
const msg: Message = {
|
||||
kind: "endgame",
|
||||
data: { issue: "draw" } as EndGame
|
||||
};
|
||||
c.ws.send(JSON.stringify(msg));
|
||||
}
|
||||
} else {
|
||||
console.log(`player ${winnerId} won !`);
|
||||
const winnerId = checkWin(grid);
|
||||
if (winnerId == -1) {
|
||||
currentPlayer = clients.find(x => x.id !== currentPlayer?.id); // change player
|
||||
console.assert(currentPlayer);
|
||||
console.log(`current player is #${currentPlayer?.id}`);
|
||||
} else if (winnerId == 0) {
|
||||
for (const c of clients) {
|
||||
const msg: Message = {
|
||||
kind: "endgame",
|
||||
data: { issue: "draw" } as EndGame
|
||||
};
|
||||
c.ws.send(JSON.stringify(msg));
|
||||
}
|
||||
} else {
|
||||
console.log(`player ${winnerId} won !`);
|
||||
|
||||
const winner = clients.find(x => x.id === winnerId);
|
||||
winner?.ws?.send(JSON.stringify({
|
||||
kind: "endgame",
|
||||
data: { issue: "win" } as EndGame
|
||||
} as Message));
|
||||
const winner = clients.find(x => x.id === winnerId);
|
||||
winner?.ws?.send(JSON.stringify({
|
||||
kind: "endgame",
|
||||
data: { issue: "win" } as EndGame
|
||||
} as Message));
|
||||
|
||||
const loser = clients.find(x => x.id !== winnerId);
|
||||
loser?.ws?.send(JSON.stringify({
|
||||
kind: "endgame",
|
||||
data: { issue: "lose" } as EndGame
|
||||
} as Message));
|
||||
}
|
||||
}
|
||||
const loser = clients.find(x => x.id !== winnerId);
|
||||
loser?.ws?.send(JSON.stringify({
|
||||
kind: "endgame",
|
||||
data: { issue: "lose" } as EndGame
|
||||
} as Message));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
|
Loading…
Reference in New Issue
Block a user