-Model, which shall be used as a base for the rule engine
-ModelOptions, which shall be used for containing the options for creating a model
-Direction, which shall be used to simplify the winning condition check.
-GameStatus, which shall be used as the return result after a winning condition check.
-MoveStatus, which shall be used as the return result after a player makes a move.
-Player, which shall be used for containing the information about the players.
We shall consider that the Tic Tac Toe game takes place on a n x m board having 2 or more players, where n can be equal m.
For the model we shall define the following the attributes:
-Number of players
-The width and height of the game table
-The number of symbols needed for a player to win
The model must allow the following operations:
-Put symbol, which shall allow a player to insert his symbol at a certain position on the table if that position was not occupied by another player.
-Winning condition check, which needs to verify the table and signal if a player managed to have enough symbols placed in a line/diagonal configuration.
The implementation for the Model is:
package tictactoe.ruleengine; import java.awt.Point; /** * * @author bogdan */ public class Model { /** * A reference to the options used for creating the model */ private ModelOptions options; /** * The game table */ private int[][] table; /** * Creates new Model object * @param options The options used for creating the model */ public Model(ModelOptions options) { this.options = options; this.table = new int[options.getHeight()][options.getWidth()]; clearTable(); } /** * Initializes the game table with the default values */ private void clearTable() { int i,j; int height = options.getHeight(); int width = options.getWidth(); for(i = 0; i<height; i++) { for(j = 0; j<width; j++) { table[i][j] = Player.NO_PLAYER; } } } /** * Checks if the coordinates are within the game table bounds * @param x The X coordinate of the point * @param y The Y coordinate of the point * @return True if the point is within bounds, false otherwise */ private boolean isWithinBounds(int x, int y) { return ((x>=0) && (x<options.getHeight()) && (y>=0) && (y<options.getWidth())); } /** * Verifies if P(i,j) is situated on a line or diagonal with enough symbols * in order for the player to win. * @param i The X coordinate of the point * @param j The Y coordinate of the point * @param symbol The type of symbol who is counted * @return True - if a player won * False - No one one. */ private boolean checkSquare(int i, int j, int symbol) { int k; int counter; int x, y; boolean hasWon = false; Direction dir; int symbolsToWin = options.getSymbolsToWin(); //Checks in every direction if there are enough symbols in order //for a player to win for(k = Direction.FIRST_SYMBOL; k< Direction.LAST_SYMBOL; k++) { counter = 1; x = i; y = j; while(counter!=symbolsToWin) { dir = Direction.getDirection(k); x += dir.getXOffset(); y += dir.getYOffset(); if((isWithinBounds(x, y)==true)&& (table[x][y]==symbol) && (counter<symbolsToWin)) { counter++; } else { break; } } if(counter==symbolsToWin) { hasWon = true; break; } } return hasWon; } /** * Verifies each square to see if any player won * @return * GameStatus.GAME_OVER - The game is over, no one won (if there no more * free squares) * GameStatus.GAME_PENDING - No one won yet * Otherwise returns a GameStatus object initialized with the symbol of * the winning player. */ public GameStatus checkWinningConditions() { int i,j; int result = Player.NO_CHANGE; boolean hasWon; int emptySymbolCounter = 0; int height = options.getHeight(); int width = options.getWidth(); //Checks for every square if winning conditions are achieved for(i=0; (i<height && result==-1); i++) { for(j=0; (j<width && result==-1); j++) { if(table[i][j]!=Player.NO_PLAYER) { hasWon = checkSquare(i,j,table[i][j]); if(hasWon==true) { result = table[i][j]; } } else { emptySymbolCounter++; } } } if((result==-1) && (emptySymbolCounter==0)) { result = Player.NO_PLAYER; } return new GameStatus(result); } /** * Inserts a symbol on the game table * @param x The X coordinate at which the symbol is inserted * @param y The Y coordinate at which the symbol is inserted * @param symbol The value of the symbol who will be inserted * @return * MoveStatus.IsValid - if the insertion is valid * MoveStatus.AlreadyOccupied - */ public MoveStatus putSymbol(int x, int y, int symbol) { int i; boolean found = false; MoveStatus status =MoveStatus.None; int[] playerSymbols = Player.getAllPlayerSymbols(); for(i = 0; i< playerSymbols.length; i++) { if(symbol == playerSymbols[i]) { if(isWithinBounds(x, y)==true) { if(table[x][y]==Player.NO_PLAYER) { table[x][y] = symbol; status = MoveStatus.Valid; } else { status = MoveStatus.AlreadyOccupied; } } else { status = MoveStatus.OutOfBounds; } found=true; break; } } if(found==false) { status = MoveStatus.UnknownSymbol; } return status; } /** * Returns a string representation of the table * @return A string representation of the table */ @Override public String toString() { int i,j; int height = options.getHeight(); int width = options.getWidth(); StringBuilder builder = new StringBuilder(); for(i = 0; i< height; i++) { for(j = 0; j<width; j++) { builder.append(table[i][j]); builder.append(" "); } builder.append("\n"); } return builder.toString(); } /** * Returns the symbol at the point P(i,j) * @param i The X coordinate of the point * @param j The Y coordinate of the point * @return The symbol at the point P(i,j) if i and j are within the table * bounds. Otherwise returns Integer.MAX_VALUE; */ public int getSymbolAt(int i, int j) throws IllegalArgumentException { if(isWithinBounds(i,j)==true) { return table[i][j]; } else { return Integer.MAX_VALUE; } } }The implementation for the ModelOptions is:
package tictactoe; import java.util.ArrayList; /** * * @author dystopiancode */ public class ModelOptions { /** * A reference to an ArrayList containing the players. */ private ArrayList<Player> players; /** * The width of the game table */ private int width; /** * The height of the game table */ private int height; /** * The number of symbols that need to be in a line or a row in order for * a player to win */ private int symbolsToWin; /** * Creates a new ModelOptions object * @param players A reference to an ArrayList containing the players. * @param height The height of the game table * @param width The width of the game table * @param symbolsToWin The number of symbols that need to be in a line or * a row in order for a player to win * @throws IllegalArgumentException */ public ModelOptions(ArrayList<Player> players, int height, int width, int symbolsToWin) throws IllegalArgumentException { if((symbolsToWin<=height) && (symbolsToWin<=width)) { this.players = players; this.width = width; this.height = height; this.symbolsToWin = symbolsToWin; } else { throw new IllegalArgumentException("Size of table too small"); } } /** * Returns a reference to an ArrayList containing the players * @return A reference to the players Arraylist */ public ArrayList<Player> getPlayers() { return players; } /** * Returns the number of symbols needed for winning * @return The number of symbols needed for winning */ public int getSymbolToWin() { return symbolsToWin; } /** * Returns the height of the game table * @return The height of the game table */ public int getHeight() { return height; } /** * Returns the width of the game table * @return The width of the game table */ public int getWidth() { return width; } }The implementation for Direction is:
package tictactoe; /** * @author dystopiancode */ public enum Direction { NONE(0,0,0), NORTH(1,-1,0), SOUTH(2,1,0), EAST(3,0,-1), WEST(4,0,1), NORTH_EAST(5,-1,-1), NORTH_WEST(6,-1,1), SOUTH_EAST(7,1,-1), SOUTH_WEST(8,1,1); /** * The symbol for no direction */ public static final int NONE_SYMBOL = 0; /** * The symbol for the first direction */ public static final int FIRST_SYMBOL = 1; /** * The symbol for the last direction */ public static final int LAST_SYMBOL = 8; /** * The symbol used for representing a direction */ private int _symbol; /** * The x axis offset introduced by changing the location according to * the direction. */ private int _xOffset; /** * The y axis offset introduced by changing the location according to * the direction. */ private int _yOffset; /** * Creates a Direction object * @param symbol The symbol used for representing the direction * @param xOffset The x axis offset introduced by changing the location * according to the direction. * @param yOffset The y axis offset introduced by changing the location * according to the direction. */ private Direction(int symbol, int xOffset, int yOffset) { _symbol = symbol; _xOffset = xOffset; _yOffset = yOffset; } /** * Return the x axis offset * @return The x axis offset */ public int getXOffset() { return _xOffset; } /** * Return the y axis offset * @return The y axis offset */ public int getYOffset() { return _yOffset; } /** * Return the symbol of the direction * @return The symbol */ public int getSymbol() { return _symbol; } /** * Creates a direction according to a symbol * @param symbol The symbol used for creating the direction * @return The direction who has the indicated symbol */ public static Direction getDirection(int symbol) { switch(symbol) { case 1: return Direction.NORTH; case 2: return Direction.SOUTH; case 3: return Direction.EAST; case 4: return Direction.WEST; case 5: return Direction.NORTH_EAST; case 6: return Direction.NORTH_WEST; case 7: return Direction.SOUTH_EAST; case 8: return Direction.SOUTH_WEST; default: return Direction.NONE; } } }The implementation for GameStatus is:
package tictactoe; /** * * @author dystopiancode */ public class GameStatus { /** * The game is over, no on won */ public static GameStatus GAME_OVER = new GameStatus(Player.NO_PLAYER); /** * The game is still running */ public static GameStatus GAME_PENDING = new GameStatus(Player.NO_CHANGE); /** * The symbol encapsulated by the GameStatus */ private int symbol; /** * Creates a new GameStatus object * @param symbol The symbol representing the status (usually the symbol * of the player who won the game). */ public GameStatus(int symbol) { this.symbol = symbol; } /** * Returns the symbol of the winning player, if any. * @return The symbol of the winning player */ public int getSymbol() { return symbol; } /** * Verifies if the GameStatus objects are equal * @param status The object who is going to be compared with the current * object * @return True if the object are equal, false otherwise */ public boolean equals(GameStatus status) { return (symbol==status.symbol); } }The implementation for MoveStatus is:
package tictactoe; /** * * @author dystopiancode */ public enum MoveStatus { None(0), Valid(1), AlreadyOccupied(2), OutOfBounds(3), UnknownSymbol(4); /** * A integer representing the code of a status resulted after a player * made a move. */ private int statusCode; /** * Private constructor for the enumeration * @param statusCode The code of the current status */ private MoveStatus(int statusCode) { this.statusCode = statusCode; } /** * Checks if two MoveStatus objects are equal * @param status The object to which the current object will be compared * @return True if the objects are equal, otherwise false */ public boolean equals(MoveStatus status) { return (this.statusCode == status.statusCode); } }The implementation for Player is:
package tictactoe; /** * * @author dystopiancode */ public class Player { /** * */ public static final int NO_CHANGE = -1; public static final int NO_PLAYER = 0; /** * Static counter-like variable used for assigning a symbol for each new * player. */ private static int symbolCounter = 1; /** * The name of the player */ private String name; /** * The symbol used for representing a a player on the game table */ private int symbol; /** * Createa a new Player object * @param name The name of the player */ public Player(String name) { this.name = name; symbol = symbolCounter; symbolCounter++; } /** * Returns the player's symbol * @return The Player's symbol */ public int getSymbol() { return symbol; } /** * Returns the player's name * @return The player's name */ public String getName() { return name; } /** * Creates a vector containing all the symbols used by the existing players * @return A vector containing all the symbols used by the existing players */ public static int[] getAllPlayerSymbols() { int[] symbols = new int[symbolCounter]; for(int i = 0; i<symbolCounter; i++) { symbols[i] = i; } return symbols; } }
No comments:
Post a Comment
Got a question regarding something in the article? Leave me a comment and I will get back at you as soon as I can!