Каркас проекта: ТЗ, nginx+gameserver(uWebSockets) в Docker, /ws echo

Frontend — чистый HTML/CSS/JS без сборки (резюме, проекты, игра-заглушки).
Gameserver — C++ на uWebSockets (CMake FetchContent), протокол hello/lobby.list_rooms/error проверен end-to-end.
This commit is contained in:
2026-07-26 19:31:44 +05:00
commit a9d2a6b15c
23 changed files with 491 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
// TODO: клиентские представления Tank/Bullet/Wall для Quintus — этапы 4-5
+21
View File
@@ -0,0 +1,21 @@
// TODO: полноценный UI лобби (список/создание/присоединение комнат) — этап 3.
// Пока — проверка связки клиент <-> gameserver (hello -> lobby.list_rooms).
import { Network, ServerMessage } from './network.js';
export function connectAndProbe() {
const net = new Network();
net.on('_open', () => net.hello('cacto'));
net.on(ServerMessage.HELLO_ACK, () => {
console.log('[gameserver] hello.ack received');
net.listRooms();
});
net.on(ServerMessage.LOBBY_ROOMS, (payload) => {
console.log('[gameserver] rooms:', payload.rooms);
});
net.on(ServerMessage.ERROR, (payload) => {
console.error('[gameserver] error:', payload);
});
return net;
}
+4
View File
@@ -0,0 +1,4 @@
// TODO: инициализация Quintus и игрового цикла рендера — этапы 4-5
import { connectAndProbe } from './lobby.js';
connectAndProbe();
+38
View File
@@ -0,0 +1,38 @@
import { ClientMessage, ServerMessage } from './protocol.js';
export class Network {
constructor() {
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
this.socket = new WebSocket(`${proto}://${location.host}/ws`);
this.handlers = new Map();
this.socket.addEventListener('open', () => this._emit('_open'));
this.socket.addEventListener('close', () => this._emit('_close'));
this.socket.addEventListener('message', (event) => {
const msg = JSON.parse(event.data);
this._emit(msg.type, msg.payload);
});
}
on(type, handler) {
this.handlers.set(type, handler);
}
_emit(type, payload) {
this.handlers.get(type)?.(payload);
}
send(type, payload = {}) {
this.socket.send(JSON.stringify({ type, payload }));
}
hello(nickname) {
this.send(ClientMessage.HELLO, { nickname });
}
listRooms() {
this.send(ClientMessage.LOBBY_LIST_ROOMS);
}
}
export { ServerMessage };
+21
View File
@@ -0,0 +1,21 @@
// Типы сообщений из TZ.md, раздел 5.
export const ClientMessage = {
HELLO: 'hello',
LOBBY_LIST_ROOMS: 'lobby.list_rooms',
LOBBY_CREATE_ROOM: 'lobby.create_room',
LOBBY_JOIN_ROOM: 'lobby.join_room',
LOBBY_LEAVE_ROOM: 'lobby.leave_room',
GAME_READY: 'game.ready',
GAME_INPUT: 'game.input',
};
export const ServerMessage = {
HELLO_ACK: 'hello.ack',
ERROR: 'error',
LOBBY_ROOMS: 'lobby.rooms',
LOBBY_ROOM_JOINED: 'lobby.room_joined',
LOBBY_ROOM_UPDATED: 'lobby.room_updated',
GAME_START: 'game.start',
GAME_STATE: 'game.state',
GAME_OVER: 'game.over',
};
+11
View File
@@ -0,0 +1,11 @@
// TODO: полноценный canvas-эффект цифрового дождя — этап 2
const canvas = document.getElementById('matrix-rain');
if (canvas) {
const ctx = canvas.getContext('2d');
const resize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resize();
window.addEventListener('resize', resize);
}
+1
View File
@@ -0,0 +1 @@
// TODO: рендер карточек проектов из данных — этап 2