Salta el contingut
 

Exercici: TicTacToe

Autor: Joan Puigcerver Ibáñez

Correu electrònic: j.puigcerveribanez@edu.gva.es

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

Objectius

Implementar un programa amb estructura client-servidor amb un protocol complex utilitzant l'enviament d'objectes.

Introducció

Tots els exercicis han d'estar situats en el package corresponent.

  • Package: ud3.exericises.tictactoe

Acaba la implementació del joc 3 en ratlla basant-se amb el codi font proporcionat.

Exercici: TicTacToe

Models

Board.java
package ud3.exercises.tictactoe.models;

public class Board {
    private final int[][] board;
    private int moveCount;
    private int winner;
    private boolean finished;

    public Board() {
        this.board = new int[3][3];
        this.moveCount = 0;
        this.winner = 0;
        this.finished = false;
    }

    public boolean isValid(BoardChoice boardChoice){
        return this.board[boardChoice.getX()][boardChoice.getY()] == 0;
    }

    public void updateBoard(BoardChoice boardChoice){
        int x = boardChoice.getX();
        int y = boardChoice.getY();
        int player = boardChoice.getPlayer();
        this.board[x][y] = player;
    }

    public void render(){
        for (int y = 2; y >= 0; y--) {
            System.out.println("-------------");
            for (int x = 0; x <= 2; x++) {
                int v = board[x][y];
                System.out.printf("| %s ", v > 0 ? v : " ");
            }
            System.out.println("|");
        }
        System.out.println("-------------");
    }

    public void addChoice(BoardChoice boardChoice){
        int x = boardChoice.getX();
        int y = boardChoice.getY();
        int player = boardChoice.getPlayer();
        this.board[x][y] = player;
        moveCount++;

        checkRow(y, player);
        if (!finished) checkColumn(x, player);
        if (!finished) checkDiagonal(x, y, player);
        if (!finished) checkAntiDiagonal(x, y, player);
        if (!finished) finished = moveCount == 9;
    }

    public void checkRow(int y, int player){
        for (int x = 0; x < 3; x++){
            if (board[x][y] != player) return;
        }

        this.winner = player;
        this.finished = true;
    }

    public void checkColumn(int x, int player){
        for (int y = 0; y < 3; y++){
            if (board[x][y] != player) return;
        }

        this.winner = player;
        this.finished = true;
    }

    public void checkDiagonal(int x, int y, int player){
        if (x != y)
            return;

        for (int i = 0; i < 3; i++){
            if (board[i][i] != player) return;
        }

        this.winner = player;
        this.finished = true;
    }

    public void checkAntiDiagonal(int x, int y, int player){
        if (x + y != 2)
            return;

        for (int i = 0; i < 3; i++){
            if (board[i][2 - i] != player) return;
        }

        this.winner = player;
        this.finished = true;
    }

    public boolean isFinished() {
        return finished;
    }
    public int getWinner() {
        return winner;
    }
}

Model que representa el tauler de joc del 3 en ratlla.

BoardChoice.java
package ud3.exercises.tictactoe.models;

import java.io.Serializable;

public class BoardChoice implements Serializable {
    private final int x;
    private final int y;
    private int player;

    public BoardChoice(int x, int y, int player) {
        this.x = x;
        this.y = y;
        this.player = player;
    }
    public BoardChoice(int x, int y) {
        this(x, y, 0);
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getPlayer() {
        return player;
    }

    public void setPlayer(int player) {
        this.player = player;
    }
}

Model que representa una elecció d'una jugada.

TicTacToeMessage.java
package ud3.exercises.tictactoe.models;

import java.io.Serializable;

public class TicTacToeMessage implements Serializable {
    private TicTacToeMessageType type;
    private String message;
    private Object object;

    public TicTacToeMessage(TicTacToeMessageType type, String message) {
        this.type = type;
        this.message = message;
        this.object = null;
    }

    public TicTacToeMessage(TicTacToeMessageType type) {
        this(type, "");
    }

    public TicTacToeMessageType getType() {
        return type;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}

Model que representa un missatge utilitzat en la comunicació entre el servidor i el client.

TicTacToeMessageType.java
package ud3.exercises.tictactoe.models;

import java.io.Serializable;

public enum TicTacToeMessageType implements Serializable {
    INFO,
    ERROR,
    START_GAME,
    INVALID_CHOICE,
    START_TURN,
    WAIT_TURN,
    POST,
    UPDATE_BOARD,
    END_GAME
}

Enumeració que representa els tipus de missatges que es poden enviar.

