Android Open Source - it.alcacoop.fourinaline Game Model






From Project

Back to project page it.alcacoop.fourinaline.

License

The source code is released under:

GNU General Public License

If you think the Android project it.alcacoop.fourinaline listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * GameModel.java//  w ww .  j  a  v a2 s .c om
 *
 * Created: 2008/02/16
 *
 * Copyright (C) 2008 Julien Aubin
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.gojul.fourinaline.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

/**
 * The <code>GameModel</code> class contains the basic modelisation
 * of a 4 in a line game.<br/>
 * It supports a various number of lines, and a various number of cells.<br/>
 * It only supports two players. However it is optimized for the support of
 * alpha beta algorithm in order to improve the overall speed of an AI player. 
 * 
 * @author Julien Aubin
 */
public final class GameModel implements Serializable 
{

  /**
   * The serial version UID.
   */
  final static long serialVersionUID = 1;
  
  /**
   * The <code>CellCoord</code> class represents a class
   * of cell coordinates.
   * 
   * @author Julien Aubin
   */
  public final static class CellCoord implements Serializable
  {
    /**
     * The serial version UID.
     */
    final static long serialVersionUID = 1;    
    
    /**
     * The row index.
     */
    private int rowIndex;
    
    /**
     * The column index.
     */
    private int colIndex;
    
    /**
     * Constructor.
     * @param row the row index.
     * @param col the column index.
     */
    public CellCoord(final int row, final int col)
    {
      rowIndex = row;
      colIndex = col;
    }
    
    /**
     * Returns the row index.
     * @return the row index.
     */
    public int getRowIndex()
    {
      return rowIndex;
    }
    
