/** By Dingding */ private Set<Line.Segment> getRemovableLineSegments( Map<Position, Piece> pieceMap, PieceColor pieceColor) { Set<Line.Segment> removableLines = new HashSet<>(); Set<Line> linesOnTheBoard = Line.getLinesOnTheBoard( this); // Get all the possible lines on the board. Positions don't need to be occupied. for (Line line : linesOnTheBoard) { Position currentPosition = line.getStartPosition(); Position startOfSegment = null; Position endOfSegment = null; Direction direction = line.getDirection(); int consecutivePieces = 0; // We start at a dot position, so we can assume that we don't start in a set of // consecutive pieces boolean isInLineSegment = false; // Break the for-loop if an endOfSegment has been found (because the largest lines only have 7 // positions on the board, there // can't be more than one set of four pieces of the same color (requiring at least 9 // positions) on the board. for (; endOfSegment == null && isPositionOnPlayAreaOrOuterDots(currentPosition); currentPosition = currentPosition.next(direction)) { PieceColor currentPieceColor = pieceMap.containsKey(currentPosition) ? pieceMap.get(currentPosition).getPieceColor() : null; // Update the consecutivePieces if (currentPieceColor == pieceColor) consecutivePieces++; if (consecutivePieces == 4) isInLineSegment = true; if (currentPieceColor != pieceColor) consecutivePieces = 0; if (isInLineSegment) { if (isDotPosition(currentPosition) || currentPieceColor == null) { endOfSegment = currentPosition.previous(direction); } } // Update the startOfSegment if necessary if (startOfSegment == null) { if (currentPieceColor != null) { startOfSegment = currentPosition; } } if (currentPieceColor == null && endOfSegment == null) { startOfSegment = null; } // Add a line segment to the list if we have found one if (endOfSegment != null) { removableLines.add(new Line.Segment(this, startOfSegment, endOfSegment, direction)); } } } return removableLines; }
private void movePiece(Map<Position, Piece> pieceMap, Position currentPosition, int deltaPos) throws InvalidMoveException { Position nextPosition = new Position(currentPosition.posId + deltaPos); if (!isOnInnerBoard(nextPosition)) { throw new InvalidMoveException(); } else { try { if (pieceMap.containsKey(nextPosition)) { movePiece(pieceMap, nextPosition, deltaPos); } // Don't copy over null values, instead remove the value from the hashmap if (pieceMap.containsKey(currentPosition)) { pieceMap.put(nextPosition, pieceMap.remove(currentPosition)); } } catch (InvalidMoveException e) { throw new InvalidMoveException(); } } }
// TODO: Refactor method private void removeLines( Map<Position, Piece> pieceMap, PieceColor pieceColor, Map<PieceColor, Set<Line.Segment>> linesTakenBy, Map<PieceColor, Set<Position>> piecesBackTo) { Set<Line.Segment> intersectingSegments; Set<Line.Segment> segmentsNotRemoved = new HashSet<>(); do { intersectingSegments = new HashSet<>(); Set<Line.Segment> removableSegmentsThisPlayer = getRemovableLineSegments(pieceMap, pieceColor); for (Line.Segment segment : removableSegmentsThisPlayer) { // Remove the line segments that are not intersecting with other line segments of the set boolean intersectionFound = false; for (Line.Segment otherSegment : removableSegmentsThisPlayer) { if (!segment.equals(otherSegment) && !segmentsNotRemoved.contains(otherSegment)) { if (segment.intersectsWith(otherSegment)) { if (!segmentsNotRemoved.contains(segment)) { intersectingSegments.add(segment); intersectionFound = true; } } } } if (!intersectionFound) { if (!segmentsNotRemoved.contains(segment)) { linesTakenBy.get(pieceColor).add(segment); } } } if (intersectingSegments.size() > 0) { Line.Segment segment = intersectingSegments.iterator().next(); currentRemoveSelection = segment.getOccupiedPositions(pieceMap); int dialogResult = GipfBoardComponent.showConfirmDialog( gipfBoardState.players.current().pieceColor + ", do you want to remove " + segment .getOccupiedPositions(pieceMap) .stream() .map(Position::getName) .sorted() .collect(toList()) + "?", "Remove line segment"); if (dialogResult == JOptionPane.YES_OPTION) { // Remove the line linesTakenBy.get(pieceColor).add(segment); } else if (dialogResult == JOptionPane.NO_OPTION) { // Don't remove the line segmentsNotRemoved.add(segment); } currentRemoveSelection = new HashSet<>(); } for (Line.Segment segment : linesTakenBy.get(pieceColor)) { Predicate<Map.Entry<Position, Piece>> isNormalPiece = entry -> entry.getValue().getPieceType() == NORMAL; Predicate<Map.Entry<Position, Piece>> isCurrentPlayersColor = entry -> entry.getValue().getPieceColor() == pieceColor; Predicate<Map.Entry<Position, Piece>> doesPlayerWantToRemoveGipf = entry -> { currentRemoveSelection.add(entry.getKey()); int dialogResult = GipfBoardComponent.showConfirmDialog( gipfBoardState.players.current().pieceColor + ", do you want to remove the Gipf at " + entry.getKey().getName() + "?", "Remove Gipf"); currentRemoveSelection = new HashSet<>(); return dialogResult == JOptionPane.YES_OPTION; }; Map<Position, Piece> piecesRemovedMap = segment.getOccupiedPositions(pieceMap).stream().collect(toMap(p -> p, pieceMap::get)); piecesBackTo .get(pieceColor) .addAll( piecesRemovedMap .entrySet() .stream() .filter(isCurrentPlayersColor.and(isNormalPiece.or(doesPlayerWantToRemoveGipf))) .map(Map.Entry::getKey) .collect(toSet())); piecesBackTo .get(null) .addAll( piecesRemovedMap .entrySet() .stream() .filter( isCurrentPlayersColor .negate() .and(isNormalPiece.or(doesPlayerWantToRemoveGipf))) .map(Map.Entry::getKey) .collect(toSet())); } piecesBackTo.values().forEach(positionSet -> removePiecesFromPieceMap(pieceMap, positionSet)); } while (intersectingSegments.size() > 0); return; }
private void removePiecesFromPieceMap(Map<Position, Piece> pieceMap, Set<Position> positions) { for (Position position : positions) { // An extra check. THe removelines method will remove pieces before if (pieceMap.containsKey(position)) pieceMap.remove(position); } }
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"); } }