五子棋联机
yi_hr
·
·
个人记录
//==================================
// 服务端 (Gomoku_Server.cpp)
//==================================
#include <iostream>
#include <vector>
#include <string>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 9999
#define BUFFER_SIZE 2048
#define BOARD_SIZE 15
// 全局游戏状态
SOCKET players[2];
int playerCount = 0;
int board[BOARD_SIZE][BOARD_SIZE] = {0}; // 0: empty, 1: player1 (black), 2: player2 (white)
int currentPlayer = 0; // 0 for player 1, 1 for player 2
// 函数声明
void SendToPlayer(int playerIndex, const std::string& message);
void Broadcast(const std::string& message);
bool CheckWin(int r, int c);
std::string GetBoardString();
void InitializeServer(SOCKET& serverSocket) {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "Error: WSAStartup failed.\n";
exit(1);
}
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET) {
std::cerr << "Error: Socket creation failed.\n";
WSACleanup();
exit(1);
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = INADDR_ANY;
if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Error: Bind failed.\n";
closesocket(serverSocket);
WSACleanup();
exit(1);
}
if (listen(serverSocket, 2) == SOCKET_ERROR) {
std::cerr << "Error: Listen failed.\n";
closesocket(serverSocket);
WSACleanup();
exit(1);
}
std::cout << "Server is running. Waiting for players on port " << PORT << "...\n";
}
void AcceptPlayers(SOCKET& serverSocket) {
while (playerCount < 2) {
sockaddr_in clientAddr;
int clientAddrSize = sizeof(clientAddr);
players[playerCount] = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
if (players[playerCount] == INVALID_SOCKET) {
std::cerr << "Error: Accept failed.\n";
continue;
}
std::cout << "Player " << playerCount + 1 << " connected.\n";
// DO NOT SEND 'WELCOME' HERE.
playerCount++;
}
std::cout << "Two players connected. Preparing to start game.\n";
}
void GameLoop() {
// 初始化棋盘
memset(board, 0, sizeof(board));
currentPlayer = 0; // 玩家1 (黑棋) 先手
// --- NEW: Send initial setup messages here ---
std::cout << "Sending welcome messages and starting game...\n";
SendToPlayer(0, "WELCOME 1");
SendToPlayer(1, "WELCOME 2");
Broadcast("START");
// -------------------------------------------
char buffer[BUFFER_SIZE];
while (true) {
// 广播棋盘状态
Broadcast("BOARD " + GetBoardString());
// 通知当前玩家下棋,另一位等待
SendToPlayer(currentPlayer, "YOUR_TURN");
SendToPlayer(1 - currentPlayer, "WAIT");
// 从当前玩家处接收移动指令
memset(buffer, 0, BUFFER_SIZE);
// MODIFICATION: Add logic to handle client sending newline
std::string received_data;
while(true) {
int bytesReceived = recv(players[currentPlayer], buffer, BUFFER_SIZE - 1, 0);
if (bytesReceived <= 0) {
received_data = "DISCONNECTED";
break;
}
buffer[bytesReceived] = '\0';
received_data.append(buffer);
if (received_data.find('\n') != std::string::npos) {
received_data = received_data.substr(0, received_data.find('\n'));
break;
}
}
if (received_data == "DISCONNECTED") {
std::cerr << "Player " << currentPlayer + 1 << " disconnected.\n";
Broadcast("OPPONENT_DISCONNECTED");
break;
}
if (received_data.rfind("MOVE ", 0) == 0) {
int row, col;
sscanf_s(received_data.substr(5).c_str(), "%d,%d", &row, &col);
// 验证移动
if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] == 0) {
board[row][col] = currentPlayer + 1; // 放置棋子
if (CheckWin(row, col)) {
Broadcast("BOARD " + GetBoardString());
SendToPlayer(currentPlayer, "WIN");
SendToPlayer(1 - currentPlayer, "LOSE");
std::cout << "Game over. Player " << currentPlayer + 1 << " wins.\n";
break;
}
currentPlayer = 1 - currentPlayer;
} else {
SendToPlayer(currentPlayer, "INVALID_MOVE");
}
}
}
closesocket(players[0]);
closesocket(players[1]);
}
int main() {
SOCKET serverSocket;
InitializeServer(serverSocket);
AcceptPlayers(serverSocket);
GameLoop();
std::cout << "Server shutting down.\n";
closesocket(serverSocket);
WSACleanup();
return 0;
}
void SendToPlayer(int playerIndex, const std::string& message) {
std::string fullMessage = message + "\n";
send(players[playerIndex], fullMessage.c_str(), fullMessage.length(), 0);
}
void Broadcast(const std::string& message) {
SendToPlayer(0, message);
SendToPlayer(1, message);
}
std::string GetBoardString() {
std::string boardStr = "";
for (int i = 0; i < BOARD_SIZE; ++i) {
for (int j = 0; j < BOARD_SIZE; ++j) {
boardStr += std::to_string(board[i][j]);
}
}
return boardStr;
}
bool CheckWin(int r, int c) {
int player = board[r][c];
if (player == 0) return false;
// Check four directions
// 1. Horizontal
int count = 0;
for (int i = c - 4; i <= c + 4; ++i) {
if (i >= 0 && i < BOARD_SIZE && board[r][i] == player) {
count++;
if (count >= 5) return true;
} else {
count = 0;
}
}
// 2. Vertical
count = 0;
for (int i = r - 4; i <= r + 4; ++i) {
if (i >= 0 && i < BOARD_SIZE && board[i][c] == player) {
count++;
if (count >= 5) return true;
} else {
count = 0;
}
}
// 3. Diagonal (top-left to bottom-right)
count = 0;
for (int i = -4; i <= 4; ++i) {
int cur_r = r + i, cur_c = c + i;
if (cur_r >= 0 && cur_r < BOARD_SIZE && cur_c >= 0 && cur_c < BOARD_SIZE && board[cur_r][cur_c] == player) {
count++;
if (count >= 5) return true;
} else {
count = 0;
}
}
// 4. Anti-diagonal (top-right to bottom-left)
count = 0;
for (int i = -4; i <= 4; ++i) {
int cur_r = r + i, cur_c = c - i;
if (cur_r >= 0 && cur_r < BOARD_SIZE && cur_c >= 0 && cur_c < BOARD_SIZE && board[cur_r][cur_c] == player) {
count++;
if (count >= 5) return true;
} else {
count = 0;
}
}
return false;
}
//==================================
// 客户端 (Gomoku_Client.cpp) - 已修改为鼠标点击输入
//==================================
#include <iostream>
#include <string>
#include <vector>
#include <winsock2.h>
#include <windows.h>
#include <limits>
#pragma comment(lib, "ws2_32.lib")
#define PORT 9999
#define BUFFER_SIZE 2048
#define BOARD_SIZE 15
int board[BOARD_SIZE][BOARD_SIZE] = {0};
int myPlayerNumber = 0;
// 函数声明
void ClearScreen();
void DisplayBoard();
void ConnectToServer(SOCKET& clientSocket);
void GameLoop(SOCKET& clientSocket);
void GetMoveFromMouseClick(SOCKET clientSocket); // <-- 新增: 处理鼠标点击的函数
void ClearScreen() {
// 使用 Windows API 来清除屏幕,避免 system("cls") 闪烁
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coordScreen = {0, 0};
DWORD cCharsWritten;
CONSOLE_SCREEN_BUFFER_INFO csbi;
DWORD dwConSize;
GetConsoleScreenBufferInfo(hConsole, &csbi);
dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
FillConsoleOutputCharacter(hConsole, (TCHAR)' ', dwConSize, coordScreen, &cCharsWritten);
GetConsoleScreenBufferInfo(hConsole, &csbi);
FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);
SetConsoleCursorPosition(hConsole, coordScreen);
}
void DisplayBoard() {
ClearScreen();
std::cout << "You are Player " << myPlayerNumber << " (" << (myPlayerNumber == 1 ? "Black 'X'" : "White 'O'") << ")\n";
std::cout << "Gomoku (Five-in-a-Row)\n\n";
// 打印列号
std::cout << " ";
for (int i = 0; i < BOARD_SIZE; ++i) {
printf("%2d ", i);
}
std::cout << "\n";
std::cout << " +";
for (int i = 0; i < BOARD_SIZE; ++i) {
std::cout << "---";
}
std::cout << "+\n";
for (int i = 0; i < BOARD_SIZE; ++i) {
printf("%2d | ", i); // 打印行号
for (int j = 0; j < BOARD_SIZE; ++j) {
char piece = '.';
if (board[i][j] == 1) piece = 'X'; // Player 1 (Black)
else if (board[i][j] == 2) piece = 'O'; // Player 2 (White)
std::cout << piece << " ";
}
std::cout << "|\n";
}
std::cout << " +";
for (int i = 0; i < BOARD_SIZE; ++i) {
std::cout << "---";
}
std::cout << "+\n\n";
}
void ConnectToServer(SOCKET& clientSocket) {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "Error: WSAStartup failed.\n";
exit(1);
}
clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "Error: Socket creation failed.\n";
WSACleanup();
exit(1);
}
char serverIp[20];
std::cout << "Enter server IP address: ";
std::cin >> serverIp;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = inet_addr(serverIp);
if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Error: Connection to server failed. IP: " << serverIp << "\n";
closesocket(clientSocket);
WSACleanup();
exit(1);
}
std::cout << "Connected to server. Waiting for another player...\n";
}
// === 新增函数: 使用 Windows Console API 获取鼠标点击并转换为棋盘坐标 ===
void GetMoveFromMouseClick(SOCKET clientSocket) {
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
if (hInput == INVALID_HANDLE_VALUE) {
std::cerr << "Error getting console handle.\n";
return;
}
DWORD dwOldMode;
GetConsoleMode(hInput, &dwOldMode); // 保存当前控制台模式
// 设置新模式:开启鼠标输入,并禁用快速编辑模式(它会干扰鼠标事件)
DWORD dwNewMode = ENABLE_EXTENDED_FLAGS | ENABLE_MOUSE_INPUT;
SetConsoleMode(hInput, dwNewMode);
INPUT_RECORD InRec;
DWORD NumRead;
while (true) {
// 等待控制台输入事件
if (!ReadConsoleInput(hInput, &InRec, 1, &NumRead)) {
continue;
}
// 我们只关心鼠标事件
if (InRec.EventType == MOUSE_EVENT) {
// 我们只关心鼠标左键单击事件
if (InRec.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED &&
InRec.Event.MouseEvent.dwEventFlags == 0) // 0 表示单击,不是双击或移动
{
// 获取鼠标点击的控制台屏幕坐标 (列, 行)
int consoleX = InRec.Event.MouseEvent.dwMousePosition.X;
int consoleY = InRec.Event.MouseEvent.dwMousePosition.Y;
// --- 坐标转换逻辑 ---
// 根据 DisplayBoard() 的布局来计算
// 棋盘格的起始X坐标大约是 5 (2位行号 + ' | ')
// 棋盘格的起始Y坐标是 3 (列号 + '---' + 第一行)
// 每个格子在X方向占3个字符 ('X'/'O'/' ' 和 两个空格)
int board_col = (consoleX-5)/3;
int board_row = consoleY-5;
// --- 验证点击是否有效 ---
// 1. 必须在棋盘的行列范围内
// 2. 必须精确点击在棋子的位置上,而不是旁边的空格
// (consoleX - 5) % 3 == 0 确保了这一点
if (board_row >= 0 && board_row < BOARD_SIZE &&
board_col >= 0 && board_col < BOARD_SIZE &&
(consoleX - 5) >= 0 && (consoleX - 5) % 3 == 0)
{
// 找到了一个有效的点击,发送移动指令
std::string moveMsg = "MOVE " + std::to_string(board_row) + "," + std::to_string(board_col);
send(clientSocket, (moveMsg + "\n").c_str(), moveMsg.length() + 1, 0);
// 恢复旧的控制台模式并退出函数
SetConsoleMode(hInput, dwOldMode);
return;
}
}
}
}
}
void GameLoop(SOCKET& clientSocket) {
char buffer[BUFFER_SIZE];
bool gameRunning = true;
std::string message_buffer = "";
while (gameRunning) {
memset(buffer, 0, BUFFER_SIZE);
int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
if (bytesReceived <= 0) {
std::cout << "Disconnected from server.\n";
break;
}
message_buffer.append(buffer, bytesReceived);
size_t pos;
while ((pos = message_buffer.find('\n')) != std::string::npos) {
std::string command = message_buffer.substr(0, pos);
message_buffer.erase(0, pos + 1);
if (command.rfind("WELCOME ", 0) == 0) {
myPlayerNumber = std::stoi(command.substr(8));
} else if (command == "START") {
std::cout << "Game is starting!\n";
} else if (command.rfind("BOARD ", 0) == 0) {
std::string boardData = command.substr(6);
if (boardData.length() == BOARD_SIZE * BOARD_SIZE) {
for (int i = 0; i < BOARD_SIZE; ++i) {
for (int j = 0; j < BOARD_SIZE; ++j) {
board[i][j] = boardData[i * BOARD_SIZE + j] - '0';
}
}
DisplayBoard();
}
} else if (command == "YOUR_TURN") {
// ========================================================
// === 核心修改点 ===
// === 从键盘输入改为调用新的鼠标点击函数 ===
// ========================================================
std::cout << "Your turn. Click on the board to place your piece...\n";
GetMoveFromMouseClick(clientSocket);
} else if (command == "WAIT") {
std::cout << "Waiting for opponent's move...\n";
} else if (command == "INVALID_MOVE") {
std::cout << "Invalid move (out of bounds or position already taken). Please try again.\n";
} else if (command == "WIN") {
// 最后一次更新棋盘以显示获胜的棋局
// 服务端在发送WIN之前会发送最后的BOARD状态,所以这里不需要额外操作
std::cout << "Congratulations! You win!\n";
gameRunning = false;
} else if (command == "LOSE") {
std::cout << "You lose. Better luck next time!\n";
gameRunning = false;
} else if (command == "DRAW") {
std::cout << "The game is a draw!\n";
gameRunning = false;
} else if (command == "OPPONENT_DISCONNECTED") {
std::cout << "Your opponent has disconnected. You win by default!\n";
gameRunning = false;
}
}
}
}
int main() {
SOCKET clientSocket;
ConnectToServer(clientSocket);
GameLoop(clientSocket);
std::cout << "\nGame has ended. Press enter to exit.\n";
std::cin.get();
closesocket(clientSocket);
WSACleanup();
return 0;
}