/
PrisonShift3r
/
Point-Collector
Обзор
Документация
Войти
/
PrisonShift3r
/
Point-Collector
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
client/client.cpp
933 строки
35 KB
DMN
Добавление документации
16 дек 2025, 16:07
16 дек 2025, 16:07
f964f16
Код
Авторство
О чём код?
// Отключаем предупреждения о deprecated Winsock-функциях #define _WINSOCK_DEPRECATED_NO_WARNINGS // ====================== СЕТЕВЫЕ И СИСТЕМНЫЕ ЗАГОЛОВКИ ====================== #include <winsock2.h> #include <ws2tcpip.h> #include <windows.h> // ====================== СТАНДАРТНЫЕ БИБЛИОТЕКИ ====================== #include <iostream> #include <string> #include <thread> #include <mutex> #include <map> #include <vector> #include <sstream> #include <atomic> #include <chrono> #include <conio.h> #include <cstdlib> #include <algorithm> #pragma comment(lib, "ws2_32.lib") using namespace std; // ====================== РАЗМЕРЫ ИГРОВОГО ПОЛЯ ====================== const int WIDTH = 40; const int HEIGHT = 15; // Простая структура позиции на экране struct Pos { int x, y; }; // ====================== СОСТОЯНИЯ КЛИЕНТА ====================== enum class ClientState { AUTH_PENDING, // Авторизация MENU, // Главное меню LOBBY, // Лобби комнаты PLAYING, // Игра SPECTATING, // Наблюдение GAME_OVER // Экран завершения игры }; // ====================== ИНФОРМАЦИЯ О КОМНАТЕ ====================== struct RoomInfo { string id; // ID комнаты string playersInfo; // Текущее число игроков string state; // LOBBY / PLAYING bool allowSpectators; // Разрешены ли зрители }; // ====================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ====================== SOCKET sock = INVALID_SOCKET; atomic<bool> running{ true }; string myName; ClientState clientState = ClientState::AUTH_PENDING; // Информация о победителе string gameWinner; int gameWinnerScore = 0; map<string, int> finalScores; // ====================== ИГРОВЫЕ ДАННЫЕ ====================== mutex mtx; // Очки: id -> позиция map<int, Pos> coins; // Другие игроки: имя -> позиция map<string, Pos> otherPlayers; // Позиция и счёт игрока int myScore = 0; Pos playerPos{ WIDTH / 2, HEIGHT / 2 }; // Очки всех игроков map<string, int> allScores; // Таймер раунда atomic<int> roundTimeLeft{ 120 }; // ====================== ЛОББИ / МЕНЮ ====================== string currentRoomId; vector<string> playersInLobby; vector<string> spectatorsInLobby; map<string, RoomInfo> availableRooms; bool isRoomOwner = false; bool isSpectator = false; // ====================== СТАТИСТИКА ИГРОКОВ ====================== struct PlayerStatsClient { int totalGames = 0; int totalWins = 0; int totalCoins = 0; int totalScore = 0; float winRate = 0.0f; }; map<string, PlayerStatsClient> playerStats; atomic<bool> statsReceived{ false }; atomic<bool> authFailed{ false }; atomic<bool> authSuccess{ false }; // ====================== УТИЛИТЫ ====================== // Перевод серверных координат (0–100) в координаты сетки экрана Pos serverToGrid(float sx, float sy) { int gx = int((sx / 100.0f) * (WIDTH - 1) + 0.5f); int gy = int((sy / 100.0f) * (HEIGHT - 1) + 0.5f); // Ограничение границ if (gx < 1) gx = 1; if (gx > WIDTH - 2) gx = WIDTH - 2; if (gy < 1) gy = 1; if (gy > HEIGHT - 2) gy = HEIGHT - 2; return { gx, gy }; } // Разделение строки на токены vector<string> splitTok(const string& s) { vector<string> out; istringstream iss(s); string t; while (iss >> t) out.push_back(t); return out; } // Отправка строки серверу (автоматически добавляет \n) bool sendLine(const string& line) { if (sock == INVALID_SOCKET) return false; string msg = line; if (!msg.empty() && msg.back() != '\n') msg.push_back('\n'); int sent = send(sock, msg.c_str(), (int)msg.size(), 0); return sent > 0; } // Поток для приёма данных от сервера // Работает асинхронно, обрабатывает все серверные команды и обновляет состояние клиента void recvThread() { char buf[1024]; // Буфер для приёма данных string pending; // Хранит неполные строки, полученные от сервера while (running) { // Основной цикл потока // Получаем данные из сокета int n = recv(sock, buf, sizeof(buf) - 1, 0); // Если соединение потеряно или ошибка recv if (n <= 0) { running = false; // Останавливаем клиент break; } buf[n] = 0; // Завершаем строку нулевым символом pending += string(buf); // Добавляем к накопленным данным size_t pos; // Обрабатываем строки, разделённые '\n' while ((pos = pending.find('\n')) != string::npos) { string line = pending.substr(0, pos); // Берём одну строку pending.erase(0, pos + 1); // Удаляем обработанную строку auto tok = splitTok(line); // Разбиваем строку на токены if (tok.empty()) continue; // ====================== АВТОРИЗАЦИЯ ====================== if (tok[0] == "AUTH_OK") { lock_guard<mutex> lk(mtx); authSuccess = true; // Успешная авторизация cout << "[AUTH] Authentication successful!\n"; } else if (tok[0] == "AUTH_FAILED") { lock_guard<mutex> lk(mtx); authFailed = true; // Неудачная авторизация (имя занято) cout << "[AUTH] Authentication failed: Name already taken!\n"; } // ====================== СТАТИСТИКА ИГРОКА ====================== else if (tok[0] == "STATS" && tok.size() >= 7) { lock_guard<mutex> lk(mtx); string name = tok[1]; PlayerStatsClient stats; stats.totalGames = stoi(tok[2]); stats.totalWins = stoi(tok[3]); stats.totalCoins = stoi(tok[4]); stats.totalScore = stoi(tok[5]); stats.winRate = stof(tok[6]); playerStats[name] = stats; if (name == myName) { statsReceived = true; // Сигнал, что статистика игрока получена } } // ====================== СПИСОК КОМНАТ ====================== else if (tok[0] == "ROOMS_LIST") { lock_guard<mutex> lk(mtx); availableRooms.clear(); // Каждая комната приходит в виде "id:playersInfo:state:allowSpectators" for (size_t i = 1; i < tok.size(); i++) { string entry = tok[i]; vector<string> parts; string part; for (char c : entry) { if (c == ':') { parts.push_back(part); part.clear(); } else part += c; } if (!part.empty()) parts.push_back(part); if (parts.size() >= 4) { RoomInfo info; info.id = parts[0]; info.playersInfo = parts[1]; info.state = parts[2]; info.allowSpectators = (parts[3] == "1"); availableRooms[info.id] = info; // Обновляем локальный список комнат } } } // ====================== СОЗДАНИЕ/ПРИСОЕДИНЕНИЕ К КОМНАТЕ ====================== else if (tok[0] == "ROOM_CREATED" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); currentRoomId = tok[1]; clientState = ClientState::LOBBY; // Переход в лобби isRoomOwner = true; isSpectator = false; playersInLobby.clear(); spectatorsInLobby.clear(); playersInLobby.push_back(myName); // Добавляем себя в список игроков } else if (tok[0] == "JOIN_OK" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); currentRoomId = tok[1]; clientState = ClientState::LOBBY; isSpectator = false; playersInLobby.clear(); spectatorsInLobby.clear(); playersInLobby.push_back(myName); } else if (tok[0] == "JOIN_OK_SPECTATOR" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); currentRoomId = tok[1]; clientState = ClientState::LOBBY; isSpectator = true; playersInLobby.clear(); spectatorsInLobby.clear(); spectatorsInLobby.push_back(myName); } // ====================== ОШИБКИ ПРИ СОЕДИНЕНИИ ====================== else if (tok[0] == "JOIN_FAILED") { lock_guard<mutex> lk(mtx); cout << "Failed to join room!\n"; _getch(); } else if (tok[0] == "JOIN_FAILED_SPECTATE_ONLY") { lock_guard<mutex> lk(mtx); cout << "Room in progress. You can join as spectator only.\n"; _getch(); } else if (tok[0] == "JOIN_FAILED_GAME_IN_PROGRESS") { lock_guard<mutex> lk(mtx); cout << "Room in progress. Spectators not allowed.\n"; _getch(); } // ====================== ВЫХОД ИЗ КОМНАТЫ ====================== else if (tok[0] == "LEFT_ROOM") { lock_guard<mutex> lk(mtx); currentRoomId = ""; clientState = ClientState::MENU; playersInLobby.clear(); spectatorsInLobby.clear(); isRoomOwner = false; isSpectator = false; } // ====================== ОБНОВЛЕНИЕ СПИСКА ИГРОКОВ ====================== else if (tok[0] == "PLAYER_JOINED" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); string pname = tok[1]; if (find(playersInLobby.begin(), playersInLobby.end(), pname) == playersInLobby.end()) playersInLobby.push_back(pname); } else if (tok[0] == "PLAYER_LEFT" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); string pname = tok[1]; playersInLobby.erase(remove(playersInLobby.begin(), playersInLobby.end(), pname), playersInLobby.end()); } else if (tok[0] == "SPECTATOR_JOINED" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); string sname = tok[1]; if (find(spectatorsInLobby.begin(), spectatorsInLobby.end(), sname) == spectatorsInLobby.end()) spectatorsInLobby.push_back(sname); } else if (tok[0] == "SPECTATOR_LEFT" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); string sname = tok[1]; spectatorsInLobby.erase(remove(spectatorsInLobby.begin(), spectatorsInLobby.end(), sname), spectatorsInLobby.end()); } // ====================== НАЧАЛО ИГРЫ ====================== else if (tok[0] == "GAME_START") { lock_guard<mutex> lk(mtx); clientState = isSpectator ? ClientState::SPECTATING : ClientState::PLAYING; // Сброс всех игровых данных myScore = 0; coins.clear(); allScores.clear(); otherPlayers.clear(); playerPos = { WIDTH / 2, HEIGHT / 2 }; // Отправка начальной позиции на сервер if (!isSpectator) { allScores[myName] = 0; float sx = (float)playerPos.x / (WIDTH - 1) * 100.0f; float sy = (float)playerPos.y / (HEIGHT - 1) * 100.0f; char buf[128]; sprintf_s(buf, "MOVE %.2f %.2f", sx, sy); send(sock, buf, (int)strlen(buf), 0); } } // ====================== ОКОНЧАНИЕ ИГРЫ ====================== else if (tok[0] == "GAME_END") { lock_guard<mutex> lk(mtx); if (tok.size() >= 4 && tok[1] == "WINNER") { gameWinner = tok[2]; gameWinnerScore = stoi(tok[3]); finalScores = allScores; // Копируем финальные очки clientState = ClientState::GAME_OVER; } } // ====================== ОБНОВЛЕНИЕ СОСТОЯНИЯ ====================== else if (tok[0] == "BACK_TO_LOBBY") { lock_guard<mutex> lk(mtx); clientState = ClientState::LOBBY; } else if (tok[0] == "SPAWN" && tok.size() >= 4) { lock_guard<mutex> lk(mtx); int id = stoi(tok[1]); coins[id] = serverToGrid(stof(tok[2]), stof(tok[3])); } else if (tok[0] == "DESPAWN" && tok.size() >= 2) { lock_guard<mutex> lk(mtx); coins.erase(stoi(tok[1])); } else if (tok[0] == "SCORE_UPDATE" && tok.size() >= 3) { lock_guard<mutex> lk(mtx); string name = tok[1]; int sc = stoi(tok[2]); allScores[name] = sc; if (name == myName) myScore = sc; } else if (tok[0] == "PLAYER_POS" && tok.size() >= 4) { lock_guard<mutex> lk(mtx); string name = tok[1]; Pos p = serverToGrid(stof(tok[2]), stof(tok[3])); if (name == myName && !isSpectator) playerPos = p; else otherPlayers[name] = p; } else if (tok[0] == "ROUND_TIME" && tok.size() >= 2) { roundTimeLeft = stoi(tok[1]); } } } } // ====================== ОТРИСОВКА МЕНЮ ====================== void drawMenu() { system("cls"); // Очистка консоли cout << "=== MAIN MENU ===\n\n"; lock_guard<mutex> lk(mtx); // Блокировка мьютекса для безопасного доступа к общим данным // Показ статистики игрока if (!statsReceived) { cout << "Loading your stats...\n\n"; // Если статистика ещё не получена } else if (playerStats.find(myName) != playerStats.end()) { const auto& stats = playerStats[myName]; cout << "Your stats:\n"; cout << " Games: " << stats.totalGames << " | Wins: " << stats.totalWins << " | Win Rate: " << (int)stats.winRate << "%\n"; cout << " Total Coins: " << stats.totalCoins << " | Total Score: " << stats.totalScore << "\n\n"; } else { cout << "Your stats: No games played yet\n\n"; } // Показ доступных комнат cout << "Available rooms:\n"; if (availableRooms.empty()) { cout << " (No rooms available)\n"; } else { int idx = 1; for (auto& kv : availableRooms) { const RoomInfo& info = kv.second; cout << " " << idx++ << ". " << info.id << " (" << info.playersInfo << ")"; // Статус комнаты if (info.state == "PLAYING") { cout << " [IN GAME]"; if (info.allowSpectators) cout << " - Watch available"; } else if (info.state == "LOBBY") { cout << " [WAITING]"; } cout << "\n"; } } // Команды для меню cout << "\nCommands:\n"; cout << " C - Create new room\n"; cout << " J - Join room as player\n"; cout << " W - Watch room (spectator mode)\n"; cout << " R - Refresh room list\n"; cout << " Q - Quit\n"; } // ====================== МЕНЮ СОЗДАНИЯ КОМНАТЫ ====================== void drawCreateMenu() { system("cls"); // Очистка консоли cout << "=== CREATE NEW ROOM ===\n\n"; cout << "Max players (2-8): "; // Запрос количества игроков } // ====================== МЕНЮ ПРИСОЕДИНЕНИЯ К КОМНАТЕ ====================== void drawJoinMenu(bool spectator = false) { system("cls"); // Заголовок в зависимости от режима if (spectator) cout << "=== WATCH ROOM (SPECTATOR) ===\n\n"; else cout << "=== JOIN ROOM AS PLAYER ===\n\n"; cout << "Available rooms:\n"; lock_guard<mutex> lk(mtx); if (availableRooms.empty()) { cout << " (No rooms available)\n"; } else { int idx = 1; for (auto& kv : availableRooms) { const RoomInfo& info = kv.second; if (spectator) { // Показываем только комнаты, доступные для просмотра if (info.allowSpectators) { cout << " " << idx++ << ". " << info.id << " (" << info.playersInfo << ")"; cout << " [" << info.state << "]"; if (info.state == "PLAYING") cout << " <- GAME IN PROGRESS"; cout << "\n"; } } else { cout << " " << idx++ << ". " << info.id << " (" << info.playersInfo << ")"; cout << " [" << info.state << "]"; if (info.state == "PLAYING") cout << " <- GAME IN PROGRESS (spectate only)"; else if (info.state == "LOBBY") cout << " <- Ready to join"; cout << "\n"; } } } cout << "\nEnter room ID (or press L to go back): "; } // ====================== ОТРИСОВКА ЛОББИ ====================== void drawLobby() { system("cls"); cout << "=== LOBBY: " << currentRoomId << " ===\n"; lock_guard<mutex> lk(mtx); // Статус комнаты if (availableRooms.find(currentRoomId) != availableRooms.end()) { const RoomInfo& info = availableRooms[currentRoomId]; if (info.state == "PLAYING") cout << "[STATUS: GAME IN PROGRESS]\n"; else cout << "[STATUS: WAITING FOR PLAYERS]\n"; } if (isSpectator) cout << "[YOU ARE SPECTATOR]\n"; cout << "\n"; // Список игроков cout << "Players:\n"; for (auto& p : playersInLobby) { cout << " - " << p; if (p == myName && !isSpectator) cout << " (YOU)"; // Показываем статистику игрока if (playerStats.find(p) != playerStats.end()) { const auto& stats = playerStats[p]; cout << " [W:" << stats.totalWins << " G:" << stats.totalGames << " WR:" << (int)stats.winRate << "%]"; } cout << "\n"; } // Список зрителей if (!spectatorsInLobby.empty()) { cout << "\nSpectators:\n"; for (auto& s : spectatorsInLobby) { cout << " - " << s; if (s == myName) cout << " (YOU)"; cout << "\n"; } } // Команды cout << "\nCommands:\n"; if (isRoomOwner && !isSpectator) cout << " S - Start game (owner only)\n"; cout << " L/Q - Leave room (back to menu)\n"; } // ====================== ОТРИСОВКА ИГРЫ ====================== void drawGame() { lock_guard<mutex> lk(mtx); system("cls"); // Таймер раунда int minutes = roundTimeLeft / 60; int seconds = roundTimeLeft % 60; cout << "Time: " << minutes << ":"; if (seconds < 10) cout << "0"; cout << seconds; if (isSpectator) cout << " [SPECTATOR MODE]"; cout << "\n"; // Верхняя граница игрового поля for (int x = 0; x < WIDTH; x++) cout << '#'; cout << '\n'; // Отрисовка игрового поля for (int y = 1; y < HEIGHT - 1; y++) { cout << '#'; for (int x = 1; x < WIDTH - 1; x++) { char ch = ' '; // Игрок if (!isSpectator && playerPos.x == x && playerPos.y == y) ch = 'O'; else { bool drawn = false; // Монетки for (const auto& kv : coins) { if (kv.second.x == x && kv.second.y == y) { ch = '@'; drawn = true; break; } } // Другие игроки if (!drawn) { for (const auto& kv : otherPlayers) { if (kv.second.x == x && kv.second.y == y) { ch = 'X'; break; } } } } cout << ch; } cout << "#\n"; } // Нижняя граница игрового поля for (int x = 0; x < WIDTH; x++) cout << '#'; cout << '\n'; // Отображение лидера string leaderName; int leaderScore = -1; for (const auto& kv : allScores) { if (kv.second > leaderScore) { leaderScore = kv.second; leaderName = kv.first; } } cout << "--- Leader ---\n"; if (!leaderName.empty()) cout << leaderName << " : " << leaderScore << " points\n"; else cout << "(No scores yet)\n"; cout << "--------------\n"; // Показ всех очков cout << "\nScores:\n"; for (auto& kv : allScores) { cout << " " << kv.first << ": " << kv.second; if (kv.first == myName && !isSpectator) cout << " (YOU)"; cout << "\n"; } // Подсказки для управления if (isSpectator) cout << "\n(Spectator mode - Q to leave room)\n"; else cout << "\n(Arrows/WASD to move, Q to leave room)\n"; } // ====================== ОТРИСОВКА ЭКРАНА "ИГРА ОКОНЧЕНА" ====================== void drawGameOver() { lock_guard<mutex> lk(mtx); // Блокировка мьютекса для безопасного доступа к общим данным system("cls"); // Очистка консоли // Заголовок cout << "\n"; cout << "========================================\n"; cout << " GAME OVER! \n"; cout << "========================================\n\n"; // Победитель игры cout << "*** WINNER ***\n"; cout << " " << gameWinner << " with " << gameWinnerScore << " points!\n\n"; cout << "--- Final Scores ---\n"; // Сортировка итоговых очков по убыванию vector<pair<string, int>> sortedScores; for (const auto& kv : finalScores) { sortedScores.push_back({ kv.first, kv.second }); } sort(sortedScores.begin(), sortedScores.end(), [](const pair<string, int>& a, const pair<string, int>& b) { return a.second > b.second; }); // Печать результатов с указанием позиции int place = 1; for (const auto& kv : sortedScores) { cout << " " << place++ << ". " << kv.first << " - " << kv.second << " points"; if (kv.first == myName && !isSpectator) cout << " (YOU)"; if (kv.first == gameWinner) cout << " [WINNER]"; cout << "\n"; } cout << "\n========================================\n"; cout << "Returning to lobby in a few seconds...\n"; cout << "========================================\n"; } // ====================== ПОМОЩЬНАЯ ФУНКЦИЯ ====================== // Находит ID монетки по позиции игрока int findCoinIdAt(const Pos& p) { for (const auto& kv : coins) { if (kv.second.x == p.x && kv.second.y == p.y) return kv.first; } return -1; // Если монетки нет на этой позиции } // ====================== ГЛАВНАЯ ФУНКЦИЯ ====================== int main() { // ====================== ИНИЦИАЛИЗАЦИЯ WINSOCK ====================== WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa); // Создание TCP-сокета sock = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in srv{}; srv.sin_family = AF_INET; srv.sin_port = htons(4000); // Порт сервера srv.sin_addr.s_addr = inet_addr("127.0.0.1"); // Локальный сервер if (connect(sock, (sockaddr*)&srv, sizeof(srv)) < 0) { cerr << "Connect failed\n"; return 1; } // ====================== ЗАПУСК ПОТОКА ПРИЁМА ДАННЫХ ====================== thread rcv(recvThread); // ====================== ЦИКЛ АВТОРИЗАЦИИ ====================== bool authenticated = false; while (!authenticated && running) { system("cls"); cout << "=================================\n"; cout << " WELCOME TO THE GAME! \n"; cout << "=================================\n\n"; cout << "Enter your name: "; getline(cin, myName); if (myName.empty()) { cout << "\nName cannot be empty! Press any key to try again...\n"; _getch(); continue; } // Отправка запроса на авторизацию cout << "\nConnecting to server...\n"; sendLine("AUTH " + myName); // Сброс статусов authFailed = false; authSuccess = false; // Ждём ответ от сервера (макс. 3 секунды) int waitCount = 0; while (!authFailed && !authSuccess && waitCount < 30 && running) { this_thread::sleep_for(chrono::milliseconds(100)); waitCount++; } // Обработка результата авторизации if (authSuccess) { authenticated = true; cout << "Authentication successful!\n"; cout << "Loading game data...\n"; } else if (authFailed) { cout << "\n=================================\n"; cout << " NAME ALREADY TAKEN! \n"; cout << "=================================\n"; cout << "The name '" << myName << "' is already in use.\n"; cout << "Please choose a different name.\n"; cout << "\nPress any key to try again...\n"; _getch(); } else { cout << "\nConnection timeout! Press any key to try again...\n"; _getch(); } } // ====================== ЖДЁМ ЗАГРУЗКУ СТАТИСТИКИ ====================== if (!authenticated) { running = false; closesocket(sock); if (rcv.joinable()) rcv.join(); WSACleanup(); return 1; } cout << "Waiting for stats...\n"; int waitCount = 0; while (!statsReceived && waitCount < 20 && running) { this_thread::sleep_for(chrono::milliseconds(100)); waitCount++; } if (statsReceived) cout << "Stats loaded successfully!\n"; else cout << "Stats loading timeout (continuing anyway)\n"; // ====================== НАЧАЛО ГЛАВНОГО ЦИКЛА ====================== clientState = ClientState::MENU; sendLine("LIST_ROOMS"); this_thread::sleep_for(chrono::milliseconds(200)); while (running) { ClientState currentState; { lock_guard<mutex> lk(mtx); currentState = clientState; // Чтение состояния клиента } // ====================== МЕНЮ ====================== if (currentState == ClientState::MENU) { drawMenu(); if (_kbhit()) { char key = _getch(); if (key == 'q' || key == 'Q') { // Выход running = false; break; } else if (key == 'r' || key == 'R') { // Обновление списка комнат sendLine("LIST_ROOMS"); this_thread::sleep_for(chrono::milliseconds(200)); } else if (key == 'c' || key == 'C') { // Создание новой комнаты drawCreateMenu(); int maxP; cin >> maxP; if (cin.fail()) { cin.clear(); string input; getline(cin, input); continue; } cout << "Auto-start when full? (1=yes, 0=no): "; int autoS; cin >> autoS; cout << "Allow spectators? (1=yes, 0=no): "; int allowSpec; cin >> allowSpec; cin.ignore(); sendLine("CREATE_ROOM " + to_string(maxP) + " " + to_string(autoS) + " " + to_string(allowSpec)); this_thread::sleep_for(chrono::milliseconds(300)); } else if (key == 'j' || key == 'J') { // Присоединение к комнате игроком drawJoinMenu(false); string roomId; getline(cin, roomId); if (roomId == "l" || roomId == "L") continue; if (!roomId.empty()) { sendLine("JOIN_ROOM " + roomId); this_thread::sleep_for(chrono::milliseconds(300)); } } else if (key == 'w' || key == 'W') { // Присоединение как зритель drawJoinMenu(true); string roomId; getline(cin, roomId); if (roomId == "l" || roomId == "L") continue; if (!roomId.empty()) { sendLine("JOIN_ROOM " + roomId + " SPECTATE"); this_thread::sleep_for(chrono::milliseconds(300)); } } } this_thread::sleep_for(chrono::milliseconds(200)); } // ====================== ЛОББИ ====================== else if (currentState == ClientState::LOBBY) { drawLobby(); if (_kbhit()) { char key = _getch(); if (key == 'q' || key == 'Q' || key == 'l' || key == 'L') { // Выход из комнаты sendLine("LEAVE_ROOM"); this_thread::sleep_for(chrono::milliseconds(200)); sendLine("LIST_ROOMS"); } else if ((key == 's' || key == 'S') && isRoomOwner && !isSpectator) { // Запуск игры sendLine("START_GAME"); } } this_thread::sleep_for(chrono::milliseconds(200)); } // ====================== ИГРОВОЙ ПРОЦЕСС ====================== else if (currentState == ClientState::PLAYING || currentState == ClientState::SPECTATING) { drawGame(); if (_kbhit()) { int key = _getch(); if (key == 'q' || key == 'Q') { // Выход из комнаты sendLine("LEAVE_ROOM"); this_thread::sleep_for(chrono::milliseconds(200)); sendLine("LIST_ROOMS"); continue; } // Зрители не могут двигаться if (isSpectator) { this_thread::sleep_for(chrono::milliseconds(120)); continue; } bool moved = false; // Управление стрелками if (key == 0 || key == 224) { key = _getch(); if (key == 72 && playerPos.y > 1) { playerPos.y--; moved = true; } if (key == 80 && playerPos.y < HEIGHT - 2) { playerPos.y++; moved = true; } if (key == 75 && playerPos.x > 1) { playerPos.x--; moved = true; } if (key == 77 && playerPos.x < WIDTH - 2) { playerPos.x++; moved = true; } } // Управление WASD else { if (key == 'w' && playerPos.y > 1) { playerPos.y--; moved = true; } if (key == 's' && playerPos.y < HEIGHT - 2) { playerPos.y++; moved = true; } if (key == 'a' && playerPos.x > 1) { playerPos.x--; moved = true; } if (key == 'd' && playerPos.x < WIDTH - 2) { playerPos.x++; moved = true; } } // Отправка позиции на сервер if (moved) { float sx = (float)playerPos.x / (WIDTH - 1) * 100.0f; float sy = (float)playerPos.y / (HEIGHT - 1) * 100.0f; char buf[128]; sprintf_s(buf, "MOVE %.2f %.2f", sx, sy); sendLine(buf); lock_guard<mutex> lk(mtx); int cid = findCoinIdAt(playerPos); // Проверка монетки на текущей позиции if (cid != -1) { sendLine("COLLECT " + to_string(cid)); } } } this_thread::sleep_for(chrono::milliseconds(120)); } // ====================== ИГРА ОКОНЧЕНА ====================== else if (currentState == ClientState::GAME_OVER) { drawGameOver(); this_thread::sleep_for(chrono::milliseconds(500)); } } // ====================== ЗАВЕРШЕНИЕ ====================== running = false; closesocket(sock); if (rcv.joinable()) rcv.join(); WSACleanup(); return 0; }