mirror of
https://github.com/MaciejkaG/statki.git
synced 2024-11-30 03:42: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 { Server } from 'socket.io';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4, validate } from 'uuid';
|
||||||
import session from "express-session";
|
import session from "express-session";
|
||||||
import { engine } from 'express-handlebars';
|
import { engine } from 'express-handlebars';
|
||||||
import { createClient } from 'redis';
|
import { createClient } from 'redis';
|
||||||
|
import * as bships from './utils/battleships.js'
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@ -27,6 +28,8 @@ redis.on('error', err => console.log('Redis Client Error', err));
|
|||||||
await redis.connect();
|
await redis.connect();
|
||||||
redis.flushDb();
|
redis.flushDb();
|
||||||
|
|
||||||
|
const GInfo = new bships.GameInfo(redis, io);
|
||||||
|
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
const sessionMiddleware = session({
|
const sessionMiddleware = session({
|
||||||
secret: uuidv4(),
|
secret: uuidv4(),
|
||||||
@ -69,7 +72,7 @@ app.get("/game", async (req, res) => {
|
|||||||
const game = await redis.json.get(`game:${req.query.id}`);
|
const game = await redis.json.get(`game:${req.query.id}`);
|
||||||
if (req.session.nickname == null) {
|
if (req.session.nickname == null) {
|
||||||
res.redirect("/setup");
|
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');
|
res.status(400).send('badGameId');
|
||||||
} else {
|
} else {
|
||||||
res.render('board');
|
res.render('board');
|
||||||
@ -89,7 +92,7 @@ io.on('connection', async (socket) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await isPlayerInGame(socket)) {
|
if (!await GInfo.isPlayerInGame(socket)) {
|
||||||
socket.on('whats my nick', (callback) => {
|
socket.on('whats my nick', (callback) => {
|
||||||
callback(session.nickname);
|
callback(session.nickname);
|
||||||
});
|
});
|
||||||
@ -132,18 +135,19 @@ io.on('connection', async (socket) => {
|
|||||||
// Teraz utwórz objekt partii w trakcie w bazie Redis
|
// Teraz utwórz objekt partii w trakcie w bazie Redis
|
||||||
const gameId = uuidv4();
|
const gameId = uuidv4();
|
||||||
redis.json.set(`game:${gameId}`, '$', {
|
redis.json.set(`game:${gameId}`, '$', {
|
||||||
|
hostId: opp.id,
|
||||||
state: "pregame",
|
state: "pregame",
|
||||||
boards: {
|
boards: [
|
||||||
host: { // typ 2 to trójmasztowiec pozycja i obrót na planszy które pola zostały trafione
|
{ // 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]}
|
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?
|
// pozycja na planszy czy strzał miał udział w zatopieniu statku?
|
||||||
shots: [], // zawiera np. {posX: 3, posY: 5, sunk: true}
|
shots: [], // zawiera np. {posX: 3, posY: 5, sunk: true}
|
||||||
},
|
},
|
||||||
guest: {
|
{
|
||||||
ships: [],
|
ships: [],
|
||||||
shots: [],
|
shots: [],
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
nextPlayer: 0,
|
nextPlayer: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -193,12 +197,12 @@ io.on('connection', async (socket) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnecting', () => {
|
socket.on('disconnecting', () => {
|
||||||
if (isPlayerInRoom(socket)) {
|
if (bships.isPlayerInRoom(socket)) {
|
||||||
io.to(socket.rooms[1]).emit("player left");
|
io.to(socket.rooms[1]).emit("player left");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const playerGame = await getPlayerGameData(socket);
|
const playerGame = await GInfo.getPlayerGameData(socket);
|
||||||
|
|
||||||
if (playerGame.data.state === 'pregame') {
|
if (playerGame.data.state === 'pregame') {
|
||||||
socket.join(playerGame.id);
|
socket.join(playerGame.id);
|
||||||
@ -213,22 +217,33 @@ io.on('connection', async (socket) => {
|
|||||||
|
|
||||||
let UTCTs = Math.floor((new Date()).getTime() / 1000 + 90);
|
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('turn update', { turn: 0, phase: "preparation", timerToUTC: UTCTs });
|
||||||
io.to(playerGame.id).emit();
|
bships.timer(90, () => GInfo.endPrepPhase(socket));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// socket.on('shoot', async () => {
|
socket.on('place ship', async (type, posX, posY, rot) => {
|
||||||
// const playerGame = await getPlayerGameData(socket);
|
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 () => {
|
socket.on('disconnecting', async () => {
|
||||||
io.to(playerGame.id).emit("player left");
|
const playerGame = await GInfo.getPlayerGameData(socket);
|
||||||
|
if (playerGame !== null) {
|
||||||
redis.json.del(`game:${playerGame.id}`);
|
AFKEnd(playerGame.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -241,35 +256,32 @@ function genID() {
|
|||||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function emitToParty(partyuuid) {
|
function resetUserGame(req) {
|
||||||
// const party = gameData.find((element) => element.partyId===partyuuid);
|
req.session.reload((err) => {
|
||||||
|
if (err) return socket.disconnect();
|
||||||
|
|
||||||
// if (party!==null) {
|
req.session.activeGame = null;
|
||||||
// party.members.forEach(socketId => {
|
req.session.save();
|
||||||
|
});
|
||||||
// io.to(socketId).emit();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
function isPlayerInRoom(socket) {
|
|
||||||
return !socket.rooms.size === 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isPlayerInGame(socket) {
|
function endGame(gameId) {
|
||||||
const game = await redis.json.get(`game:${socket.session.activeGame}`);
|
const members = [...roomMemberIterator(gameId)];
|
||||||
return game != null;
|
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) {
|
function AFKEnd(gameId) {
|
||||||
const game = await redis.json.get(`game:${socket.session.activeGame}`);
|
io.to(gameId).emit("player left");
|
||||||
return game == null ? null : {id: socket.session.activeGame, data: game};
|
endGame(gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function roomMemberIterator(id) {
|
function roomMemberIterator(id) {
|
||||||
return io.sockets.adapter.rooms.get(id).entries();
|
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 {
|
.container {
|
||||||
display: none;
|
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 */
|
/* index */
|
||||||
|
@ -18,7 +18,12 @@ socket.on("player idx", (idx) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('turn update', (turnData) => {
|
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;
|
timerDestination = turnData.timerToUTC;
|
||||||
gamePhase = turnData.phase;
|
gamePhase = turnData.phase;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
var activeView;
|
var activeView;
|
||||||
|
|
||||||
function switchView(viewContainerId, useReplaceState=false) {
|
function switchView(viewContainerId, useReplaceState=false) {
|
||||||
$(`.container`).css("opacity", 0);
|
$(`.container`).css({ opacity: 0, animation: "OutAnim 0.2s 1 ease" });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$(`.container`).css("display", "none");
|
$(`.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 path = $(`.container#${viewContainerId}`).data("path");
|
||||||
let title = $(`.container#${viewContainerId}`).data("title");
|
let title = $(`.container#${viewContainerId}`).data("title");
|
||||||
if (useReplaceState) {
|
if (useReplaceState) {
|
||||||
@ -14,7 +14,7 @@ function switchView(viewContainerId, useReplaceState=false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeView = viewContainerId;
|
activeView = viewContainerId;
|
||||||
}, 150);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
function lockUI(doLock) {
|
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 class="controlsOwnBoard"><span class="important">R</span> Obrót statku</h3>
|
||||||
<h3><span class="important">B</span> Zamiana planszy</h3>
|
<h3><span class="important">B</span> Zamiana planszy</h3>
|
||||||
<span class="break"></span>
|
<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>
|
<h2 class="important" id="timer">∞</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="boardContainer">
|
<div class="boardContainer">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
<h1 class="header">Statki</h1>
|
||||||
<div class="container" id="mainMenuView" data-title="Statki" data-path="/">
|
<div class="container" id="mainMenuView" data-title="Statki" data-path="/">
|
||||||
<div>
|
<div>
|
||||||
<h1>Statki</h1>
|
|
||||||
<h2>Wybierz tryb gry</h2>
|
<h2>Wybierz tryb gry</h2>
|
||||||
<div class="modes">
|
<div class="modes">
|
||||||
<div id="pvpMenuButton">
|
<div id="pvpMenuButton">
|
||||||
@ -17,7 +18,6 @@
|
|||||||
|
|
||||||
<div class="container" id="pvpMenuView" data-title="Statki / PvP" data-path="/pvp">
|
<div class="container" id="pvpMenuView" data-title="Statki / PvP" data-path="/pvp">
|
||||||
<div>
|
<div>
|
||||||
<h1>Statki</h1>
|
|
||||||
<h2>PvP</h2>
|
<h2>PvP</h2>
|
||||||
<div class="modes">
|
<div class="modes">
|
||||||
<div id="createGameButton">
|
<div id="createGameButton">
|
||||||
@ -34,7 +34,6 @@
|
|||||||
|
|
||||||
<div class="container" id="pvpCreateView" data-title="Statki / PvP / Stwórz" data-path="/pvp/create">
|
<div class="container" id="pvpCreateView" data-title="Statki / PvP / Stwórz" data-path="/pvp/create">
|
||||||
<div>
|
<div>
|
||||||
<h1>Statki</h1>
|
|
||||||
<h2>PvP / Stwórz</h2>
|
<h2>PvP / Stwórz</h2>
|
||||||
<div class="modes">
|
<div class="modes">
|
||||||
<div>
|
<div>
|
||||||
@ -49,7 +48,6 @@
|
|||||||
|
|
||||||
<div class="container" id="pvpJoinView" data-title="Statki / PvP / Dołącz" data-path="/pvp/join">
|
<div class="container" id="pvpJoinView" data-title="Statki / PvP / Dołącz" data-path="/pvp/join">
|
||||||
<div>
|
<div>
|
||||||
<h1>Statki</h1>
|
|
||||||
<h2>PvP / Dołącz</h2>
|
<h2>PvP / Dołącz</h2>
|
||||||
<div class="modes">
|
<div class="modes">
|
||||||
<div>
|
<div>
|
||||||
@ -64,7 +62,6 @@
|
|||||||
|
|
||||||
<div class="container" id="preparingGame" data-title="Statki / PvP / Przygotowywanie" data-path="/pvp/prepairing">
|
<div class="container" id="preparingGame" data-title="Statki / PvP / Przygotowywanie" data-path="/pvp/prepairing">
|
||||||
<div>
|
<div>
|
||||||
<h1>Statki</h1>
|
|
||||||
<h2>PvP / Wczytywanie</h2>
|
<h2>PvP / Wczytywanie</h2>
|
||||||
<div class="modes">
|
<div class="modes">
|
||||||
<div>
|
<div>
|
||||||
|
Loading…
Reference in New Issue
Block a user