    /**
     * Returns the column index.
     * @return the column index.
     */
    public int getColIndex()
    {
      return colIndex;
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj)
    {
      if (obj != null && obj instanceof CellCoord)
      {
        CellCoord test = (CellCoord) obj;
        
        return test.rowIndex == rowIndex && test.colIndex == colIndex;
      }
      else
        return false;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode()
    {      
      return rowIndex + colIndex;
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString()
    {      
      return new StringBuffer("(").append(rowIndex).append(", ").append(colIndex).append(")").toString();
    }
  }
  
  /**
   * The <code>GameModelException</code> class is the exception
   * for all the game model errors.
   * 
   * @author Julien Aubin
   *
   */
  public final static class GameModelException extends RuntimeException
  {
    /**
     * The serial version UID.
     */
    final static long serialVersionUID = 1;
    
    /**
     * Constructor.
     */
    GameModelException()
    {
      super();
    }
    
    /**
     * Constructor.
     * @param message the mesage to display.
     */
    GameModelException(final String message)
    {
      super(message);
    }
    
  }
  
  /**
   * The <code>PlayerMark</code> symbol contains the mark  
   * for a game player.
   * 
   * @author Julien Aubin
   */
  public final static class PlayerMark implements Serializable
  {
    /**
     * The serial version UID.
     */
    final static long serialVersionUID = 1;
    
    /**
     * The list of players.
     */
    private final static List<PlayerMark> players = new ArrayList<PlayerMark>();
    
    /**
     * The iterator upon the list of players.
     */
    private static Iterator<PlayerMark> itPlayers = null;
    
    /**
     * The mark value.
     */
    private int markValue;
    
    /**
     * Constructor.
     * @param val the value of the player mark.
     * @throws IllegalArgumentException if <code>val</code>
     * is smaller or equal to 0 or greater or equal to <code>Character.MAX_VALUE - 'A'</code>.
     */
    private PlayerMark(final int val) throws IllegalArgumentException
    {
      if (val <= Character.MIN_VALUE || val >= (Character.MAX_VALUE - 'A'))
      {
        throw new IllegalArgumentException();
      }
      
      players.add(this);
      
      markValue = val;
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj)
    {
      
      if (obj != null && obj instanceof PlayerMark)
        return ((PlayerMark) obj).markValue == markValue;
      else
        return false;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() 
    {
      return markValue;
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() 
    {      
      return String.valueOf(markValue);
    }
    
    /**
     * Return the unique key representation
     * of this mark.
     * @return the unique key representation
     * of this mark.
     */
    public String toUniqueKey()
    {
      char c = (char) ('A' + markValue - 1);
      return String.valueOf(c);
    }
    
    /**
     * Returns the next player.
     * @return the next player.
     */
    private final static PlayerMark getNextPlayer()
    {  
      /*
      if (itPlayers == null || !itPlayers.hasNext())
        itPlayers = players.iterator();
      */
      PlayerMark result = itPlayers.next();
      
      return result;
    }
    
    /**
     * Returns an iterator over the player mark list. This iterator
     * cannot modify the player mark list.
     * @return an iterator over the player mark list. This iterator
     * cannot modify the player mark list.
     */
    public final static Iterator<PlayerMark> getPlayerIterator()
    {
      return Collections.unmodifiableCollection(players).iterator();
    }
    
    /**
     * Returns the mark that follows the player mark <code>playerMark</code>.
     * @param playerMark the player mark to consider.
     * @return the mark that follows the player mark <code>playerMark</code>.
     * @throws NullPointerException if <code>playerMark</code> is null.
     */
    public final static PlayerMark getNextMark(final PlayerMark playerMark)
      throws NullPointerException
    {
      if (playerMark == null)
        throw new NullPointerException();
      
      int index = players.indexOf(playerMark);
      
      index = (index + 1) % players.size();
      
      return players.get(index);
    }
    
    /**
     * Returns the number of known player marks.
     * @return the number of known player marks.
     */
    public final static int getNumberOfPlayerMarks()
    {
      return players.size();
    }
    
    /**
     * The player A mark.
     */
    //public final static PlayerMark PLAYER_A_MARK = new PlayerMark(1);
    
    /**
     * The player B mark.
     */
    //public final static PlayerMark PLAYER_B_MARK = new PlayerMark(2);
    
  }
  
  /**
   * The game status class represents the current game status of the game.
   * 
   * @author Julien Aubin
   */
  public final static class GameStatus implements Serializable
  {
    /**
     * The serial version UID.
     */
    final static long serialVersionUID = 1;
    
    /**
     * The game status text.
     */
    private String text;
    
    /**
     * Constructor.
     * @param statusText the status text.
     */
    private GameStatus(final String statusText)
    {
      text = statusText;
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) 
    {
      
      if (obj != null && obj instanceof GameStatus)
        return ((GameStatus) obj).text.equals(text);
      else
        return false;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() 
    {
      return text.hashCode();
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() 
    {
      return text;
    }
    
    /**
     * The continue status.
     */
    public final static GameStatus CONTINUE_STATUS = new GameStatus("Continue");
    
    /**
     * The tie status.
     */
    public final static GameStatus TIE_STATUS = new GameStatus("Tie");
    
    /**
     * The won status.
     */
    public final static GameStatus WON_STATUS = new GameStatus("Won");
  }
  
  /**
   * This class represents a step of the game.
   *
   * @author Julien Aubin
   */
  private final static class PlayStep implements Serializable {
    
    /**
     * The class serial version UID.
     */
    final static long serialVersionUID = 1L;
    
    /**
     * The played column index.
     */
    private int colIndex;
    
    /**
     * The played status.
     */
    private GameStatus currentStatus;
    
    /**
     * The current player mark.
     */
    private PlayerMark currentMark;
    
    /**
     * Constructor.
     * @param col the played column index.
     * @param status the played status.
     * @param mark the played mark.
     * @throws NullPointerException if any of the method parameter is null.
     */
    public PlayStep(final int col, final GameStatus status, final PlayerMark mark)
      throws NullPointerException {
      
      if (status == null || mark == null)
        throw new NullPointerException();
      
      colIndex = col;
      currentStatus = status;
      currentMark = mark;
    }
    
    /**
     * Return played the column index.
     * @return the played column index.
     */
    public int getColIndex() {
      return colIndex;
    }
    
    /**
     * Return the game status at the time of the start of the turn.
     * @return the game status at the time of the start of the turn.
     */
    public GameStatus getGameStatus() {
      return currentStatus;
    }
    
    /**
     * Return the mark at the start of the turn.
     * @return the mark at the start of the turn.
     */
    public PlayerMark getPlayerMark() {
      return currentMark;
    }
  }
  
  /**
   * The game tab, indexed by rows and then by column index.
   * The (0, 0) coordinate represent the (top, left) cell.
   */
  private PlayerMark[][] gameTab;
  
  /**
   * The line length required in order to win.
   */
  private int winLineLength;
  
  /**
   * The currentPlayer.
   */
  private PlayerMark currentPlayer;
  
  /**
   * The game status.
   */
  private GameStatus gameStatus;
  
  /**
   * The win line.
   */
  private List<CellCoord> winLine;
  
  /**
   * The play history.
   */
  private List<PlayStep> playHistory;
  
  /**
   * The set of all lines of the game model.
   */
  private Set<List<CellCoord>> lines; 
  
  /**
   * The map of line positions that are considered as
   * possible victory lines. This is a cache mechanism in order
   * to improve the performance, especially with AI algorithm.<br/>
   * It maps a cell to the list of lines to which it belongs.<br/>
   * This map is shared among all the instances of a gamemodel that
   * share the same parameters, since it's never written with a
   * game model own data.<br/>
   * However it's thread safe.
   */
  private Map<CellCoord, Set<List<CellCoord>>> winLinesMap;
  
  
  public GameModel(final int rows, final int cols, final int winLength, int firstPlayer) {
    PlayerMark.players.clear();
    if (firstPlayer==1) {
      new PlayerMark(1);
      new PlayerMark(2);
    } else {
      new PlayerMark(2);
      new PlayerMark(1);
    }
    PlayerMark.itPlayers = PlayerMark.players.iterator();
    create(rows, cols, winLength);
  }
  
  
  /**
   * Constructor.
   */
  public GameModel()
  {
    this(6, 7, 4, 1);
  }
  
  
  /**
   * Constructor.
   * @param rows the number of rows.
   * @param cols the number of columns.
   * @param winLength the number of cells to get in order to have
   * a winning line.
   * @throws IllegalArgumentException if any of the parameters is smaller
   * or equal to 0, or if the number of cells for a winning line is 
   * greater than <code>Math.min(rows, cols)</code>, or if <code>winLength</code>
   * is smaller or equal to 2.
   */
  public void create(final int rows, final int cols, final int winLength)
     throws IllegalArgumentException
  {
    if (rows <= 0 || cols <= 0)
      throw new IllegalArgumentException("Illegal dimenstions. Rows : " + rows + " - Columns : " + cols);
    
    if (winLength <= 2 || winLength > Math.min(rows, cols))
      throw new IllegalArgumentException("Illegal length of line in order to win. Rows : " + rows + " - Columns : " + cols + " - Length to win : " + winLength);
    
    gameTab = new PlayerMark[rows][cols];
    winLineLength = winLength;
    currentPlayer = PlayerMark.getNextPlayer();
    gameStatus = GameStatus.CONTINUE_STATUS;
    winLinesMap = new ConcurrentHashMap<CellCoord, Set<List<CellCoord>>>();
    winLine = null;
    playHistory = new LinkedList<PlayStep>();
    
    lines = new HashSet<List<CellCoord>>();
    for (int i = 0; i < rows; i++) {
      for (int j = 0; j < cols; j++) {
        lines.addAll(getAllLines(i, j));
      }
    }
  }
  
  /**
   * Constructor.
   * @param gameModel the game model to copy.
   * @throws NullPointerException if <code>gameModel</code> is null.
   */
  public GameModel(final GameModel gameModel) throws NullPointerException
  {
    if (gameModel == null)
      throw new NullPointerException();
    
    winLineLength = gameModel.winLineLength;
    currentPlayer = gameModel.currentPlayer;
    gameStatus = gameModel.gameStatus;
    // Here it is safe to copy the win line map - nothing particular
    // to a given instance of a game model is written here. 
    winLinesMap = gameModel.winLinesMap;
    lines = gameModel.lines;
    
    // Here it is safe to copy the win line, since it is readonly.
    winLine = gameModel.winLine;
    
    gameTab = new PlayerMark[gameModel.gameTab.length][];
      
    for (int i = 0; i < gameModel.gameTab.length; i++)
    {
      gameTab[i] = new PlayerMark[gameModel.gameTab[i].length];
        
      for (int j = 0; j < gameModel.gameTab[i].length; j++)
        gameTab[i][j] = gameModel.gameTab[i][j];
    }
    playHistory = new LinkedList<PlayStep>(gameModel.playHistory);
  }
  
  /**
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(final Object obj)
  {
    if (obj != null && obj instanceof GameModel)
    {
      // No test performed winLinesMap since it should be
      // the same for all the instances of GameModel which
      // have the same dimension.
      GameModel model = (GameModel) obj;
      
      boolean result = winLineLength == model.winLineLength
        && currentPlayer.equals(model.currentPlayer)
        && gameStatus.equals(model.gameStatus);
      
      if (result)
      {
        result = gameTab.length == model.gameTab.length
          && gameTab[0].length == model.gameTab[0].length;
        
        if (result)
        {
          for (int i = 0; i < gameTab.length && result; i++)
          {
            for (int j = 0; j < gameTab[i].length && result; j++)
            {
              if ((gameTab[i][j] == null && model.gameTab[i][j] != null)
                  || (gameTab[i][j] != null && !gameTab[i][j].equals(model.gameTab[i][j])))
                result = false;
                  
            }
          }
        }
      }
      
      return result;
    }
    else
      return false;
  }

  /**
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode()
  {
    return winLinesMap.hashCode();
  }

  /**
   * Returns the current player.
   * @return the current player.
   */
  public PlayerMark getCurrentPlayer()
  {
    return currentPlayer;
  }
  
  /**
   * Returns the game status.
   * @return the game status.
   */
  public GameStatus getGameStatus()
  {
    return gameStatus;
  }
  
  /**
   * Returns the number of cells to align in order to win.
   * @return the number of cells to align in order to win.
   */
  public int getWinLineLength()
  {
    return winLineLength;
  }
  
  /**
   * Returns the row count of this game model.
   * @return the row count of this game model.
   */
  public int getRowCount()
  {
    return gameTab.length;
  }
  
  /**
   * Returns the column count of this game model.
   * @return the column count of this game model.
   */
  public int getColCount()
  {
    return gameTab[0].length;
  }
  
  /**
   * Returns the mark of the cell at coordinates <code>rowIndex, colIndex</code>.
   * @param rowIndex the row index. 
   * @param colIndex the column index.
   * @return the mark of the cell at coordinates <code>rowIndex, colIndex</code>.
   * @throws ArrayIndexOutOfBoundsException if <code>rowIndex</code> is strictly
   * smaller than 0 or greater or equal to the number of rows, or if <code>colIndex</code>
   * is strictly smaller than 0 or greater or equal to the number of columns.
   */
  public PlayerMark getCell(final int rowIndex, final int colIndex) throws ArrayIndexOutOfBoundsException
  {
    if (isOutOfBounds(rowIndex, colIndex))
      throw new ArrayIndexOutOfBoundsException(new CellCoord(rowIndex, colIndex).toString());
    
    return gameTab[rowIndex][colIndex];
  }
  
  /**
   * Returns the mark of the cell at coordinates represented by <code>cellCoord</code>.
   * @param cellCoord the cell coordinates.
   * @return the mark of the cell at coordinates represented by <code>cellCoord</code>.
   * @throws NullPointerException if <code>cellCoord</code> is null.
   * @throws ArrayIndexOutOfBoundsException if <code>cellCoord</code> is out of the
   * game tab bounds.
   */
  public PlayerMark getCell(final CellCoord cellCoord) throws NullPointerException, ArrayIndexOutOfBoundsException
  {
    if (cellCoord == null)
      throw new NullPointerException();
    
    return getCell(cellCoord.getRowIndex(), cellCoord.getColIndex());
  }

  /**
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() 
  {
    
    final String LINE_SEP = System.getProperty("line.separator");
    
    StringBuilder sbContent = new StringBuilder();
    
    for (int i = 0; i < gameTab.length; i++)
    {
      for (int j = 0; j < gameTab[i].length; j++)
      {
        String mark = null;
        
        if (gameTab[i][j] != null)
          mark = gameTab[i][j].toString();
        else
          mark = "0";
        
        sbContent.append(mark);
        
        if (j < gameTab[i].length - 1)
          sbContent.append(" ");
        else
          sbContent.append(LINE_SEP);
      }
    }
    
    return sbContent.toString();
    
  }
  
  /**
   * Return the unique string representation
   * of this game model.<br/>
   * The string representation is provided
   * under a compressed form.
   * @return the unique string representation
   * of this game model.
   */
  public String toUniqueKey()
  {
    StringBuilder result = new StringBuilder();
    
    int nbEmptyCells = 0;
    
    for (int i = 0; i < gameTab.length; i++)
    {
      for (int j = 0; j < gameTab[i].length; j++)
      {
        if (gameTab[i][j] != null)
        {
          if (nbEmptyCells > 0)
            result.append(nbEmptyCells);
          nbEmptyCells = 0;
          result.append(gameTab[i][j].toUniqueKey());
        }
        else
        {
          nbEmptyCells++;
        }
      }
    }
    
    if (nbEmptyCells > 0)
      result.append(nbEmptyCells);
    
    return result.toString();
  }
  
  /**
   * Returns the set of playable columns.
   * @return the list of playable columns.
   */
  public Collection<Integer> getListOfPlayableColumns()
  {
    Set<Integer> result = new TreeSet<Integer>();
    
    if (!gameStatus.equals(GameStatus.CONTINUE_STATUS))
      return result;
    
    for (int i = 0; i < gameTab[0].length; i++)
      if (gameTab[0][i] == null)
        result.add(Integer.valueOf(i));
    
    return result;
  }
  
  /**
   * Perform a play at the column number <code>colIndex</code>, with
   * player mark <code>playerMark</code>.<br/>
   * Gives the turn to the next player, and updates the game status
   * if necessary.
   * @param colIndex the column index where the player wants to play.
   * @param playerMark the player mark.
   * @throws GameModelException if this is not the player turn, if
   * the game is over or if the column number <code>colIndex</code>
   * is not playable.
   */ 
  public void play(final int colIndex, final PlayerMark playerMark)
     throws GameModelException
  {
    Collection<Integer> playableColumns = getListOfPlayableColumns();
    
    if (!playableColumns.contains(Integer.valueOf(colIndex)))
      throw new GameModelException("The column number " + colIndex + " is not included in the list of playable columns " + playableColumns);
    
    if (!playerMark.equals(currentPlayer))
      throw new GameModelException("This is not the turn of player " + playerMark + ". Current turn : " + currentPlayer);
    
    gameTab[getFreeRowIndexForColumn(colIndex)][colIndex] = playerMark;
    
    playHistory.add(new PlayStep(colIndex, gameStatus, currentPlayer));
    
    updateGameStatus(colIndex);
    
    if (gameStatus.equals(GameStatus.CONTINUE_STATUS))
    {
      currentPlayer = PlayerMark.getNextMark(currentPlayer);
    }
  }
  
  /**
   * Cancel the last play in the play history.
   * @throws GameModelException if there's no more more
   * play to remove.
   */
  public void cancelLastPlay() throws GameModelException {
    if (playHistory.isEmpty())
      throw new GameModelException();
    
    PlayStep lastStep = playHistory.remove(playHistory.size() - 1);
    
    gameStatus = lastStep.getGameStatus();
    currentPlayer = lastStep.getPlayerMark();
    int colIndex = lastStep.getColIndex();
    gameTab[getFreeRowIndexForColumn(colIndex) + 1][colIndex] = null;
  }
  
  /**
   * Returns the first free row index for the column which has for
   * index <code>colIndex</code>, i.e. the row index of the first
   * free cell of the column, or -1 if there's not such a free cell.
   * @param colIndex the column index to test.
   * @return the first free row index for the column which has for
   * index <code>colIndex</code>, i.e. the row index of the first
   * free cell of the column, or -1 if there's not such a free cell.
   * @throws ArrayIndexOutOfBoundsException if <code>colIndex</code>
   * is strictly smaller than 0, or greater or equal to the number of
   * columns of the game tab.
   */
  public int getFreeRowIndexForColumn(final int colIndex) throws ArrayIndexOutOfBoundsException
  {
    if (colIndex < 0 || colIndex >= gameTab[0].length)
      throw new ArrayIndexOutOfBoundsException(colIndex);
    
    int rowResult = -1;
    
    boolean occupiedCellFound = false;
    
    for (int i = 0; i < gameTab.length && !occupiedCellFound; i++)
    {
      if (gameTab[i][colIndex] != null)
      {
        occupiedCellFound = true;
        rowResult = i - 1;
      }
    }
    
    if (!occupiedCellFound)
      rowResult = gameTab.length - 1;
    
    return rowResult;
  }
  
  /**
   * Returns the set of horizontal lines that cover the
   * cell at coordinates <code>row, col</code>.
   * @param row the row index.
   * @param col the column index.
   * @return the set of horizonal lines that cover the cell
   * at coordinates <code>row, col</code>.
   */
  private Set<List<CellCoord>> getHorizontalLines(final int row, final int col)
  {
    // Computes the min, max columns that contain the column number col.
    // We must ensure that he column number col is included in all the lines,
    // which explains the "+1"
    int minColIndex = Math.max(0, col - winLineLength + 1);    
    int maxColIndex = Math.min(col, gameTab[0].length - winLineLength);
    
    Set<List<CellCoord>> result = new HashSet<List<CellCoord>>();
    
    // Here we use a <= symbol because the extreme safe coordinates
    // are already computed.
    for (int i = minColIndex; i <= maxColIndex; i++)
    {
      List<CellCoord> line = new ArrayList<CellCoord>();
      
      for (int j = 0; j < winLineLength; j++)
        line.add(new CellCoord(row, i + j));
      
      result.add(Collections.unmodifiableList(line));
    }
    
    return result;
  }
  
  /**
   * Returns the set of vertical lines that cover the cell
   * at coordinates <code>row, col</code>.
   * @param row the row index.
   * @param col the column index.
   * @return the set of vertical lines that cover the cell
   * at coordinates <code>row, col</code>.
   */
  private Set<List<CellCoord>> getVerticalLines(final int row, final int col)
  {
    // Computes the min, max rows that contain the row number row.
    // We must ensure that he row number row is included in all the columns,
    // which explains the "+1"
    int minRowIndex = Math.max(0, row - winLineLength + 1);
    int maxRowIndex = Math.min(row, gameTab.length - winLineLength);
    
    // Here we use a <= symbol because the extreme safe coordinates
    // are already computed.
    Set<List<CellCoord>> result = new HashSet<List<CellCoord>>();
    
    for (int i = minRowIndex; i <= maxRowIndex; i++)
    {
      List<CellCoord> line = new ArrayList<CellCoord>();
      
      for (int j = 0; j < winLineLength; j++)
        line.add(new CellCoord(i + j, col));
      
      result.add(Collections.unmodifiableList(line));
    }
    
    return result;
  }
  
  /**
   * Returns true if the coordinate <code>row, col</code> is out of bounds,
   * false elsewhere.
   * @param row the row index.
   * @param col the column index.
   * @return true if the coordinate <code>row, col</code> is out of bounds,
   * false elsewhere.
   */
  private boolean isOutOfBounds(final int row, final int col)
  {
    return row < 0 || row >= gameTab.length || col < 0 || col >= gameTab[0].length;
  }
  
  /**
   * Returns the set of down diagonals that cover the cell
   * at coodinates <code>row, col</code>. Down diagonals
   * go from (top, left) to (bottom, right) coordinates.
   * @param row the row index.
   * @param col the column index.
   * @return the set of down diagonals that cover the cell
   * at coodinates <code>row, col</code>.
   */
  private Set<List<CellCoord>> getDownDiagonals(final int row, final int col)
  {
    // The algorithem is pretty simple : just consists in computing all
    // the diagonals of line winLineLength that would contain the cell of coords (row, col)
    // in an infinite grid, and then churning out all the ones that
    // contain out of bounds cells.
    
    // Computes the extremities of the diagonals that contain the cell
    // of coord (row, col)
    int minColIndex = col - winLineLength + 1;    
    int minRowIndex = row - winLineLength + 1;
    
    Set<List<CellCoord>> result = new HashSet<List<CellCoord>>();
    
    // Here we use the <= and >= symbol because all the possible
    // diagonals contain the cell of coords (row, col)
    for (int rowIndex = minRowIndex, colIndex = minColIndex; rowIndex <= row && colIndex <= col; rowIndex++, colIndex++)
    {
      List<CellCoord> line = new ArrayList<CellCoord>();
      
      boolean outOfBoundsDetected = false;
      
      // Computing diagonals is much more tricky since we evolve
      // simulataneously on two different dimensions.
      // That's why we must always detect if we're out of bounds or not.
      for (int i = 0; i < winLineLength && !outOfBoundsDetected; i++)
      {
        int cellRow = rowIndex + i;
        int cellCol = colIndex + i;
        
        if (!isOutOfBounds(cellRow, cellCol))
          line.add(new CellCoord(cellRow, cellCol));
        else
          outOfBoundsDetected = true;
      }
      
      if (!outOfBoundsDetected)
        result.add(Collections.unmodifiableList(line));
    }
    
    return result;
  }
  
  /**
   * Returns the set of up diagonals that cover the cell
   * at coodinates <code>row, col</code>. Up diagonals
   * go from (bottom, left) to (top, right) coordinates.
   * @param row the row index.
   * @param col the column index.
   * @return the set of down diagonals that cover the cell
   * at coodinates <code>row, col</code>.
   */
  private Set<List<CellCoord>> getUpDiagonals(final int row, final int col)
  {
    // The algorithem is pretty simple : just consists in computing all
    // the diagonals of line winLineLength that would contain the cell of coords (row, col)
    // in an infinite grid, and then churning out all the ones that
    // contain out of bounds cells.
    
    // Computes the extremities of the diagonals that contain the cell
    // of coord (row, col)    
    int maxColIndex = col + winLineLength - 1;
    int minRowIndex = row - winLineLength + 1;
    
    Set<List<CellCoord>> result = new HashSet<List<CellCoord>>();
    
    // Here we use the <= and >= symbol because all the possible
    // diagonals contain the cell of coords (row, col)
    for (int rowIndex = minRowIndex, colIndex = maxColIndex; rowIndex <= row && colIndex >= col; rowIndex++, colIndex--)
    {
      List<CellCoord> line = new ArrayList<CellCoord>();
      
      boolean outOfBoundsDetected = false;
      
      // Computing diagonals is much more tricky since we evolve
      // simulataneously on two different dimensions.
      // That's why we must always detect if we're out of bounds or not.
      for (int i = 0; i < winLineLength && !outOfBoundsDetected; i++)
      {
        int cellRow = rowIndex + i;
        int cellCol = colIndex - i;
        
        if (!isOutOfBounds(cellRow, cellCol))
          line.add(new CellCoord(cellRow, cellCol));
        else
          outOfBoundsDetected = true;
      }
      
      if (!outOfBoundsDetected)
        result.add(Collections.unmodifiableList(line));
    }
    
    return result;
  }
  
  /**
   * Returns the list of all the lines of this game
   * model. Each line appears only once in the returned
   * set.
   * @return the list of all the lines of this game
   * model.
   */
  public Set<List<CellCoord>> getAllLines() {
    return Collections.unmodifiableSet(lines);
  }
  
  /**
   * Returns the list of all the values for the line <code>line</code>.
   * @param line the line for which we want to get all the values.
   * @return the list of all the values for the line <code>line</code>.
   * @throws NullPointerException if <code>line</code> is null.
   * @throws ArrayIndexOutOfBoundsException if any of the coordinates
   * pointed out by <code>line</code> is null.
   */
  public List<PlayerMark> getValuesOfLine(final List<CellCoord> line) throws NullPointerException, ArrayIndexOutOfBoundsException {
    if (line == null)
      throw new NullPointerException();
    
    // It's very easy to know if a line is valid, since
    // we know all the possible lines of the game.
    if (!lines.contains(line))
      throw new ArrayIndexOutOfBoundsException();
    
    List<PlayerMark> result = new ArrayList<PlayerMark>(line.size());
    
    for (CellCoord coord: line) {
      result.add(gameTab[coord.getRowIndex()][coord.getColIndex()]);
    }
    
    return result;
  }
  
  /**
   * Returns the list of all the possible lines for coordinates <code>row, col</code>.
   * @param row the row index.
   * @param col the column index.
   * @return the list of all the possible lines that are possible
   * for coordinates <code>row, col</code>.
   * @throws ArrayIndexOutOfBoundsException if <code>row</code> is strictly
   * smaller than 0 or greater or equal to the number of rows, or if <code>col</code>
   * is strictly smaller than 0 or greater or equal to the number of columns.
   */
  public Collection<List<CellCoord>> getAllLines(final int row, final int col)
     throws ArrayIndexOutOfBoundsException
  {
    if (isOutOfBounds(row, col))
      throw new ArrayIndexOutOfBoundsException(new CellCoord(row, col).toString());
    
    return getAllLines(new CellCoord(row, col));
  }
  
  /**
   * Returns the list of all the possible lines for coordinates <code>row, col</code>.
   * @param cellCoord the coordinates to consider.
   * @return the list of all the possible lines for coordinates <code>row, col</code>.
   * @throws NullPointerException if <code>cellCoord</code> is null.
   * @throws ArrayIndexOutOfBoundsException if <code>cellCoord</code> is out of bounds.
   */
  public Collection<List<CellCoord>> getAllLines(final CellCoord cellCoord)
     throws NullPointerException, ArrayIndexOutOfBoundsException
  {
    if (cellCoord == null)
      throw new NullPointerException();
    
    Set<List<CellCoord>> result = winLinesMap.get(cellCoord);
    
    if (result == null)
    {
      int row = cellCoord.rowIndex;
      int col = cellCoord.colIndex;
    
      result = getHorizontalLines(row, col);
      result.addAll(getVerticalLines(row, col));
      result.addAll(getDownDiagonals(row, col));
      result.addAll(getUpDiagonals(row, col));
      
      winLinesMap.put(cellCoord, Collections.unmodifiableSet(result));
    }
    
    return result;
  }
  
  /**
   * Returns the winning line, sorted so that the cell
   * which has for index <code>i</code> in the line is contiguous
   * to the cell which has for index <code>i + 1</code>.
   * @return the winning line, or null if the game is not won.
   */
  public List<CellCoord> getWinLine()
  {
    return winLine;
  }
  
  /**
   * Returns true if the game is won, false otherwise.
   * @param colIndex the index of the last played column.
   * @return true if the game is won, false otherwise.
   */
  private boolean isGameWon(final int colIndex)
  {
    // Here we just have to determine if the game
    // has been won around the last cell played. The
    // other ones have not been updated, so they're not
    // interesting for us.
    boolean isWon = false;
    
    Set<List<CellCoord>> encounteredLines = new HashSet<List<CellCoord>>();

    int rowIndex = getFreeRowIndexForColumn(colIndex) + 1;
    PlayerMark markTest = gameTab[rowIndex][colIndex]; 
    
    // We only consider occupied cells here.
    if (markTest != null)
    {
      Collection<List<CellCoord>> lines = getAllLines(rowIndex, colIndex);
      
      Iterator<List<CellCoord>> it = lines.iterator();
      
      while (it.hasNext() && !isWon)
      {
        List<CellCoord> line = it.next();
        
        if (!encounteredLines.contains(line))
        {
          encounteredLines.add(line);
                    
          boolean isAllEqual = true;
          
          Iterator<CellCoord> itCoord = line.iterator();
          
          // A line is considered as correct if all its
          // marks are not null and equal to each other.
          while (itCoord.hasNext() && isAllEqual)
          {
            PlayerMark mark = getCell(itCoord.next());
            
            if (mark != null)
              isAllEqual = mark.equals(markTest);
            else
              isAllEqual = false;
          }
          
          isWon = isAllEqual;
          
          if (isWon)
            winLine = Collections.unmodifiableList(line);
        }
      }
    }
    
    return isWon;
  }
  
  /**
   * Updates the game status.
   * @param colIndex the index of the last played column.
   */
  private void updateGameStatus(final int colIndex)
  {
    if (isGameWon(colIndex))
      gameStatus = GameStatus.WON_STATUS;
    else
    {
      // In case nothing is playable, the game is tie
      if (getListOfPlayableColumns().isEmpty())
        gameStatus = GameStatus.TIE_STATUS;
    }
  }
}




Java Source Code List

android.UnusedStub.java
it.alcacoop.fourinaline.BaseGServiceApplication.java
it.alcacoop.fourinaline.FourInALineDesktop.java
it.alcacoop.fourinaline.FourInALine.java
it.alcacoop.fourinaline.GServiceApplication.java
it.alcacoop.fourinaline.GServiceInterface.java
it.alcacoop.fourinaline.MainActivity.java
it.alcacoop.fourinaline.NativeFunctions.java
it.alcacoop.fourinaline.PrivateDataManager.java
it.alcacoop.fourinaline.PurchaseActivity.java
it.alcacoop.fourinaline.SoundManager.java
it.alcacoop.fourinaline.actors.BoardImage.java
it.alcacoop.fourinaline.actors.Board.java
it.alcacoop.fourinaline.actors.ChatBox.java
it.alcacoop.fourinaline.actors.Checker.java
it.alcacoop.fourinaline.actors.FixedButtonGroup.java
it.alcacoop.fourinaline.actors.IconButton.java
it.alcacoop.fourinaline.actors.ParticleEffectActor.java
it.alcacoop.fourinaline.actors.PlayerBlock.java
it.alcacoop.fourinaline.actors.UIDialog.java
it.alcacoop.fourinaline.billingutils.Base64DecoderException.java
it.alcacoop.fourinaline.billingutils.Base64.java
it.alcacoop.fourinaline.billingutils.IabException.java
it.alcacoop.fourinaline.billingutils.IabHelper.java
it.alcacoop.fourinaline.billingutils.IabResult.java
it.alcacoop.fourinaline.billingutils.Inventory.java
it.alcacoop.fourinaline.billingutils.Purchase.java
it.alcacoop.fourinaline.billingutils.Security.java
it.alcacoop.fourinaline.billingutils.SkuDetails.java
it.alcacoop.fourinaline.client.GwtLauncher.java
it.alcacoop.fourinaline.fsm.FSM.java
it.alcacoop.fourinaline.gservice.GServiceClient.java
it.alcacoop.fourinaline.gservice.GServiceCookieMonster.java
it.alcacoop.fourinaline.gservice.GServiceMessages.java
it.alcacoop.fourinaline.gservice.GServiceNetHandler.java
it.alcacoop.fourinaline.layers.BaseScreen.java
it.alcacoop.fourinaline.layers.GameScreen.java
it.alcacoop.fourinaline.layers.MatchOptionsScreen.java
it.alcacoop.fourinaline.layers.MenuScreen.java
it.alcacoop.fourinaline.layers.OptionsScreen.java
it.alcacoop.fourinaline.layers.SplashScreen.java
it.alcacoop.fourinaline.logic.AIExecutor.java
it.alcacoop.fourinaline.logic.MatchState.java
it.alcacoop.fourinaline.util.Base64DecoderException.java
it.alcacoop.fourinaline.util.Base64.java
it.alcacoop.fourinaline.util.GServiceGameHelper.java
it.alcacoop.fourinaline.util.GameHelperUtils.java
it.alcacoop.fourinaline.utils.AchievementsManager.java
it.alcacoop.fourinaline.utils.AppDataManager.java
it.alcacoop.fourinaline.utils.ELORatingManager.java
org.gojul.fourinaline.model.AlphaBeta.java
org.gojul.fourinaline.model.DefaultEvalScore.java
org.gojul.fourinaline.model.EvalScore.java
org.gojul.fourinaline.model.GameModel.java