private Move solveMove(Move move) throws NoFreeCellException, AgainstTheRulesException { MoveMacro newMove = new MoveMacro(); Iterator stackIterator = stacks[move.getFrom()].createIterator(); FreeCellAllocator allocator = new FreeCellAllocator(); { // Find out if any move would be possible before we go to the trouble of creating a movemacro. boolean found = false; while (stackIterator.hasNext()) { Card theCard = (Card) stackIterator.next(); if (stacks[move.getTo()].canPush(theCard)) { found = true; break; } /* * TODO: A side effect of this is if the fromstack includes any * card that could be moved, the move will allocate free cells * until it runs out, even if the card is buried deep? * //int freeCells = allocator.getNumFreeCells(); * //for (int i=0; i < freeCells; i++) { */ } if (!found) { throw new AgainstTheRulesException("There are no valid moves possible."); } } // Go back to the beginning. stackIterator.reset(); while (stackIterator.hasNext()) { Card theCard = (Card) stackIterator.next(); // if this card can be moved, then create the move and end if (stacks[move.getTo()].canPush(theCard)) { newMove.addMove(move); break; } else { // if it can't be moved directly, then get a free cell and create a move to the free cell. int cell = allocator.getFreeCell(); newMove.addMove(new Move(move.getFrom(), cell, this)); } // If we get a NoFreeCellException here, it's passed back and newMove gets gc'ed. } // Now, go back through the moves and move everything to the destination stack in reverse order. Iterator moveIterator = newMove.createIterator(); moveIterator.previous(); // Skip the very last move, we don't need to repeat that. while (moveIterator.hasPrevious()) { Move theMove = (Move) moveIterator.previous(); newMove.addMove(new Move(theMove.getTo(), move.getTo(), this)); } return newMove; }
// alpha-beta search public Move alphaBeta( Board currentBoard, int ply, Player player, double alpha, double beta, int maxDepth) { /* Algorithm provided by: Dr. Cameron Implemented by: Brett Hodges */ if (ply >= maxDepth) { Move returnMove = new Move(); returnMove.value = currentBoard.evaluateBoard(player); return returnMove; } else { ArrayList<Move> moves = currentBoard.generateMoves(player); Move bestMove = moves.get(0); for (Move move : moves) { Board newBoard = new Board(currentBoard); newBoard.applyMove(player, move.move); // Recursively Call alpha-beta Move tempMove = alphaBeta(newBoard, ply + 1, player.getOpponant(), beta * -1, alpha * -1, maxDepth); move.value = -tempMove.value; if (move.value > alpha) { bestMove = move; alpha = move.value; if (alpha > beta) return bestMove; } } return bestMove; } }
public void revertMove() { // pops a move from the stack and reverses it try { Move move = this.popMoveFromHistory(); // pop a move off the stack if (move.getPosition() == -1) { // move is pass } else { this.positions[move.getPosition()] = 0; // set move position to empty int stackSize = move.getFlipStackSize(); // how many flips are there? for (int i = 0; i < stackSize; i++) { // loop through the stack int flipPosition = move.popFlip(); // System.out.println(flipPosition); this.setPosition(flipPosition); // pop a flip and un-flip it } } // Debugging stuff popCount++; // System.out.println("pop"); // this.printBoard(); } catch (NullPointerException e) { System.out.println("(C you tried to pop an empty move stack )"); } }
// generates all possible moves for player passed, gives them a rating, and sorts them public ArrayList<Move> generateMoves(Player player) { int playerNumber = player.getPlayerNumber(); ArrayList<Move> moveList = new ArrayList<>(); for (int i = 0; i < board.length; i++) { if (isValidMove(playerNumber, i)) { Move move = new Move(i); if (CORNERS.contains(move.getMove())) { move.setValue(500); } else if (AMOVES.contains(move.getMove())) { move.setValue(250); } else if (BMOVES.contains(move.getMove())) { move.setValue(100); } else if (CMOVES.contains(move.getMove())) { move.setValue(50); } else move.setValue(-5); moveList.add(move); } } // in case of no moves, add pass move if (moveList.size() > 0) { Collections.sort(moveList, new MoveComparator()); Collections.reverse(moveList); } else { String pass = "" + player.getPlayerColor(); Move move = new Move(pass); moveList.add(move); } player.assignPossibleMoves(moveList); return moveList; }
public void unmove(Move move) { try { Card theCard = stacks[move.getTo()].getTopCard(); stacks[move.getFrom()].deal(theCard); stacks[move.getTo()].pop(); } catch (Exception e) { controller.alert("Sorry, I guess you're outta luck."); } this.repaint(); }
/** * Apply the given Move to this Board (i.e., mark the Move's location on this Board with the * Move's Player). */ public void apply(Move move) throws TTTRuntimeException { int x = move.getX(); int y = move.getY(); int z = move.getZ(); if (x < 0 || x >= size || y < 0 || y >= size || z < 0 || z >= size) { throw new IllegalLocationException(); } else if (!isEmpty(x, y, z)) { throw new LocationOccupiedException(); } else { grid[x][y][z] = move.getPlayer(); } }
public void applyMove(int color, Move move) { // applies move based on fields of supplied move and pushes it to the stack if (move.getPosition() == -1) { // move is pass } else { this.positions[move.getPosition()] = color; Stack<Integer> flipStack = move.getFlips(); int amountOfFlips = flipStack.size(); for (int i = 0; i < amountOfFlips; i++) { this.positions[flipStack.pop()] = color; } } this.pushMoveToHistory(move); this.clearLegalMovesList(); // debugging stuff pushCount++; // System.out.println("push"); // this.printBoard(); }
public int makeComputerMove(long timeRemaining) { // othellonator makes a move Move myMove = null; moveCount++; System.out.println("(C moveCount = " + moveCount + ")"); if (this.generateLegalMoves(myColor)) { // evaluate board, do stuff if legal moves found String listSize = Integer.toString(getLegalMovesListSize()); System.out.println("(C Number of moves:" + listSize + " )"); if (moveCount < 10) { myMove = Search.alphaBetaSearch(this, 6, myColor); } else if (moveCount >= 10 && moveCount <= 20) { myMove = Search.alphaBetaSearch(this, 8, myColor); } else { myMove = Search.alphaBetaSearch(this, 10, myColor); } this.applyMove(myColor, myMove); // apply the move returned by the search return myMove.getPosition(); // return the position of the move so it can be output } else return -1; // signal that no move was found }
public boolean evaluateOpponentMove(int color, int position) { // runs generateMoveForPosition() to generate flips and push the move on to the list // then applies the move at the front of the list boolean legal = false; Move move = null; this.generateLegalMoves(myColor * -1); // evaluate legal moves for opponent for (int i = 0; i < this.legalMovesList.size(); i++) { // check move against legalMovesList Move tester = this.getLegalMove(i); // if opponent's move found in legalMoveList, it is legal if (tester.getPosition() == position) { legal = true; move = tester; } } // if the move is legal, apply it and return true. otherwise return false. if (legal) { applyMove(color, move); return true; } else return false; }
public void autoHome() { boolean found = true; // Set once for the beginning. while (found) { // As long as we found something on a previous pass, keep trying. found = false; // TODO: Search cellstacks as well. I got arrayIndexOutOfBounds before, because it overstepped // some boundary. for (int i = 0; i < PLAYSTACKLENGTH; i++) { if (stacks[i].hasCards()) { Card theCard = stacks[i].getTopCard(); int homeStack = findHome(theCard); if (homeStack > -1) { found = true; Move move = new Move(i, homeStack, this); try { move.execute(); addToHistory(move); } catch (Exception e) { controller.alert(e.getMessage()); } } } } } }
@Override @HibernateUserRead public void step2() { CardType ct = CardType.getTL(ctId); Date dt = new Date(); Card c = new Card(txt, ct, dt); c.setCreatedInMove(Move.getCurrentMoveTL()); c.setAuthor(User.getTL(author.getId())); // fresh if (newCardListener != null) newCardListener.cardCreatedTL(c); content.setValue(""); content.setInputPrompt("Enter text for another card."); closeDrawer(); resetCoroutine(); // for another click }
/** * 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"); } }
@Override public int compare(Move move1, Move move2) { return Integer.compare(move1.getValue(), move2.getValue()); }
static int MiniMax( int[][] board, int depth, int max_depth, int[] the_move, int turn, int[] counter, int white_best, int black_best) { // System.out.println ("\n"); // System.out.println ("Calculating minimax"); int the_score = 0; int[][] new_board = new int[8][8]; int best_score; // , chosen_score; int[] best_move = new int[4]; Vector<int[]> moves_list = new Vector<int[]>(); // vector of 4x1 arrays // Thread.yield(); // assumes that depth is never equal to max_depth to begin with since // chosen_move is not set here if (depth == max_depth) { best_score = Evaluation(board); counter[0]++; } else { moves_list = Move.generate_moves(board, turn); best_score = which_turn(turn); switch (moves_list.size()) { case 0: counter[0]++; return best_score; case 1: if (depth == 0) { // forced move: immediately return control best_move = (int[]) moves_list.elementAt(0); for (int k = 0; k < 4; k++) the_move[k] = best_move[k]; return 0; } else { // extend search since there is a forcing move max_depth += 1; } } for (int i = 0; i < moves_list.size(); i++) { new_board = copy_board(board); // board need not be touched Move.move_board(new_board, (int[]) moves_list.elementAt(i)); // returns new_board int temp[] = new int[4]; the_score = MiniMax( new_board, depth + 1, max_depth, temp, opponent(turn), counter, white_best, black_best); if (turn == Checkers.BLACK && the_score > best_score) { best_move = (int[]) moves_list.elementAt(i); best_score = the_score; if (best_score > black_best) { if (best_score >= white_best) break; /* alpha_beta cutoff */ else black_best = best_score; // the_score } } else if (turn == Checkers.WHITE && the_score < best_score) { best_move = (int[]) moves_list.elementAt(i); best_score = the_score; if (best_score < white_best) { if (best_score <= black_best) break; /* alpha_beta cutoff */ else white_best = best_score; // the_score } } } // end for } // end else for (int k = 0; k < 4; k++) the_move[k] = best_move[k]; return best_score; } // end minimax
static int which_turn(int turn) { return Move.color(turn) == Checkers.BLACK ? -INFINITY : INFINITY; }
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; }
protected void keyPressed(int keyCode) { int selectionLocation = getSelectionLocation(); // Selection buttons if ((keyCode == KEY_NUM4) || (keyCode == -3) || (keyCode == LEFT)) { // NUM4, left, bbtrackball left gotoPrevious(); } else if ((keyCode == KEY_NUM6) || (keyCode == -4) || (keyCode == RIGHT)) { // NUM5, right, bbtrackball right gotoNext(); // Action buttons } else if ((keyCode == -5) || (keyCode == FIRE) || (keyCode == -8) || (keyCode == 10)) { // Emulator OK, FIRE, bbtrackball click, keyboard return if (selectionLocation > -1) { if (selectionLocation == cursorLocation) { // Selecting the same card that's already selected try { // Toggle selection stacks[selectionLocation].unselect(); } catch (Exception e) { controller.alert(e.getMessage()); } } else { try { // Create the move and execute it. Move move = new Move(selectionLocation, cursorLocation, this); move.execute(); addToHistory(move); if (stacks[selectionLocation].hasCards()) { stacks[selectionLocation].unselect(); } } catch (AgainstTheRulesException e) { // If no solution is found, then complain. controller.alert(e.getMessage()); } catch (NoFreeCellException e) { // If the solver couldn't find enough cells, then complain. controller.alert(e.getMessage()); } catch (InvalidCardException e) { controller.alert(e.getMessage()); } finally { try { // And make sure nothing is left selected selectionLocation = getSelectionLocation(); if (selectionLocation > -1) { stacks[selectionLocation].unselect(); } } catch (Exception e) { // And if that fails, complain controller.alert(e.getMessage()); } } } this.repaint(); } else { try { // Select the card under the cursor stacks[cursorLocation].select(); } catch (Exception e) { // And if that fails, complain controller.alert(e.getMessage()); } repaint(); } } else if ((keyCode == UP) || (keyCode == -1)) { // UP, Emulator UP if (getSelectionLocation() == -1) { // Move the selection location to the top, for convenience. gotoLocation(PLAYSTACKLENGTH + CELLSTACKLENGTH - 1); this.repaint(); } else { // Move the currently-selected card home if possible, or to a free cell, if available. try { Card theCard = stacks[selectionLocation].getTopCard(); int homeslot = findHome(theCard); if (homeslot > -1) { Move move = new Move(selectionLocation, homeslot, this); move.execute(); addToHistory(move); } else { // Find a free cell and if found, create a move and execute. int freeCell = new FreeCellAllocator().getFreeCell(); Move move = new Move(selectionLocation, freeCell, this); move.execute(); addToHistory(move); } if (stacks[selectionLocation].hasCards()) { stacks[selectionLocation].unselect(); } } catch (AgainstTheRulesException e) { // If no solution is found, then complain. controller.alert(e.getMessage()); } catch (NoFreeCellException e) { // If the solver couldn't find enough cells, then complain. controller.alert(e.getMessage()); } catch (InvalidCardException e) { controller.alert(e.getMessage()); } finally { try { // And make sure nothing is left selected selectionLocation = getSelectionLocation(); if (selectionLocation > -1) { stacks[selectionLocation].unselect(); } } catch (Exception e) { // And if that fails, complain controller.alert(e.getMessage()); } } } // Other buttons } else { // DEBUG // controller.alert("KeyCode:"+keyCode+"\nCursor:"+cursor.toString()); } }
public void doMove(Move move) throws AgainstTheRulesException, NoFreeCellException, InvalidCardException { Card theCard = null; try { theCard = stacks[move.getFrom()].pop(); stacks[move.getTo()].push(theCard); } catch (AgainstTheRulesException e1) { if (!theCard.equals(null)) { // If we managed to pop the card, deal it back. stacks[move.getFrom()].deal(theCard); } // TODO: If this move is already part of a movemacro, then we don't want to keep trying to // solve it. Move newMove = solveMove(move); newMove.execute(); addToHistory(move); } this.repaint(); }