Example #1
0
 public void returnToPreviousBoard() {
   if (boardHistory.size() > 1) {
     gipfBoardState = boardHistory.pop();
     loadState(gipfBoardState);
     gameLogger.log("Returned to previous game state");
   }
 }
Example #2
0
  Game() {
    /*
     * Initialize a new starting GipfBoardState object for this game. The initializePieceMap() and initializePlayers()
     * methods are meant to be overridden by the classes extending the game class.
     */
    this.gipfBoardState = new GipfBoardState(null, initializePieceMap(), initializePlayers());

    boardHistory = new BoardHistory();
    boardHistory.add(gipfBoardState);

    /*
     * Empty logger, for now it makes no sense to store all the log data in all generated games. String concatenations
     * are relatively expensive and the output is shown nowhere.
     */
    gameLogger = new EmptyLogger();
  }
Example #3
0
  /**
   * applyMove applies the given move to the board. First, the new piece is added to the startPos
   * Then the pieces are moved in the direction of the move, and finally pieces that need to be
   * removed are removed from the board
   *
   * @param move the move that is applied
   */
  public void applyMove(Move move) {
    // An invalidMoveException can be thrown if applying that move would mean to place pieces on an
    // illegal position.
    try {
      moveCounter++;
      // If there's already a winner, the move won't be applied
      if (gipfBoardState.players.winner() != null) return;

      /*
       * Prepare for creating a new child GipfBoardState. The pieceMap and playersInGame objects of the current
       * gipfBoardState are unmodifiable, so we have to create modifiable copies.
       * If the move turns out to be legal, a new child GipfBoardState will be generated, based on the modified
       * copies of the PieceMap and the PlayersInGame objects.
       */
      // The piece map returned by getPieceMap() is unmodifiable, so it has to be converted to a new
      // (hash) map
      // the newPieceMap can be modified, after that a new GipfBoardState can be generated.
      Map<Position, Piece> newPieceMap = new HashMap<>(gipfBoardState.getPieceMap());

      // The same is true for the PlayersInGame object. It is unmodifiable, so a new instance has to
      // be created
      // for the new board state.
      PlayersInGame newPlayers = new PlayersInGame(gipfBoardState.players);

      // If the current player has enough pieces left in the reserve to perform the move (1 for a
      // normal move, 2 for
      // a Gipf move.)
      if (newPlayers.current().reserve >= move.addedPiece.getPieceValue()) {
        /*
         * Move the piece
         */
        // Each move adds a new piece to the board
        newPieceMap.put(move.startPos, move.addedPiece);

        // Move it into the direction determined by the move
        movePiecesTowards(newPieceMap, move.startPos, move.direction);

        /*
         * Remove the lines and pieces that can be removed from the board
         */
        // Create an object that keeps track of which piece is taken by whom. An EnumMap instead of
        // a HashMap is
        // used, because all keys correspond with values from the PieceColor enum.
        Map<PieceColor, Set<Line.Segment>> linesTakenBy = new EnumMap<>(PieceColor.class);
        linesTakenBy.put(WHITE, new HashSet<>());
        linesTakenBy.put(BLACK, new HashSet<>());

        // Create an object that keeps track of which individual pieces are taken by whom.
        // A HashMap is used because it can handle null keys, in contrast with EnumMaps.
        Map<PieceColor, Set<Position>> piecesBackTo = new HashMap<>();
        piecesBackTo.put(WHITE, new HashSet<>());
        piecesBackTo.put(BLACK, new HashSet<>());
        piecesBackTo.put(null, new HashSet<>()); // Used for pieces that are removed from the board

        /*
         * Distinguish between complete and incomplete moves.
         *  - Complete moves:
         *    are generated by the getAllowedMoves() method and contain all information about that move,
         *    including a choice for which lines or gipf pieces will be removed.
         *  - Incomplete moves:
         *    are performed by human players. These moves don't contain the information of which pieces are
         *    removed. This means that there may be user interaction required if the player must choose between
         *    multiple lines or gipf pieces that can be removed.
         */
        if (move.isCompleteMove) {
          // Complete moves are the easiest to handle, the positions of pieces that are removed are
          // already
          // determined.
          // This means that we only have to read the values for the pieces that are returned to
          // each player
          // into the piecesBackTo map.
          piecesBackTo.get(WHITE).addAll(move.piecesToWhite);
          piecesBackTo.get(BLACK).addAll(move.piecesToBlack);
          piecesBackTo.get(null).addAll(move.piecesRemoved);
        } else {
          // Now we have incomplete moves. This means that we have to remove the pieces that are
          // required to
          // be removed. If the player must choose between different pieces / lines, the removeLines
          // method
          // will ask the player to make a choice.

          // Get the lines that are taken by the current player (retrieved from linesTakenBy) and
          // store them
          // in the piecesBackTo map. The opponent's pieces are stored in piecesBackTo.get(null),
          // because they
          // are removed from the board.
          removeLines(newPieceMap, newPlayers.current().pieceColor, linesTakenBy, piecesBackTo);
          // linesTakenBy.get(newPlayers.current().pieceColor).addAll(getRemovableLineSegments(newPieceMap, newPlayers.current().pieceColor));

          // Get the lines that are taken by the opponent (retrieved from the linesTakenBy map), and
          // store
          // them in the piecesBackTo map. The current player's pieces are stored in
          // piecesBackTO.get(null),
          // because they are removed from the board.
          PieceColor opponentColor = newPlayers.current().pieceColor == WHITE ? BLACK : WHITE;
          removeLines(newPieceMap, opponentColor, linesTakenBy, piecesBackTo);
          // linesTakenBy.get(opponentColor).addAll(getRemovableLineSegments(newPieceMap,
          // opponentColor));
        }
        gameLogger.log(move.toString());

        // Each value in the piecesBackTo map is a set, and each element (position) of the sets of
        // all values
        // is removed from the pieceMap.
        // The number of the returned pieces for each player are added to their reserve.
        for (Map.Entry<PieceColor, Set<Position>> removedPieces : piecesBackTo.entrySet()) {
          if (removedPieces.getKey() != null) {
            // Calculate the sum for the pieces returned to this player. Normal pieces have a value
            // of 1,
            // gipf pieces a value of 2 determined in Piece.getPieceValue().
            int returnedPiecesSum =
                removedPieces
                    .getValue()
                    .stream()
                    .mapToInt(
                        position -> {
                          if (newPieceMap.containsKey(position))
                            return newPieceMap.get(position).getPieceValue();
                          else return 1;
                        })
                    .sum();

            newPlayers.get(removedPieces.getKey()).reserve += returnedPiecesSum;
            gameLogger.log(removedPieces.getKey() + " retrieved " + returnedPiecesSum + " pieces");
          }

          // The pieces are not earlier removed from the board, because the returnedPiecesSum
          // variable can
          // only be set if all the pieces are still on the board.
          removePiecesFromPieceMap(newPieceMap, removedPieces.getValue());
        }

        /*
         * Set the properties for the player, based on the move
         */
        if (move.addedPiece.getPieceType() == PieceType.GIPF) {
          newPlayers.current().hasPlacedGipfPieces = true;
        }
        if (!newPlayers.current().isPlacingGipfPieces) {
          newPlayers.current().hasPlacedNormalPieces = true;
        }

        // Update the current player's reserve for the last added piece
        newPlayers.current().reserve -= move.addedPiece.getPieceValue();

        /*
         * Check whether it is game over
         */
        // If we create a new GipfBoardState based on the calculated properties, will there be a
        // game over situation?
        if (getGameOverState(new GipfBoardState(null, newPieceMap, newPlayers))) {
          // If the current player causes a game over situation, the other player (updateCurrent()),
          // will be
          // the winner of the game.
          newPlayers = newPlayers.updateCurrent().makeCurrentPlayerWinner();
          gameLogger.log("Game over! " + newPlayers.winner().pieceColor + " won!");

          if (moveCounter != 1) {
            if (SettingsSingleton.getInstance().showExperimentOutput) {
              String moveCountString = Integer.toString(moveCounter);
              String durationString =
                  Long.toString(Duration.between(gameStartInstant, Instant.now()).toMillis());
              String winnerString = newPlayers.winner().pieceColor.toString();
              String whiteAlgorithm = whitePlayer.getClass().getSimpleName();
              String blackAlgorithm = blackPlayer.getClass().getSimpleName();

              ExperimentLogger.get()
                  .log(
                      whiteAlgorithm
                          + "; "
                          + blackAlgorithm
                          + "; "
                          + moveCountString
                          + "; "
                          + durationString
                          + "; "
                          + winnerString);
            }
          }
        }

        // We don't need to update the current player if the game has ended
        if (newPlayers.winner() == null) {
          newPlayers = newPlayers.updateCurrent();
        }

        // Create a new gipfBoardState, based on the calculated PieceMap and PlayersInGame objects.
        GipfBoardState newGipfBoardState =
            new GipfBoardState(gipfBoardState, newPieceMap, newPlayers);
        boardHistory.add(gipfBoardState);
        this.gipfBoardState = newGipfBoardState;
      } else {
        gameLogger.log("No pieces left");
      }

      // Recalculate the properties of this gipfBoardState
      gipfBoardState.boardStateProperties.updateBoardState();

    } catch (InvalidMoveException e) {
      System.out.println("Move not applied");
    }
  }