public Set<Move> getAllowedMoves() { // If there is already a winn if (gipfBoardState.players.winner() != null) { return Collections.emptySet(); } // Create a set of incomplete moves containing the starting positions and directions for the // current piece Set<Move> potentialMoves = getPotentialStartMoves(getCurrentPiece()); // If the current piece is a GIPF piece, the player is also allowed to place normal pieces. if (getCurrentPiece().getPieceType() == GIPF) potentialMoves.addAll( getPotentialStartMoves(Piece.of(NORMAL, getCurrentPiece().getPieceColor()))); // These moves are marked as complete so a temporary game won't ask for user input. potentialMoves.stream().forEach(m -> m.isCompleteMove = true); Set<Move> potentialMovesIncludingLineSegmentRemoval = new HashSet<>(); for (Move potentialMove : potentialMoves) { try { Map<Position, Piece> temporaryPieceMap = new HashMap<>(getGipfBoardState().getPieceMap()); temporaryPieceMap.put(potentialMove.startPos, potentialMove.addedPiece); movePiecesTowards( temporaryPieceMap, potentialMove.getStartingPosition(), potentialMove.getDirection()); Set<List<Pair<PieceColor, Line.Segment>>> RLineOrderingsSet = getRemovableLineOrderingsSetFromGipfBoard( temporaryPieceMap, getCurrentPiece().getPieceColor()); if (RLineOrderingsSet.size() > 0) { for (List<Pair<PieceColor, Line.Segment>> RLineOrdering : RLineOrderingsSet) { Set<Position> piecesToWhite = new HashSet<>(); Set<Position> piecesToBlack = new HashSet<>(); Set<Position> piecesRemoved = new HashSet<>(); for (Pair<PieceColor, Line.Segment> RLine : RLineOrdering) { Line.Segment removedSegment = RLine.getValue(); // The color of the player who removed the line PieceColor colorRemoved = RLine.getKey(); // Determine per segment to whom the pieces are given. Pieces can only be given to the // player // who removed the line, or deleted from the game. Set<Position> occupiedPositions = removedSegment.getOccupiedPositions(temporaryPieceMap); Set<Position> piecesFromSegmentBackToReserve = occupiedPositions .stream() .filter( position -> temporaryPieceMap.get(position).getPieceColor() == colorRemoved) .collect(toSet()); Set<Position> piecesFromSegmentRemoved = occupiedPositions .stream() .filter(position -> !piecesFromSegmentBackToReserve.contains(position)) .collect(toSet()); if (colorRemoved == WHITE) piecesToWhite.addAll(piecesFromSegmentBackToReserve); if (colorRemoved == BLACK) piecesToBlack.addAll(piecesFromSegmentBackToReserve); piecesRemoved.addAll(piecesFromSegmentRemoved); } // And finally add the move // the constructor will define this as a complete move, because all the parameters have // a value. potentialMovesIncludingLineSegmentRemoval.add( new Move( potentialMove.addedPiece, potentialMove.startPos, potentialMove.direction, piecesToWhite, piecesToBlack, piecesRemoved)); } } else { // If no line segments can be removed, just add the original move potentialMovesIncludingLineSegmentRemoval.add(potentialMove); } } catch (InvalidMoveException e) { // We don't consider this move if it is invalid } } return potentialMovesIncludingLineSegmentRemoval; }
/** * 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"); } }