Client Server Gomuke Game with GUI

Description

You’ll be writing a game, called Gomoko (five-in-a-raw). It is an abstract strategy board game. It is traditionally played with Go pieces (black and white stones) on a Go board, using 15×15 grid intersections.The pieces are not moved or removed from the board. The game is known in several countries under different names. For instance it is known as Gomoku in Japan.

The two players alternate turns placing a stone of their color on an empty intersection. The winner is the first player to form an unbroken chain of five stones horizontally, vertically, or diagonally. The player with black stone moves first.

Your program is a client-server program with GUI. It will mainly consist of two parts:  the GomokuServer, the server and the GomokuClient, the client.

There is one instance of the server.  It will run on a particular port and receive TCP (“Stream”) connections from one or more clients.  The first two clients will be paired to join the same game. If more than two clients send request to join a game, the server should let the third client wait and pair the client with the next client in the line (i.e. the fourth client) who sends the request.

The players participate in the game discussion by running the client.  The client connects to a GomokuServer.  All the players should be able to see a graphical user interface. A client should be able to display 1) a board of 15×15; 2) the white color stones and the black color stones; 3) a chat area for the two players to have a private chat; 4) any message from the server, for instance the announcement of who is playing first, who wins, who left the game etc.

In addition, the program should allow the players to enter their names (attach each to one of the 2 colors), give up, and reset a game.

public class GomokuProtocol {

private static final String SEPARATOR = “\0”;

private static final String MESSAGE_PLAY = SEPARATOR + “/play”;

private static final String MESSAGE_SET_BLACK = SEPARATOR + “/black”;

private static final String MESSAGE_SET_WHITE = SEPARATOR + “/white”;

private static final String MESSAGE_WIN= SEPARATOR + “/win”;

private static final String MESSAGE_LOSE = SEPARATOR + “/lose”;

private static final String MESSAGE_RESET = SEPARATOR + “/reset”;

private static final String MESSAGE_GIVEUP = SEPARATOR + “/giveup”;

private static final String MESSAGE_CHAT = SEPARATOR + “/chat”;

private static final String MESSAGE_CHANGE_NAME = SEPARATOR + “/nick”;

public static String generateChatMessage(String sender, String chat) {

StringBuildersb = new StringBuilder();

sb.append(MESSAGE_CHAT).append(SEPARATOR).append(sender)

.append(SEPARATOR).append(chat);

returnsb.toString();

}

public static booleanisChatMessage(String msg) {

returnmsg.startsWith(MESSAGE_CHAT);

}

public static String[] getChatDetail(String msg) {

if (isChatMessage(msg)) {

String[] tokens = msg.split(SEPARATOR);

if (tokens.length>= 4) {

return new String[]{tokens[2], tokens[3]};

}

}

return null;

}

public static String generatePlayMessage(booleanisBlack, int row, int col) {

StringBuildersb = new StringBuilder();

sb.append(MESSAGE_PLAY).append(SEPARATOR).append(isBlack ? 1: 0)

.append(SEPARATOR).append(row)

.append(SEPARATOR).append(col);

returnsb.toString();

}

public static booleanisPlayMessage(String msg) {

returnmsg.startsWith(MESSAGE_PLAY);

}

public static int[] getPlayDetail(String msg) {

if (isPlayMessage(msg)) {

String[] tokens = msg.split(SEPARATOR);

if (tokens.length>= 5) {

return new int[]{

Integer.parseInt(tokens[2]),

Integer.parseInt(tokens[3]),

Integer.parseInt(tokens[4])};

}

}

return null;

}

public static String generateChangeNameMessage(String oldName, String newName) {

StringBuildersb = new StringBuilder();

sb.append(MESSAGE_CHANGE_NAME).append(SEPARATOR).append(oldName)

.append(SEPARATOR).append(newName);

returnsb.toString();

}

public static booleanisChangeNameMessage(String msg) {

returnmsg.startsWith(MESSAGE_CHANGE_NAME);

}

public static String[] getChangeNameDetail(String msg) {

if (isChangeNameMessage(msg)) {

String[] tokens = msg.split(SEPARATOR);

if (tokens.length>= 3) {

return new String[]{tokens[2], tokens[3]};

}

}

return null;

}

public static String generateSetBlackColorMessage() {

return MESSAGE_SET_BLACK;

}

public static booleanisSetBlackColorMessage(String msg) {

returnmsg.startsWith(MESSAGE_SET_BLACK);

}

public static String generateSetWhiteColorMessage() {

return MESSAGE_SET_WHITE;

}

public static booleanisSetWhiteColorMessage(String msg) {

returnmsg.startsWith(MESSAGE_SET_WHITE);

}

public static String generateWinMessage() {

return MESSAGE_WIN;

}

public static booleanisWinMessage(String msg) {

returnmsg.startsWith(MESSAGE_WIN);

}

public static String generateLoseMessage() {

return MESSAGE_LOSE;

}

public static booleanisLoseMessage(String msg) {

returnmsg.startsWith(MESSAGE_LOSE);

}

public static String generateResetMessage() {

return MESSAGE_RESET;

}

public static booleanisResetMessage(String msg) {

returnmsg.startsWith(MESSAGE_RESET);

}

public static String generateGiveupMessage() {

return MESSAGE_GIVEUP;

}

public static booleanisGiveupMessage(String msg) {

returnmsg.startsWith(MESSAGE_GIVEUP);

}

public static void main(String[] args) {

// example how to generate a message and how to parse it

String msg = generatePlayMessage(true, 10, 4);

if (isPlayMessage(msg)) {

int[] detail = getPlayDetail(msg);

// black is 1 and white is 0

System.out.println(“color is ” + detail[0]);

System.out.println(“row is ” + detail[1]);

System.out.println(“col is ” + detail[2]);

}

}

} 

