You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Java Socket的三人回合制3×3多人游戏开发及回合计时咨询

Java Socket 3-Player Grid Game Implementation Guide

Hey there! Great choice diving into network programming with a multiplayer game—this kind of project is perfect for learning how client-server communication works. Let’s break down how to build your 3-player turn-based grid game step by step, covering all your requirements: 3-player start trigger, 3×3 interactive grid, turn-based rotation, and turn timers.

Core Architecture Overview

We’ll use a server-client model where the server manages all game state (grid data, current player, timers) and broadcasts updates to all clients. Clients handle user input and render the game interface based on server messages.


1. Server-Side Implementation

The server’s job is to manage player connections, validate moves, track game state, and enforce turn rules.

Player Connection Management

  • Use a thread-safe collection (like CopyOnWriteArrayList) to keep track of connected players.
  • Wait until exactly 3 players connect before starting the game. Each player gets a unique ID (1, 2, 3) when they join.
  • For each player, spawn a dedicated PlayerHandler thread to handle incoming messages from that client.

Game State & Turn Control

  • Maintain a 3×3 integer array (grid) to track which player owns each cell (0 = unclaimed, 1/2/3 = player ID).
  • Track the currentPlayer to know whose turn it is. After a valid move, rotate to the next player (1 → 2 → 3 → 1).
  • Broadcast all state updates to every client to ensure everyone sees the same game board.

Turn Timer Implementation

  • Use ScheduledExecutorService to run a timer for each turn (e.g., 30 seconds).
  • If the timer expires before the current player makes a move, broadcast a timeout message, skip their turn, and switch to the next player.
  • Cancel the timer immediately when a valid move is received.

Server Code Snippets

Here’s a simplified GameServer class:

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

public class GameServer {
    private final List<PlayerHandler> players = new CopyOnWriteArrayList<>();
    private final int[][] grid = new int[3][3];
    private int currentPlayer = 1;
    private final ScheduledExecutorService timerService = Executors.newScheduledThreadPool(1);
    private ScheduledFuture<?> currentTimer;
    private static final int TURN_TIMEOUT = 30;

    public void start(int port) throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Server running—waiting for 3 players...");

        int playerId = 1;
        while (players.size() < 3) {
            Socket clientSocket = serverSocket.accept();
            PlayerHandler handler = new PlayerHandler(clientSocket, this, playerId);
            players.add(handler);
            handler.start();
            System.out.println("Player " + playerId + " connected");
            playerId++;
        }

        // Trigger game start for all players
        broadcast("GAME_START");
        startTurnTimer();
    }

    private void broadcast(String message) {
        for (PlayerHandler player : players) {
            player.sendMessage(message);
        }
    }

    public void processMove(int playerId, int row, int col) {
        // Validate move: correct player and unclaimed cell
        if (playerId != currentPlayer || grid[row][col] != 0) {
            players.get(playerId - 1).sendMessage("INVALID_MOVE");
            return;
        }

        // Update grid and broadcast to all clients
        grid[row][col] = playerId;
        broadcast("UPDATE_GRID:" + row + "," + col + "," + playerId);

        // Cancel timer and switch player
        if (currentTimer != null) currentTimer.cancel(false);
        currentPlayer = currentPlayer % 3 + 1;
        broadcast("NEXT_TURN:" + currentPlayer);
        startTurnTimer();
    }

    private void startTurnTimer() {
        currentTimer = timerService.schedule(() -> {
            broadcast("TURN_TIMEOUT:" + currentPlayer);
            currentPlayer = currentPlayer % 3 + 1;
            broadcast("NEXT_TURN:" + currentPlayer);
            startTurnTimer();
        }, TURN_TIMEOUT, TimeUnit.SECONDS);
    }

    public void removePlayer(PlayerHandler handler) {
        players.remove(handler);
        // Add logic to handle player disconnections (e.g., end game or re-recruit players)
    }

    public static void main(String[] args) throws IOException {
        new GameServer().start(12345);
    }

    // Inner class to handle individual player connections
    private static class PlayerHandler extends Thread {
        private final Socket socket;
        private final GameServer server;
        private final int playerId;
        private PrintWriter out;
        private BufferedReader in;

        public PlayerHandler(Socket socket, GameServer server, int playerId) {
            this.socket = socket;
            this.server = server;
            this.playerId = playerId;
        }

        @Override
        public void run() {
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintWriter(socket.getOutputStream(), true);

                // Send player ID to client
                out.println("PLAYER_ID:" + playerId);

                String input;
                while ((input = in.readLine()) != null) {
                    if (input.startsWith("CLICK:")) {
                        String[] coords = input.split(":")[1].split(",");
                        int row = Integer.parseInt(coords[0]);
                        int col = Integer.parseInt(coords[1]);
                        server.processMove(playerId, row, col);
                    }
                }
            } catch (IOException e) {
                System.err.println("Player " + playerId + " disconnected");
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                server.removePlayer(this);
            }
        }

        public void sendMessage(String message) {
            out.println(message);
        }
    }
}

2. Client-Side Implementation

The client handles the game interface (3×3 button grid) and communicates with the server to send moves and receive updates. We’ll use Swing for the UI, but you could also use JavaFX.

UI Setup

  • Create a 3×3 grid of JButton components, each initialized with a blank image.
  • Disable all buttons by default—only enable them when it’s the local player’s turn.
  • Add a label to display the remaining turn time.

