mirror of
https://github.com/MaciejkaG/statki.git
synced 2024-11-29 22:52:55 +01:00
Major changes
- Huge improvements in UI design and animations - Improvements in code organisation (separated multiple functions into an util file) - Multiple bug fixes and improvements to stability - Client side events improved
This commit is contained in:
parent
dbb3ad0f1d
commit
72da84c3d6
92
index.js
92
index.js
@ -3,10 +3,11 @@ import { createServer } from 'node:http';
|
||||
import { Server } from 'socket.io';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { v4 as uuidv4, validate } from 'uuid';
|
||||
import session from "express-session";
|
||||
import { engine } from 'express-handlebars';
|
||||
import { createClient } from 'redis';
|
||||
import * as bships from './utils/battleships.js'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@ -27,6 +28,8 @@ redis.on('error', err => console.log('Redis Client Error', err));
|
||||
await redis.connect();
|
||||
redis.flushDb();
|
||||
|
||||
const GInfo = new bships.GameInfo(redis, io);
|
||||
|
||||
app.set('trust proxy', 1);
|
||||
const sessionMiddleware = session({
|
||||
secret: uuidv4(),
|
||||
@ -69,7 +72,7 @@ app.get("/game", async (req, res) => {
|
||||
const game = await redis.json.get(`game:${req.query.id}`);
|
||||
if (req.session.nickname == null) {
|
||||
res.redirect("/setup");
|
||||
} else if (req.query.id == null || game == null || game.state == "expired") {
|
||||
} else if (req.query.id == null || game == null || game.state == "expired" || req.session.activeGame == null) {
|
||||
res.status(400).send('badGameId');
|
||||
} else {
|
||||
res.render('board');
|
||||
@ -89,7 +92,7 @@ io.on('connection', async (socket) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await isPlayerInGame(socket)) {
|
||||
if (!await GInfo.isPlayerInGame(socket)) {
|
||||
socket.on('whats my nick', (callback) => {
|
||||
callback(session.nickname);
|
||||
});
|
||||
@ -132,18 +135,19 @@ io.on('connection', async (socket) => {
|
||||
// Teraz utwórz objekt partii w trakcie w bazie Redis
|
||||
const gameId = uuidv4();
|
||||
redis.json.set(`game:${gameId}`, '$', {
|
||||
hostId: opp.id,
|
||||
state: "pregame",
|
||||
boards: {
|
||||
host: { // typ 2 to trójmasztowiec pozycja i obrót na planszy które pola zostały trafione
|
||||
boards: [
|
||||
{ // typ 2 to trójmasztowiec pozycja i obrót na planszy które pola zostały trafione
|
||||
ships: [], // zawiera np. {type: 2, posX: 3, posY: 4, rot: 2, hits: [false, false, true]}
|
||||
// pozycja na planszy czy strzał miał udział w zatopieniu statku?
|
||||
shots: [], // zawiera np. {posX: 3, posY: 5, sunk: true}
|
||||
},
|
||||
guest: {
|
||||
{
|
||||
ships: [],
|
||||
shots: [],
|
||||
}
|
||||
},
|
||||
],
|
||||
nextPlayer: 0,
|
||||
});
|
||||
|
||||
@ -193,12 +197,12 @@ io.on('connection', async (socket) => {
|
||||
});
|
||||
|
||||
socket.on('disconnecting', () => {
|
||||
if (isPlayerInRoom(socket)) {
|
||||
if (bships.isPlayerInRoom(socket)) {
|
||||
io.to(socket.rooms[1]).emit("player left");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const playerGame = await getPlayerGameData(socket);
|
||||
const playerGame = await GInfo.getPlayerGameData(socket);
|
||||
|
||||
if (playerGame.data.state === 'pregame') {
|
||||
socket.join(playerGame.id);
|
||||
@ -213,22 +217,33 @@ io.on('connection', async (socket) => {
|
||||
|
||||
let UTCTs = Math.floor((new Date()).getTime() / 1000 + 90);
|
||||
io.to(playerGame.id).emit('turn update', { turn: 0, phase: "preparation", timerToUTC: UTCTs });
|
||||
io.to(playerGame.id).emit();
|
||||
bships.timer(90, () => GInfo.endPrepPhase(socket));
|
||||
}
|
||||
}
|
||||
|
||||
// socket.on('shoot', async () => {
|
||||
// const playerGame = await getPlayerGameData(socket);
|
||||
socket.on('place ship', async (type, posX, posY, rot) => {
|
||||
const playerGame = await GInfo.getPlayerGameData(socket);
|
||||
|
||||
// if (playerGame.state === 'action') {
|
||||
if (playerGame.state === 'preparation') {
|
||||
|
||||
// }
|
||||
// });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('shoot', async () => {
|
||||
const playerGame = await GInfo.getPlayerGameData(socket);
|
||||
|
||||
if (playerGame.state === 'action') {
|
||||
if (bships.checkTurn(playerGame, socket.id)) {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnecting', async () => {
|
||||
io.to(playerGame.id).emit("player left");
|
||||
|
||||
redis.json.del(`game:${playerGame.id}`);
|
||||
const playerGame = await GInfo.getPlayerGameData(socket);
|
||||
if (playerGame !== null) {
|
||||
AFKEnd(playerGame.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -241,35 +256,32 @@ function genID() {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
// async function emitToParty(partyuuid) {
|
||||
// const party = gameData.find((element) => element.partyId===partyuuid);
|
||||
function resetUserGame(req) {
|
||||
req.session.reload((err) => {
|
||||
if (err) return socket.disconnect();
|
||||
|
||||
// if (party!==null) {
|
||||
// party.members.forEach(socketId => {
|
||||
|
||||
// io.to(socketId).emit();
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
function isPlayerInRoom(socket) {
|
||||
return !socket.rooms.size === 1;
|
||||
req.session.activeGame = null;
|
||||
req.session.save();
|
||||
});
|
||||
}
|
||||
|
||||
async function isPlayerInGame(socket) {
|
||||
const game = await redis.json.get(`game:${socket.session.activeGame}`);
|
||||
return game != null;
|
||||
function endGame(gameId) {
|
||||
const members = [...roomMemberIterator(gameId)];
|
||||
for (let i = 0; i < members.length; i++) {
|
||||
const sid = members[i][0];
|
||||
const socket = io.sockets.sockets.get(sid);
|
||||
resetUserGame(socket.request);
|
||||
socket.leave(gameId);
|
||||
}
|
||||
|
||||
redis.json.del(`game:${gameId}`);
|
||||
}
|
||||
|
||||
async function getPlayerGameData(socket) {
|
||||
const game = await redis.json.get(`game:${socket.session.activeGame}`);
|
||||
return game == null ? null : {id: socket.session.activeGame, data: game};
|
||||
function AFKEnd(gameId) {
|
||||
io.to(gameId).emit("player left");
|
||||
endGame(gameId);
|
||||
}
|
||||
|
||||
function roomMemberIterator(id) {
|
||||
return io.sockets.adapter.rooms.get(id).entries();
|
||||
}
|
||||
|
||||
function getPlayerRoom(socket) {
|
||||
return socket.rooms.entries()[1] === undefined ? null : socket.rooms.entries()[1][0];
|
||||
}
|
@ -1,9 +1,20 @@
|
||||
body {
|
||||
transition: opacity 0.1s;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: none;
|
||||
transition: opacity 0.175s;
|
||||
}
|
||||
|
||||
@keyframes OutAnim {
|
||||
from {transform: translateY(0)}
|
||||
to {transform: translateY(5%);}
|
||||
}
|
||||
|
||||
@keyframes InAnim {
|
||||
from {transform: translateY(-5%)}
|
||||
to {transform: translateY(0%);}
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* index */
|
||||
|
@ -18,7 +18,12 @@ socket.on("player idx", (idx) => {
|
||||
});
|
||||
|
||||
socket.on('turn update', (turnData) => {
|
||||
turnData.turn == playerIdx ? $("#whosTurn").html("Ty") : $("#whosTurn").html("Przeciwnik");
|
||||
if (turnData.phase === "preparation") {
|
||||
$("#whosTurn").html("Faza przygotowań");
|
||||
} else {
|
||||
turnData.turn === playerIdx ? $("#whosTurn").html("Twoja tura") : $("#whosTurn").html("Tura przeciwnika");
|
||||
}
|
||||
|
||||
|
||||
timerDestination = turnData.timerToUTC;
|
||||
gamePhase = turnData.phase;
|
||||
|
@ -1,10 +1,10 @@
|
||||
var activeView;
|
||||
|
||||
function switchView(viewContainerId, useReplaceState=false) {
|
||||
$(`.container`).css("opacity", 0);
|
||||
$(`.container`).css({ opacity: 0, animation: "OutAnim 0.2s 1 ease" });
|
||||
setTimeout(() => {
|
||||
$(`.container`).css("display", "none");
|
||||
$(`.container#${viewContainerId}`).css({"display": "flex", "opacity": "1"});
|
||||
$(`.container#${viewContainerId}`).css({ display: "flex", opacity: 1, animation: "InAnim 0.2s 1 ease" });
|
||||
let path = $(`.container#${viewContainerId}`).data("path");
|
||||
let title = $(`.container#${viewContainerId}`).data("title");
|
||||
if (useReplaceState) {
|
||||
@ -14,7 +14,7 @@ function switchView(viewContainerId, useReplaceState=false) {
|
||||
}
|
||||
|
||||
activeView = viewContainerId;
|
||||
}, 150);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function lockUI(doLock) {
|
||||
|
131
utils/battleships.js
Normal file
131
utils/battleships.js
Normal file
@ -0,0 +1,131 @@
|
||||
// export class Client {
|
||||
// constructor(clientId, clientSecret, redirectUri) {
|
||||
// this.clientId = clientId;
|
||||
// this.clientSecret = clientSecret;
|
||||
// this.redirectUri = redirectUri;
|
||||
// }
|
||||
// getAccessToken(code) {
|
||||
// }
|
||||
// }
|
||||
|
||||
export class GameInfo {
|
||||
constructor(redis, io) {
|
||||
this.redis = redis;
|
||||
this.io = io;
|
||||
}
|
||||
|
||||
async isPlayerInGame(socket) {
|
||||
const game = await this.redis.json.get(`game:${socket.session.activeGame}`);
|
||||
return game != null;
|
||||
}
|
||||
|
||||
async getPlayerGameData(socket) {
|
||||
const game = await this.redis.json.get(`game:${socket.session.activeGame}`);
|
||||
return game == null ? null : { id: socket.session.activeGame, data: game };
|
||||
}
|
||||
|
||||
async endPrepPhase(socket) {
|
||||
const gameId = socket.session.activeGame;
|
||||
const key = `game:${gameId}`;
|
||||
|
||||
await this.redis.json.set(key, '$.state', 'action');
|
||||
await this.redis.json.set(key, '$.nextPlayer', 0);
|
||||
|
||||
const UTCTs = Math.floor((new Date()).getTime() / 1000 + 30);
|
||||
this.io.to(gameId).emit('turn update', { turn: 0, phase: "action", timerToUTC: UTCTs });
|
||||
}
|
||||
|
||||
async passTurn(socket) {
|
||||
const gameId = socket.session.activeGame;
|
||||
const key = `game:${gameId}`;
|
||||
|
||||
await this.redis.json.set(key, '$.state', 'action');
|
||||
let nextPlayer = await this.redis.json.get(key, '$.nextPlayer');
|
||||
nextPlayer = nextPlayer === 0 ? 1 : 0;
|
||||
await this.redis.json.set(key, '$.nextPlayer', nextPlayer);
|
||||
|
||||
const UTCTs = Math.floor((new Date()).getTime() / 1000 + 30);
|
||||
this.io.to(gameId).emit('turn update', { turn: 0, phase: "action", timerToUTC: UTCTs });
|
||||
}
|
||||
}
|
||||
|
||||
export function isPlayerInRoom(socket) {
|
||||
return !socket.rooms.size === 1;
|
||||
}
|
||||
|
||||
var lastTimeChange = new Date().getTime();
|
||||
|
||||
export function timer(time, callback) {
|
||||
let localLastChange = lastTimeChange;
|
||||
|
||||
let timeout = setTimeout(callback, time * 1000);
|
||||
|
||||
let interval = setInterval(() => {
|
||||
if (timeout._destroyed) {
|
||||
// timer is finished, stop monitoring turn changes
|
||||
clearInterval(interval);
|
||||
}
|
||||
if (localLastChange != lastTimeChange) {
|
||||
// timer has been reset
|
||||
clearTimeout(timeout);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
export function resetTimers() {
|
||||
lastTimeChange = -lastTimeChange;
|
||||
}
|
||||
|
||||
// export function getShipsLeft(data, playerIdx) {
|
||||
// let shipsLeft = [4, 3, 2, 1];
|
||||
|
||||
// const playerShips = shipsLeft.boards[playerIdx].ships;
|
||||
|
||||
// playerShips.forEach(ship => {
|
||||
// var isSunk = true;
|
||||
// ship.hits.every(isHit => {
|
||||
// isSunk = isHit;
|
||||
// return isHit;
|
||||
// });
|
||||
// switch (ship.type) {
|
||||
// case 0:
|
||||
// shipsLeft[0]--;
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
export function getShipsAvailable(data, playerIdx) {
|
||||
let shipsLeft = [4, 3, 2, 1];
|
||||
|
||||
const playerShips = shipsLeft.boards[playerIdx].ships;
|
||||
|
||||
playerShips.forEach(ship => {
|
||||
shipsLeft[ship.type]--;
|
||||
});
|
||||
|
||||
return shipsLeft;
|
||||
}
|
||||
|
||||
export function checkShot(data, playerIdx) {
|
||||
playerIdx = playerIdx === 0 ? 1 : 0
|
||||
|
||||
data.boards[playerIdx]
|
||||
}
|
||||
|
||||
export function checkTurn(data, playerId) {
|
||||
// Check if it's player's turn
|
||||
if (playerId == data.hostId) {
|
||||
return data.nextPlayer === 0;
|
||||
} else {
|
||||
return data.nextPlayer === 1;
|
||||
}
|
||||
}
|
||||
|
||||
// timer(5, () => {
|
||||
// console.log("out of time");
|
||||
// });
|
@ -18,7 +18,7 @@
|
||||
<h3 class="controlsOwnBoard"><span class="important">R</span> Obrót statku</h3>
|
||||
<h3><span class="important">B</span> Zamiana planszy</h3>
|
||||
<span class="break"></span>
|
||||
<h3>Ruch: <span class="dynamic" id="whosTurn">Przeciwnik</span></h3>
|
||||
<h3><span class="dynamic" id="whosTurn"></span></h3>
|
||||
<h2 class="important" id="timer">∞</h2>
|
||||
</div>
|
||||
<div class="boardContainer">
|
||||
|
@ -1,6 +1,7 @@
|
||||
<h1 class="header">Statki</h1>
|
||||
<div class="container" id="mainMenuView" data-title="Statki" data-path="/">
|
||||
<div>
|
||||
<h1>Statki</h1>
|
||||
|
||||
<h2>Wybierz tryb gry</h2>
|
||||
<div class="modes">
|
||||
<div id="pvpMenuButton">
|
||||
@ -17,7 +18,6 @@
|
||||
|
||||
<div class="container" id="pvpMenuView" data-title="Statki / PvP" data-path="/pvp">
|
||||
<div>
|
||||
<h1>Statki</h1>
|
||||
<h2>PvP</h2>
|
||||
<div class="modes">
|
||||
<div id="createGameButton">
|
||||
@ -34,7 +34,6 @@
|
||||
|
||||
<div class="container" id="pvpCreateView" data-title="Statki / PvP / Stwórz" data-path="/pvp/create">
|
||||
<div>
|
||||
<h1>Statki</h1>
|
||||
<h2>PvP / Stwórz</h2>
|
||||
<div class="modes">
|
||||
<div>
|
||||
@ -49,7 +48,6 @@
|
||||
|
||||
<div class="container" id="pvpJoinView" data-title="Statki / PvP / Dołącz" data-path="/pvp/join">
|
||||
<div>
|
||||
<h1>Statki</h1>
|
||||
<h2>PvP / Dołącz</h2>
|
||||
<div class="modes">
|
||||
<div>
|
||||
@ -64,7 +62,6 @@
|
||||
|
||||
<div class="container" id="preparingGame" data-title="Statki / PvP / Przygotowywanie" data-path="/pvp/prepairing">
|
||||
<div>
|
||||
<h1>Statki</h1>
|
||||
<h2>PvP / Wczytywanie</h2>
|
||||
<div class="modes">
|
||||
<div>
|
||||
|
Loading…
Reference in New Issue
Block a user