Solution 

Client.java 

importjava.awt.BorderLayout;

importjava.awt.Color;

importjava.awt.Dimension;

importjava.awt.GridLayout;

importjava.awt.event.ActionEvent;

importjava.awt.event.ActionListener;

importjava.io.BufferedReader;

importjava.io.IOException;

importjava.io.InputStreamReader;

importjava.io.OutputStreamWriter;

importjava.net.Socket;

importjavax.swing.BorderFactory;

importjavax.swing.JButton;

importjavax.swing.JFrame;

importjavax.swing.JOptionPane;

importjavax.swing.JPanel;

importjavax.swing.JScrollPane;

importjavax.swing.JTextArea;

importjavax.swing.JTextField;

importjavax.swing.SwingUtilities;

/**

* The host of the server must be specified in the command line argument.

* The port can be specified as the second argument, otherwise

* {@code GomokuProtocol#DEFAULT_PORT} is used.

*/

public class Client implements Runnable, ActionListener {

public static void main(String[] args) {

String host;

int port;

try {

host = args[0];

if (args.length> 1) {

port = Integer.parseInt(args[1]);

} else {

port = GomokuProtocol.DEFAULT_PORT;

}

} catch (final RuntimeException re) {

System.out.println(“Usage: java Client host port”);

return;

}

try {

System.out.println(“waiting for the other client..”);

new Client(host, port);

} catch (final IOException e) {

System.out.printf(“could not connect with the server %s:%d\n”, host, port);

}

}

private final Socket sk;

private final BufferedReader in;

private final OutputStreamWriter out;

private String name;

public Client(final String host, final int port) throws IOException {

// create the socket and streams to communicate with the server.

this.sk = new Socket(host, port);

this.in = new BufferedReader(new InputStreamReader(this.sk.getInputStream()));

this.out = new OutputStreamWriter(this.sk.getOutputStream());

this.name = “”;

// create the GUI for the game

this.initializeGUI();

// create a thread to accept the information from the server

new Thread(this).start();

frame.setVisible(true);

}

public Client() {

this.sk = null;

this.in = null;

this.out = null;

this.name = “<init>”;

this.initializeGUI();

}

privateJFrame frame;

privateJTextAreachatArea;

privateJTextFieldinputField;

privateJButtonchatButton;

privateJButtongiveupButton;

privateJButtonresetButton;

privateJButtonrenameButton;

privateJButtoncloseButton;

// the buttons to render black and whites

privateJButton[][] buttons;

private void initializeGUI() {

// left is the game panel and right is the control and chat panel.

finalJPanelrightPanel = new JPanel();

initializeControlPanel(rightPanel);

finalJPanelleftPanel = new JPanel();

initializeGamePanel(leftPanel);

finalJPanelmainPanel = new JPanel();

mainPanel.setLayout(new BorderLayout());

mainPanel.add(leftPanel, BorderLayout.CENTER);

mainPanel.add(rightPanel, BorderLayout.EAST);

// create the frame to render the GUI panels

this.frame = new JFrame();

frame.setTitle(“Hello, <unknown>!”);

frame.setContentPane(mainPanel);

frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

frame.pack();

// add button listeners

this.closeButton.addActionListener(this);

this.giveupButton.addActionListener(this);

this.resetButton.addActionListener(this);

this.renameButton.addActionListener(this);

this.chatButton.addActionListener(this);

for (int i = 0; i <GomokuProtocol.BOARD_SIZE; i++) {

for (int j = 0; j <GomokuProtocol.BOARD_SIZE; j++) {

this.buttons[i][j].addActionListener(this);

}

}

}

// create the panel to render black and whites

private void initializeGamePanel(final JPanel parent) {

this.buttons = new JButton[GomokuProtocol.BOARD_SIZE][GomokuProtocol.BOARD_SIZE];

parent.setLayout(new GridLayout(GomokuProtocol.BOARD_SIZE, GomokuProtocol.BOARD_SIZE, 0, 0));

for (int i = 0; i <GomokuProtocol.BOARD_SIZE; i++) {

for (int j = 0; j <GomokuProtocol.BOARD_SIZE; j++) {

finalJButton button = new JButton();

button.setPreferredSize(new Dimension(32, 32));

button.setBorder(BorderFactory.createLineBorder(Color.BLUE));

button.setOpaque(true);

button.setEnabled(false);

parent.add(button);

buttons[i][j] = button;

}

}

}

private void initializeControlPanel(final JPanel parent) {

this.chatArea = new JTextArea(20, 40);

this.inputField = new JTextField();

this.chatButton = new JButton(“Send”);

this.giveupButton = new JButton(“Give up”);

this.closeButton = new JButton(“Close”);

this.renameButton = new JButton(“Rename”);

this.resetButton = new JButton(“Reset”);

this.chatArea.setEditable(false);

// create the control buttons panel

JPaneltopPane = new JPanel();

topPane.add(this.renameButton);

topPane.add(this.resetButton);

topPane.add(this.giveupButton);

topPane.add(this.closeButton);

JPanelbottomPane = new JPanel();

bottomPane.setLayout(new BorderLayout());

bottomPane.add(this.inputField, BorderLayout.CENTER);

bottomPane.add(this.chatButton, BorderLayout.EAST);

// put them into the panel

parent.setLayout(new BorderLayout());

parent.add(topPane, BorderLayout.NORTH);

parent.add(new JScrollPane(this.chatArea), BorderLayout.CENTER);

parent.add(bottomPane, BorderLayout.SOUTH);

}

@Override

public void actionPerformed(ActionEvent e) {

final Object source = e.getSource();

if (source == this.giveupButton) {

this.send(GomokuProtocol.generateGiveupMessage());

return;

}

if (source == this.resetButton) {

this.send(GomokuProtocol.generateResetMessage());

return;

}

if (source == this.closeButton) {

this.send(GomokuProtocol.MESSAGE_CLOSE);

return;

}

if (source == this.chatButton) {

// send the chat message to the other side

final String message = inputField.getText().trim();

this.inputField.setText(“”);

if (message.length() > 0) {

final String content = GomokuProtocol.generateChatMessage(this.name, message);

this.send(content);

}

return;

}

if (source == this.renameButton) {

// ask the user to enter the new name

String newName = JOptionPane.showInputDialog(frame, “Enter your new name:”);

if (newName != null) {

// send the new name to the server

newName = newName.trim();

if (newName.length() > 0) {

final String content = GomokuProtocol.generateChangeNameMessage(this.name, newName);

this.send(content);

}

}

return;

}

// otherwise, the user tries to put one chess onto the board.

// simply forward this message to the server, the server

// will decide if this client can put the chess there.

for (int i = 0; i <GomokuProtocol.BOARD_SIZE; i++) {

for (int j = 0; j <GomokuProtocol.BOARD_SIZE; j++) {

if (source == this.buttons[i][j]) {

// black or white is ignored here, it’s safe for the server

// to decide.

final String content = GomokuProtocol.generatePlayMessage(false, i, j);

send(content);

return;

}

}

}

}

public void send(final String message) {

try {

final String content = message + “\n”;

this.out.write(content);

this.out.flush();

} catch (final IOException e) {

this.close(“The server has disconnected!”);

}

}

@Override

public void run() {

try {

while (true) {

// read the message from the server until the

// server close the game by terminating the stream.

final String message = this.in.readLine();

if (message == null) {

throw new IOException(“The server has disconnected!”);

}

process(message);

}

} catch (final IOException e) {

close(e.getMessage());

}

}

private void onMessage(final String message) {

SwingUtilities.invokeLater(() -> {

// display the message on the text area

this.chatArea.append(message);

this.chatArea.append(“\n”);

});

}

private void process(final String message) {

if (GomokuProtocol.isChangeNameMessage(message)) {

// change the name of this client

final String[] details = GomokuProtocol.getChangeNameDetail(message);

onMessage(String.format(“[%s] change name to [%s]”, details[0], details[1]));

if (details[0].equals(this.name)) {

this.name = details[1];

this.frame.setTitle(“Hello, ” + this.name + “!”);

}

return;

}

if (GomokuProtocol.isChatMessage(message)) {

final String[] details = GomokuProtocol.getChatDetail(message);

onMessage(String.format(“[%s] says: %s”, details[0], details[1]));

return;

}

if (GomokuProtocol.MESSAGE_CLOSE.equals(message)) {

this.close(“The game session is closed!”);

return;

}

if (GomokuProtocol.isPlayMessage(message)) {

// render this new chess on the board

finalint[] details = GomokuProtocol.getPlayDetail(message);

draw(details[0] == 1, details[1], details[2]);

return;

}

if (GomokuProtocol.isResetMessage(message)) {

onMessage(“The game is reset.”);

reset(true, true);

return;

}

if (GomokuProtocol.isGiveupMessage(message)) {

onMessage(“Someone has given up…”);

reset(false, false);

return;

}

// check the game result

if (GomokuProtocol.isWinMessage(message)) {

JOptionPane.showMessageDialog(frame, “You win!”);

onMessage(“You win! Click Reset to play again.”);

reset(false, false);

return;

}

if (GomokuProtocol.isLoseMessage(message)) {

JOptionPane.showMessageDialog(frame, “You Lose!”);

onMessage(“You Lose! Click Reset to play again.”);

reset(false, false);

return;

}

if (GomokuProtocol.MESSAGE_DRAW_GAME.equals(message)) {

JOptionPane.showMessageDialog(frame, “Draw Game!”);

onMessage(“Draw Game! Click Reset to play again.”);

reset(false, false);

return;

}

}

private void draw(booleanisBlack, int row, int col) {

// put a chess at the given position and disable

// the button so the user will not put it here again.

SwingUtilities.invokeLater(() -> {

JButton button = this.buttons

[row]

[col];

button.setEnabled(false);

button.setBackground(isBlack ? Color.BLACK :Color.WHITE);

});

}

private void reset(final boolean enabled, final booleanresetColor) {

SwingUtilities.invokeLater(() -> {

// reset the game and a new game will be started.

for (int i = 0; i <GomokuProtocol.BOARD_SIZE; i++) {

for (int j = 0; j <GomokuProtocol.BOARD_SIZE; j++) {

JButton button = this.buttons[i][j];

button.setEnabled(enabled);

if (resetColor) {

button.setBackground(Color.GRAY);

}

}

}

});

}

private void close(final String message) {

// display the message to the user and close the game

JOptionPane.showMessageDialog(frame, message);

System.exit(0);

}

} 