  • INFO: El servidor envia un missatge d'informació als clients.
  • ERROR: El servidor envia un missatge d'error als clients.
  • START_GAME: El servidor indica als jugadors que la partida acaba de començar.
  • START_TURN: El servidor indica a un jugador que és el seu torn i que espera rebre la seua jugada.
  • WAIT_TURN: El servidor indica a un jugador que és el torn del contrincant i que cal esperar la seua jugada.
  • POST: El client indica al servidor quina és la jugada realitzada.
  • INVALID_CHOICE: El servidor indica a un jugador que la jugada que ha intentat fer no és correcta i que ha de tornar a triar una jugada.
  • UPDATE_BOARD: El servidor indica que el tauler ha segut actualitzat amb una nova jugada, indicada en el missatge.
  • END_GAME: El servidor indica que la partida ha finalitzat.

TicTacToeServer

El servidor és l'encarregat de gestionar les connexions dels jugadors i emparellar-los perquè juguen entre ells.

  • El servidor esperarà a que es connecten dos jugadors perquè comencen una partida entre ells.
  • Quan dos jugadors es connecten, llançarà un nou fil que gestionarà la partida entre els dos jugadors.

El codi font és el següent:

TicTacToeServer.java
package ud3.exercises.tictactoe.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class TicTacToeServer extends Thread {
    ServerSocket server;

    List<TicTacToeGame> games;
    TicTacToeGame nextGame;
    boolean running;

    public TicTacToeServer(int port) throws IOException {
        server = new ServerSocket(port);
        games = new ArrayList<>();
        nextGame = new TicTacToeGame(0);
        running = true;
    }

    public void close(){
        running = false;
        this.interrupt();
    }

    /**
     * TODO: Execució del servidor.
     * <p>
     * El servidor accepta les connexions dels clients.
     * Quan dos jugadors es connecten, els emparella per començar una partida.
     */
    @Override
    public void run() {
        while (running){
            try {
                // TODO
            } catch (IOException e) {
                System.err.println("Error while accepting new connection");
                System.err.println(e.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        try {
            Scanner scanner = new Scanner(System.in);
            TicTacToeServer server = new TicTacToeServer(1234);
            server.start();

            scanner.nextLine();

            server.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}
TicTacToeServerPlayerHandler.java
package ud3.exercises.tictactoe.server;

import ud3.exercises.tictactoe.models.BoardChoice;
import ud3.exercises.tictactoe.models.TicTacToeMessage;
import ud3.exercises.tictactoe.models.TicTacToeMessageType;

import java.io.*;
import java.net.Socket;

import static ud3.exercises.tictactoe.models.TicTacToeMessageType.*;
public class TicTacToeServerPlayerHandler {
    private final Socket socket;
    private final ObjectOutputStream out;
    private final ObjectInputStream in;

    public TicTacToeServerPlayerHandler(Socket socket) throws IOException {
        this.socket = socket;

        this.in = new ObjectInputStream(socket.getInputStream());
        this.out = new ObjectOutputStream(socket.getOutputStream());
    }

    public void sendMessage(TicTacToeMessageType type, String message) throws IOException {
        out.writeObject(new TicTacToeMessage(type, message));
    }
    public void sendMessage(TicTacToeMessageType type, String message, Object obj) throws IOException {
        TicTacToeMessage ticTacToeMessage = new TicTacToeMessage(type, message);
        ticTacToeMessage.setObject(obj);
        out.writeObject(ticTacToeMessage);
    }
    public void sendMessage(TicTacToeMessageType type) throws IOException {
        out.writeObject(new TicTacToeMessage(type));
    }

    public BoardChoice getChoice() throws IOException, ClassNotFoundException {
        TicTacToeMessage receivedMessage = (TicTacToeMessage) in.readObject();
        while (receivedMessage.getType() != POST){
            sendMessage(ERROR, "Expected a POST message.");
            receivedMessage = (TicTacToeMessage) in.readObject();
        }

        return (BoardChoice) receivedMessage.getObject();
    }

    public void close() throws IOException {
        this.socket.close();
    }
}

Com que és una aplicació amb múltiples clients, la gestió de cada client és independent en el seu propi fil d'execució.

TicTacToeGame

Aquest fil és l'encarregat de portar a terme la partida entre els dos jugadors connectats.

Internament, emmagatzemara el tauler Board amb l'estat de la partida actual. Preguntarà per torns als jugadors per un moviment, que anirà actualitzant en el seu tauler. A més, comunicarà a cada jugador els moviments perquè el client puga actualitzar el seu tauler.

La comunicació es gestionarà mitjançant objectes TicTacToeMessage, el qual té un TicTacToeMessageType, un missatge i un objecte.

TicTacToeGame.java
package ud3.exercises.tictactoe.server;

import ud3.exercises.tictactoe.models.Board;
import ud3.exercises.tictactoe.models.BoardChoice;
import ud3.exercises.tictactoe.models.TicTacToeMessageType;

import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

import static ud3.exercises.tictactoe.models.TicTacToeMessageType.*;

public class TicTacToeGame extends Thread {
    private final int id;
    private final List<TicTacToeServerPlayerHandler> players;

    private final Board board;
    private boolean finished;
    private int currentPlayer;

    public TicTacToeGame(int id) {
        this.id = id;
        this.players = new ArrayList<>();
        board = new Board();
        finished = false;
    }

    public void addPlayer(Socket client) throws IOException {
        if (players.size() < 2)
            players.add(new TicTacToeServerPlayerHandler(client));
    }

    public boolean enoughPlayers(){
        return players.size() >= 2;
    }

    public TicTacToeServerPlayerHandler getCurrentPlayer(){
        return players.get(currentPlayer);
    }

    public void sendGlobalMessage(TicTacToeMessageType type) throws IOException{
        for(TicTacToeServerPlayerHandler player : players){
            player.sendMessage(type);
        }
    }
    public void sendGlobalMessage(TicTacToeMessageType type, String message) throws IOException{
        for(TicTacToeServerPlayerHandler player : players){
            player.sendMessage(type, message);
        }
    }
    public void sendGlobalMessage(TicTacToeMessageType type, String message, Object obj) throws IOException{
        for(TicTacToeServerPlayerHandler player : players){
            player.sendMessage(type, message, obj);
        }
    }

    /**
     * TODO: Send the choice to all players, so they update the board.
     */
    public void updateClientBoards(BoardChoice choice) throws IOException {
    }

    public void changeTurn(){
        currentPlayer = (currentPlayer + 1) % players.size();
    }

    private void close() throws IOException {
        for(TicTacToeServerPlayerHandler player : players){
            player.close();
        }
    }

    /**
     * Server side of TicTacToe game.
     * <p>
     * TODO: The server should keep track of:
     * - Comunication with both connected players.
     * - The TicTacToe Board. It should be updated given
     *      the players choices.
     * - It's a turn based game.
     * - Winner/Loser or draw.
     */
    @Override
    public void run(){
        try {
            // TODO
        } catch (IOException | ClassNotFoundException e) {
            System.err.println(e.getMessage());
        }
    }
}

TicTacToeClient

El client es composarà d'un únic fil d'execució que ha de reaccionar als missatges enviats pel servidor i actuar en conseqüència, seguint el protocol.

TicTacToeClient.java
package ud3.exercises.tictactoe.client;

import ud3.exercises.tictactoe.models.Board;
import ud3.exercises.tictactoe.models.BoardChoice;
import ud3.exercises.tictactoe.models.TicTacToeMessage;
import ud3.exercises.tictactoe.models.TicTacToeMessageType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;

import static ud3.exercises.tictactoe.models.TicTacToeMessageType.*;

public class TicTacToeClient {
    private final Socket socket;
    private final Scanner scanner;
    private final ObjectOutputStream out;
    private final ObjectInputStream in;

    private Board board;
    private boolean finished;

    public TicTacToeClient(String host, int port) throws IOException {
        this.scanner = new Scanner(System.in);
        this.socket = new Socket(host, port);
        this.out = new ObjectOutputStream(socket.getOutputStream());
        this.in = new ObjectInputStream(socket.getInputStream());
        this.board = new Board();
        this.finished = false;
    }

    private TicTacToeMessage readMessage() throws IOException, ClassNotFoundException {
        return (TicTacToeMessage) in.readObject();
    }
    private void sendMessage(TicTacToeMessageType type, String message, Object obj) throws IOException {
        TicTacToeMessage ticTacToeMessage = new TicTacToeMessage(type, message);
        ticTacToeMessage.setObject(obj);
        out.writeObject(ticTacToeMessage);
    }

    /**
     * TODO: Ask for the position where the user wants to play.
     * Send the user's choice to the server.
     */
    private void playMove() throws IOException {
    }

    /**
     * Client-side TicTacToe game.
     * <p>
     * TODO: This method should recevie a TicTacToeMessage from the server and implement
     * the accions based on the message type.
     *
     */
    public void game() throws IOException, ClassNotFoundException {
    }

    public static void main(String[] args) {
        System.out.println("Connectant-se amb el servidor...");
        try {
            TicTacToeClient ticTacToeClient = new TicTacToeClient("localhost", 1234);
            ticTacToeClient.game();
        } catch (IOException | ClassNotFoundException e) {
            System.err.println(e.getMessage());
        }
    }
}