Server Message Handling

  • Spawn a background thread to listen for incoming messages from the server.
  • Update the UI based on server commands:
    • PLAYER_ID: Store the local player’s ID.
    • GAME_START: Show a notification that the game has begun.
    • NEXT_TURN: Enable/disable buttons based on whether it’s the local player’s turn, and start a local countdown timer.
    • UPDATE_GRID: Replace the button’s image with the corresponding player’s icon.
    • TURN_TIMEOUT: Notify the user that another player timed out.

Client Code Snippets

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.*;

public class GameClient extends JFrame {
    private final JButton[][] gridButtons = new JButton[3][3];
    private Socket socket;
    private PrintWriter out;
    private BufferedReader in;
    private int playerId;
    private final JLabel timerLabel = new JLabel("Time left: 30s", SwingConstants.CENTER);

    public GameClient(String serverIp, int port) throws IOException {
        // Connect to server
        socket = new Socket(serverIp, port);
        out = new PrintWriter(socket.getOutputStream(), true);
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        // Initialize UI
        initUI();

        // Start thread to handle server messages
        new Thread(this::listenToServer).start();
    }

    private void initUI() {
        setTitle("3-Player Grid Game");
        setSize(450, 500);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        // Add timer label
        timerLabel.setFont(new Font("Arial", Font.BOLD, 16));
        add(timerLabel, BorderLayout.NORTH);

        // Create grid panel
        JPanel gridPanel = new JPanel(new GridLayout(3, 3, 5, 5));
        gridPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        for (int row = 0; row < 3; row++) {
            for (int col = 0; col < 3; col++) {
                gridButtons[row][col] = new JButton();
                gridButtons[row][col].setIcon(new ImageIcon("blank.png")); // Replace with your blank image
                gridButtons[row][col].setEnabled(false);

                final int r = row;
                final int c = col;
                gridButtons[row][col].addActionListener(e -> out.println("CLICK:" + r + "," + c));

                gridPanel.add(gridButtons[row][col]);
            }
        }
        add(gridPanel, BorderLayout.CENTER);

        setVisible(true);
    }

    private void listenToServer() {
        try {
            String message;
            while ((message = in.readLine()) != null) {
                processServerMessage(message);
            }
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this, "Lost connection to server!");
            System.exit(1);
        }
    }

    private void processServerMessage(String message) {
        SwingUtilities.invokeLater(() -> {
            if (message.startsWith("PLAYER_ID:")) {
                playerId = Integer.parseInt(message.split(":")[1]);
                JOptionPane.showMessageDialog(this, "You are Player " + playerId);
            } else if (message.equals("GAME_START")) {
                JOptionPane.showMessageDialog(this, "Game started!");
            } else if (message.startsWith("NEXT_TURN:")) {
                int nextPlayer = Integer.parseInt(message.split(":")[1]);
                // Enable buttons only if it's the local player's turn and cell is unclaimed
                for (int row = 0; row < 3; row++) {
                    for (int col = 0; col < 3; col++) {
                        boolean isUnclaimed = gridButtons[row][col].getIcon().toString().contains("blank");
                        gridButtons[row][col].setEnabled(nextPlayer == playerId && isUnclaimed);
                    }
                }
                // Start local countdown
                startLocalCountdown();
            } else if (message.startsWith("UPDATE_GRID:")) {
                String[] parts = message.split(":")[1].split(",");
                int row = Integer.parseInt(parts[0]);
                int col = Integer.parseInt(parts[1]);
                int player = Integer.parseInt(parts[2]);
                gridButtons[row][col].setIcon(new ImageIcon("player" + player + ".png")); // Replace with your player images
                gridButtons[row][col].setEnabled(false);
            } else if (message.startsWith("TURN_TIMEOUT:")) {
                int timedOutPlayer = Integer.parseInt(message.split(":")[1]);
                JOptionPane.showMessageDialog(this, "Player " + timedOutPlayer + " timed out!");
            } else if (message.equals("INVALID_MOVE")) {
                JOptionPane.showMessageDialog(this, "Invalid move!\nEither it's not your turn or the cell is taken.");
            }
        });
    }

    private void startLocalCountdown() {
        new Thread(() -> {
            for (int i = 29; i >= 0; i--) {
                // Stop countdown if it's no longer the local player's turn
                if (!gridButtons[0][0].isEnabled()) break;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int finalI = i;
                SwingUtilities.invokeLater(() -> timerLabel.setText("Time left: " + finalI + "s"));
            }
        }).start();
    }

    public static void main(String[] args) throws IOException {
        new GameClient("localhost", 12345);
    }
}

Key Notes & Best Practices

  • Thread Safety: Always ensure shared server state (like the grid or player list) is accessed in a thread-safe way—CopyOnWriteArrayList helps with this, and you can use synchronized blocks if needed for more complex operations.
  • Message Protocol: Define clear, consistent message formats (like COMMAND:PARAMS) to avoid parsing errors between client and server.
  • Resource Management: Load images using getResource() instead of absolute paths to make your project portable (e.g., getClass().getResource("/blank.png")).
  • Disconnection Handling: Add logic to handle player disconnections gracefully—you might want to end the game and notify remaining players, or wait for a new player to join.

内容的提问来源于stack exchange,提问作者Rommel Yohanes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:29:17