GomokuProtocol.java

public class GomokuProtocol {

public static final int DEFAULT_PORT = 23120;

public static final int BOARD_SIZE = 15;

private static final String SEPARATOR = “\0”;

private static final String MESSAGE_PLAY = SEPARATOR + “/play”;

private static final String MESSAGE_SET_BLACK = SEPARATOR + “/black”;

private static final String MESSAGE_SET_WHITE = SEPARATOR + “/white”;

private static final String MESSAGE_WIN = SEPARATOR + “/win”;

private static final String MESSAGE_LOSE = SEPARATOR + “/lose”;

private static final String MESSAGE_RESET = SEPARATOR + “/reset”;

private static final String MESSAGE_GIVEUP = SEPARATOR + “/giveup”;

private static final String MESSAGE_CHAT = SEPARATOR + “/chat”;

private static final String MESSAGE_CHANGE_NAME = SEPARATOR + “/nick”;

public static final String MESSAGE_CLOSE = SEPARATOR + “/close”;

public static final String MESSAGE_DRAW_GAME = SEPARATOR + “/draw”;

public static String generateChatMessage(String sender, String chat) {

StringBuildersb = new StringBuilder();

sb.append(MESSAGE_CHAT).append(SEPARATOR).append(sender)

.append(SEPARATOR).append(chat);

returnsb.toString();

}

public static booleanisChatMessage(String msg) {

returnmsg.startsWith(MESSAGE_CHAT);

}

public static String[] getChatDetail(String msg) {

if (isChatMessage(msg)) {

String[] tokens = msg.split(SEPARATOR);

if (tokens.length>= 4) {

return new String[]{tokens[2], tokens[3]};

}

}

return null;

}

public static String generatePlayMessage(booleanisBlack, int row, int col) {

StringBuildersb = new StringBuilder();

sb.append(MESSAGE_PLAY).append(SEPARATOR).append(isBlack ? 1: 0)

.append(SEPARATOR).append(row)

.append(SEPARATOR).append(col);

returnsb.toString();

}

public static booleanisPlayMessage(String msg) {

returnmsg.startsWith(MESSAGE_PLAY);

}

public static int[] getPlayDetail(String msg) {

if (isPlayMessage(msg)) {

String[] tokens = msg.split(SEPARATOR);

if (tokens.length>= 5) {

return new int[]{

Integer.parseInt(tokens[2]),

Integer.parseInt(tokens[3]),

Integer.parseInt(tokens[4])};

}

}

return null;

}

public static String generateChangeNameMessage(String oldName, String newName) {

StringBuildersb = new StringBuilder();

sb.append(MESSAGE_CHANGE_NAME).append(SEPARATOR).append(oldName)

.append(SEPARATOR).append(newName);

returnsb.toString();

}

public static booleanisChangeNameMessage(String msg) {

returnmsg.startsWith(MESSAGE_CHANGE_NAME);

}

public static String[] getChangeNameDetail(String msg) {

if (isChangeNameMessage(msg)) {

String[] tokens = msg.split(SEPARATOR);

if (tokens.length> 3) {

return new String[]{tokens[2], tokens[3]};

}

}

return null;

}

public static String generateSetBlackColorMessage() {

return MESSAGE_SET_BLACK;

}

public static booleanisSetBlackColorMessage(String msg) {

returnmsg.startsWith(MESSAGE_SET_BLACK);

}

public static String generateSetWhiteColorMessage() {

return MESSAGE_SET_WHITE;

}

public static booleanisSetWhiteColorMessage(String msg) {

returnmsg.startsWith(MESSAGE_SET_WHITE);

}

public static String generateWinMessage() {

return MESSAGE_WIN;

}

public static booleanisWinMessage(String msg) {

returnmsg.startsWith(MESSAGE_WIN);

}

public static String generateLoseMessage() {

return MESSAGE_LOSE;

}

public static booleanisLoseMessage(String msg) {

returnmsg.startsWith(MESSAGE_LOSE);

}

public static String generateResetMessage() {

return MESSAGE_RESET;

}

public static booleanisResetMessage(String msg) {

returnmsg.startsWith(MESSAGE_RESET);

}

public static String generateGiveupMessage() {

return MESSAGE_GIVEUP;

}

public static booleanisGiveupMessage(String msg) {

returnmsg.startsWith(MESSAGE_GIVEUP);

}

public static void main(String[] args) {

// example how to generate a message and how to parse it

String msg = generatePlayMessage(true, 10, 4);

if (isPlayMessage(msg)) {

int[] detail = getPlayDetail(msg);

// black is 1 and white is 0

System.out.println(“color is ” + detail[0]);

System.out.println(“row is ” + detail[1]);

System.out.println(“col is ” + detail[2]);

}

}

} 

