专栏文章

五子棋联机

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mioruprt
此快照首次捕获于
2025/12/03 00:07
3 个月前
此快照最后确认于
2025/12/03 00:07
3 个月前
查看原文
CPP
//==================================
// 服务端 (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;
}


评论

0 条评论,欢迎与作者交流。

正在加载评论...