Server.java 

importjava.io.BufferedReader;

importjava.io.IOException;

importjava.io.InputStreamReader;

importjava.io.OutputStreamWriter;

importjava.net.ServerSocket;

importjava.net.Socket;

importjava.util.concurrent.atomic.AtomicBoolean;

importjava.util.concurrent.atomic.AtomicLong;

/**

* The first parameter of the program is the port this server is listening on.

* If no port is specified, {@code GomokuProtocol#DEFAULT_PORT} is used.

*

*/

public class Server {

public static void main(String[] args) {

int port = GomokuProtocol.DEFAULT_PORT;

if (args.length>= 1) {

port = Integer.parseInt(args[0]);

}

try {

new Server(port).start();

} catch (final IOException e) {

System.out.println(“[server] The server is closed.”);

}

}

private final int port;

private final ServerSocketss;

private final AtomicLong count;

public Server(final int port) throws IOException {

this.port = port;

this.count = new AtomicLong(0);

// create a server socket on the given port

this.ss = new ServerSocket(port);

this.ss.setReuseAddress(true);

System.out.println(“[server] waiting on port ” + this.port);

}

/**

* Accept every two of clients and pair them together for one game.

*/

public void start() {

while (true) {

try {

final Socket c1 = this.ss.accept();

final Socket c2 = this.ss.accept();

// create a new game thread to handle this pair of clients.

final Game game = new Game(c1, c2);

game.start();

} catch (final IOException e) {

System.out.println(“[server] could not process clients: ” + e.getMessage());

}

}

}

// to describe the color for the players

privateenumChessColor {

BLACK { ChessColor opposite() { return WHITE; } },

WHITE { ChessColor opposite() { return BLACK; } },

EMPTY { ChessColor opposite() { return null; } };

abstractChessColor opposite();

}

// hold the information of one client

private class Client implements Runnable {

private final Game game;  // reference to the game

privateChessColor color;

private final Socket sk;

private final BufferedReader in;  // to read the input from the client

private final OutputStreamWriter out; // to send message to the client

protected String name;

public Client(final Game game, final Socket sk) throws IOException {

this.game = game;

this.sk = sk;

this.name = String.format(“anonymous-%d”, count.getAndIncrement());

// create the input and output stream to communicate with the client

this.in = new BufferedReader(new InputStreamReader(this.sk.getInputStream()));

this.out = new OutputStreamWriter(this.sk.getOutputStream());

// send the default name to the client

String message = GomokuProtocol.generateChangeNameMessage(“”, this.name);

this.send(message);

}

@Override

public void run() {

try {

// continue process the input from the client until the game

// is closed.

while (!game.terminated.get()) {

final String line = this.in.readLine();

if (line == null) {

if (game.terminated.get()) {

break;

}

throw new IOException(“disconnected abnormally”);

}

process(line);

}

} catch (final IOException e) {

// this client has communication errors,

// close this game.

System.out.printf(“[%s] %s\n”, this.name, e.getMessage());

game.close();

}

}

private void process(final String message) throws IOException {

if (GomokuProtocol.isChangeNameMessage(message)) {

// change the name of the client

final String[] details = GomokuProtocol.getChangeNameDetail(message);

if (details != null) {

System.out.printf(“[%s] change name to [%s]\n”, this.name, details[1]);

this.name = details[1];

game.broadcast(message); // send the new name back to the client

}

return;

}

if (GomokuProtocol.isChatMessage(message)) {

final String[] details = GomokuProtocol.getChatDetail(message);

if (details != null) {

System.out.printf(“[%s] says: %s\n”, this.name, details[1]);

// forward this message to both clients so they can display it.

game.broadcast(GomokuProtocol.generateChatMessage(this.name, details[1]));

}

return;

}

if (GomokuProtocol.MESSAGE_CLOSE.equals(message)) {

// close the game

System.out.printf(“[%s] closes the game\n”, this.name);

game.broadcast(message);

game.terminated.set(true);

return;

}

if (GomokuProtocol.isGiveupMessage(message)) {

System.out.printf(“[%s] gives up\n”, this.name);

game.broadcast(message);

return;

}

if (GomokuProtocol.isResetMessage(message)) {

// reset the game will start a new game immediately

System.out.printf(“[%s] resets the game\n”, this.name);

game.reset();

return;

}

if (GomokuProtocol.isPlayMessage(message)) {

// the client tries to put a chess

finalint[] details = GomokuProtocol.getPlayDetail(message);

if (details != null) {

draw(details[1], details[2]);

}

}

}

private void draw(int row, int col) {

System.out.printf(“[%s] tries to put %s at (%d, %d)\n”, this.name, color, row, col);

// make sure if this draw is allowed

if (row < 0 || row >= GomokuProtocol.BOARD_SIZE || col < 0 || col >= GomokuProtocol.BOARD_SIZE) {

return; // invalid range

}

if (game.board

[row]

[col] != ChessColor.EMPTY) {

return; // this location is already occupied.

}

if (this.color != game.turn) {

return; // it’s not the user’s turn yet.

}

game.update(this, row, col);

}

public void send(final String message) {

try {

final String content = message + “\n”;

this.out.write(content);

this.out.flush();

} catch (final IOException e) {

// if we cannot send the message, simply close the game session.

System.out.printf(“[%s] %s\n”, this.name, e.getMessage());

if (!game.terminated.get()) {

game.close();

}

}

}

}

private class Game {

protected final AtomicBoolean terminated = new AtomicBoolean(false);

private final Client[] clients;

private final ChessColor[][] board;

privateChessColor turn; // current color in turn

public Game(final Socket c1, final Socket c2) throws IOException {

this.clients = new Client[] { new Client(this, c1), new Client(this, c2) };

System.out.printf(“[server] new game for [%s] & [%s]\n”, this.clients[0].name, this.clients[1].name);

// initialize the empty board

this.board = new ChessColor[GomokuProtocol.BOARD_SIZE][GomokuProtocol.BOARD_SIZE];

}

public void start() {

// close a new thread to handle each client.

for (final Client client : this.clients) {

new Thread(client).start();

}

// reset the game to let the players start to play

this.reset();

}

public void broadcast(final String message) {

for (final Client client : this.clients) {

client.send(message);

}

}

public void close() {

this.terminated.set(true);

// send close message to both clients and close the socket of  clients.

for (final Client client : this.clients) {

try {

client.send(GomokuProtocol.MESSAGE_CLOSE);

client.sk.close();

} catch (final IOException e) {

// ignored intentionally.

}

}

}

/**

* Put the color in current turn to the given location.

* Then switch the color in turn.

* This function also sends feedback to the client

*/

public synchronized void update(final Client client, final int row, final int col) {

board

[row]

[col] = turn;

final String message = GomokuProtocol.generatePlayMessage(turn == ChessColor.BLACK, row, col);

this.broadcast(message);

// check if this player has win the game

if (win(turn)) {

for (Client c : this.clients) {

if (c == client) {

c.send(GomokuProtocol.generateWinMessage());

} else {

c.send(GomokuProtocol.generateLoseMessage());

}

}

turn = ChessColor.EMPTY; // no more chess is allowed in this game

return;

}

if (full()) { // draw game, no more space on the board.

for (Client c : this.clients) {

c.send(GomokuProtocol.MESSAGE_DRAW_GAME);

}

turn = ChessColor.EMPTY; // no more chess is allowed in this game

return;

}

turn = turn.opposite(); // switch the color in turn

}

privateboolean win(ChessColor color) {

// check four directions on each location

int[][] directions = { {-1, 1}, {0, 1}, {1, 1}, {1, 0} };

for (int i = 0; i <GomokuProtocol.BOARD_SIZE; i++) { // check each row

for (int j = 0; j <GomokuProtocol.BOARD_SIZE; j++) {

if (board[i][j] != color) {

continue;

}

for (int[] dir : directions) {

// check if in this direction, the next four chess pieces

// are of the same color.

int di = dir[0], dj = dir[1];

boolean matched = true;

for (int s = 1; s < 5; s ++) {

intni = i + s * di, nj = j + s * dj;

if (ni< 0 || ni>= GomokuProtocol.BOARD_SIZE ||

nj< 0 || nj>= GomokuProtocol.BOARD_SIZE ||

board[ni][nj] != color) {

matched = false;

break;

}

}

if (matched) {

return true;

}

}

}

}

return false;

}

privateboolean full() {

// check if there is no more empty cells on the board

for (int i = 0; i <GomokuProtocol.BOARD_SIZE; i++) {

for (int j = 0; j <GomokuProtocol.BOARD_SIZE; j++) {

if (board[i][j] == ChessColor.EMPTY) {

return false;

}

}

}

return true;

}

/**

* Reset and start the game.

*/

public synchronized void reset() {

for (int i = 0; i <GomokuProtocol.BOARD_SIZE; i++) {

for (int j = 0; j <GomokuProtocol.BOARD_SIZE; j++) {

this.board[i][j] = ChessColor.EMPTY; // empty

}

}

this.broadcast(GomokuProtocol.generateResetMessage());

// initialize the color of the two players

this.clients[0].color = (Math.random() < 0.5 ? ChessColor.BLACK :ChessColor.WHITE);

this.clients[1].color = this.clients[0].color.opposite();

this.turn = ChessColor.BLACK; // let black go first

// send the color to the client

for (Client client : this.clients) {

final String message = String.format(“You play %s!”,

client.color == ChessColor.WHITE ? “white” : “black, you go first!”);

client.send(GomokuProtocol.generateChatMessage(“server”, message));

}

}

}

}