package com.hearthsim; import com.hearthsim.card.Card; import com.hearthsim.card.CardEndTurnInterface; import com.hearthsim.card.CharacterIndex; import com.hearthsim.card.basic.spell.TheCoin; import com.hearthsim.card.minion.Minion; import com.hearthsim.exception.HSException; import com.hearthsim.model.BoardModel; import com.hearthsim.model.PlayerModel; import com.hearthsim.model.PlayerSide; import com.hearthsim.player.playercontroller.ArtificialPlayer; import com.hearthsim.results.GameRecord; import com.hearthsim.results.GameResult; import com.hearthsim.results.GameSimpleRecord; import com.hearthsim.util.HearthAction; import com.hearthsim.util.HearthAction.Verb; import com.hearthsim.util.HearthActionBoardPair; import com.hearthsim.util.factory.BoardStateFactoryBase; import com.hearthsim.util.tree.HearthTreeNode; import java.util.ArrayList; import java.util.List; public class Game { private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass()); private final static int maxTurns_ = 100; private BoardModel boardModel; private PlayerModel playerGoingFirst; private ArtificialPlayer aiForPlayerGoingFirst; private ArtificialPlayer aiForPlayerGoingSecond; public final ArrayList<HearthActionBoardPair> gameHistory = new ArrayList<>(); public Game(PlayerModel playerModel0, PlayerModel playerModel1, ArtificialPlayer ai0, ArtificialPlayer ai1) { this(playerModel0, playerModel1, ai0, ai1, false); } public Game(PlayerModel playerModel0, PlayerModel playerModel1, ArtificialPlayer ai0, ArtificialPlayer ai1, int firstPlayerId) { playerGoingFirst = playerModel0; PlayerModel playerGoingSecond = playerModel1; aiForPlayerGoingFirst = ai0; aiForPlayerGoingSecond = ai1; if (firstPlayerId == 1) { playerGoingFirst = playerModel1; playerGoingSecond = playerModel0; aiForPlayerGoingFirst = ai1; aiForPlayerGoingSecond = ai0; } log.debug("alternate play order: {}", firstPlayerId); log.debug("first player id: {}", playerGoingFirst.getPlayerId()); boardModel = new BoardModel(playerGoingFirst, playerGoingSecond); } public Game(PlayerModel playerModel0, PlayerModel playerModel1, ArtificialPlayer ai0, ArtificialPlayer ai1, boolean shufflePlayOrder) { this(playerModel0, playerModel1, ai0, ai1, shufflePlayOrder && Math.random() >= 0.5 ? 0 : 1); } public GameResult runGame() throws HSException { //the first player draws 3 cards boardModel.getCurrentPlayer().placeCardHand(0); boardModel.getCurrentPlayer().placeCardHand(1); boardModel.getCurrentPlayer().placeCardHand(2); boardModel.getCurrentPlayer().setDeckPos((byte)3); //the second player draws 4 cards boardModel.getWaitingPlayer().placeCardHand(0); boardModel.getWaitingPlayer().placeCardHand(1); boardModel.getWaitingPlayer().placeCardHand(2); boardModel.getWaitingPlayer().placeCardHand(3); boardModel.getWaitingPlayer().placeCardHand(new TheCoin()); boardModel.getWaitingPlayer().setDeckPos((byte)4); GameRecord record = new GameSimpleRecord(); record.put(0, PlayerSide.CURRENT_PLAYER, boardModel.deepCopy(), null); record.put(0, PlayerSide.CURRENT_PLAYER, boardModel.flipPlayers().deepCopy(), null); gameHistory.add(new HearthActionBoardPair(null, boardModel)); GameResult gameResult; for (int turnCount = 0; turnCount < maxTurns_; ++turnCount) { log.debug("starting turn " + turnCount); long turnStart = System.currentTimeMillis(); gameResult = playTurn(turnCount, record, aiForPlayerGoingFirst); if (gameResult != null) return gameResult; gameResult = playTurn(turnCount, record, aiForPlayerGoingSecond); if (gameResult != null) return gameResult; long turnEnd = System.currentTimeMillis(); long turnDelta = turnEnd - turnStart; if (turnDelta > aiForPlayerGoingFirst.getMaxThinkTime() / 2) { log.warn("turn took {} ms, more than half of alloted think time ({})", turnDelta, aiForPlayerGoingFirst.getMaxThinkTime()); } else { log.debug("turn took {} ms", turnDelta); } } return new GameResult(playerGoingFirst.getPlayerId(), -1, 0, record); } private GameResult playTurn(int turnCount, GameRecord record, ArtificialPlayer ai) throws HSException { boardModel = Game.beginTurn(boardModel.deepCopy()); // Deep copy here to make sure history is preserved properly gameHistory.add(new HearthActionBoardPair(new HearthAction(Verb.START_TURN), boardModel.deepCopy())); GameResult gameResult; gameResult = checkGameOver(turnCount, record); if (gameResult != null) return gameResult; List<HearthActionBoardPair> allMoves = ai.playTurn(turnCount, boardModel); if (allMoves.size() > 0) { // If allMoves is empty, it means that there was absolutely nothing the AI could do boardModel = allMoves.get(allMoves.size() - 1).board; gameHistory.addAll(allMoves); } boardModel = Game.endTurn(boardModel); record.put(turnCount + 1, PlayerSide.CURRENT_PLAYER, boardModel.deepCopy(), allMoves); gameResult = checkGameOver(turnCount, record); if (gameResult != null) return gameResult; boardModel = boardModel.flipPlayers(); gameHistory.add(new HearthActionBoardPair(new HearthAction(Verb.END_TURN), boardModel.deepCopy())); return null; } public GameResult checkGameOver(int turnCount, GameRecord record) { if (boardModel.isDead(PlayerSide.CURRENT_PLAYER)) { PlayerModel winner = boardModel.modelForSide(PlayerSide.WAITING_PLAYER); return new GameResult(playerGoingFirst.getPlayerId(), winner.getPlayerId(), turnCount + 1, record); } else if (boardModel.isDead(PlayerSide.WAITING_PLAYER)) { PlayerModel winner = boardModel.modelForSide(PlayerSide.CURRENT_PLAYER); return new GameResult(playerGoingFirst.getPlayerId(), winner.getPlayerId(), turnCount + 1, record); } return null; } public static BoardModel beginTurn(BoardModel board) throws HSException { HearthTreeNode toRet = new HearthTreeNode(board); toRet.data_.resetHand(); toRet.data_.resetMinions(); PlayerModel currentPlayer = toRet.data_.getCurrentPlayer(); PlayerModel waitingPlayer = toRet.data_.getWaitingPlayer(); for (Minion targetMinion : currentPlayer.getMinions()) { toRet = targetMinion.startTurn(PlayerSide.CURRENT_PLAYER, toRet); } for (Minion targetMinion : waitingPlayer.getMinions()) { toRet = targetMinion.startTurn(PlayerSide.WAITING_PLAYER, toRet); } toRet = BoardStateFactoryBase.handleDeadMinions(toRet); // TODO card draw will need to move into BoardModel at some point so we can react to them appropriately Card drawn = currentPlayer.drawNextCardFromDeck(); toRet.data_.applyAurasToCardInHand(PlayerSide.CURRENT_PLAYER, drawn); if (currentPlayer.getMaxMana() < 10) { currentPlayer.addMaxMana((byte) 1); } toRet.data_.resetMana(); return toRet.data_; } public static BoardModel endTurn(BoardModel board) throws HSException { HearthTreeNode toRet = new HearthTreeNode(board); PlayerModel currentPlayer = toRet.data_.getCurrentPlayer(); PlayerModel waitingPlayer = toRet.data_.getWaitingPlayer(); toRet = currentPlayer.getHero().endTurn(PlayerSide.CURRENT_PLAYER, toRet); toRet = waitingPlayer.getHero().endTurn(PlayerSide.WAITING_PLAYER, toRet); // TODO: The minions should trigger end-of-turn effects in the order that they were played for (int index = 0; index < currentPlayer.getNumMinions(); ++index) { CardEndTurnInterface targetMinion = currentPlayer.getCharacter(CharacterIndex.fromInteger(index + 1)); try { toRet = targetMinion.endTurn(PlayerSide.CURRENT_PLAYER, toRet); } catch(HSException e) { e.printStackTrace(); } } for (int index = 0; index < waitingPlayer.getNumMinions(); ++index) { CardEndTurnInterface targetMinion = waitingPlayer.getCharacter(CharacterIndex.fromInteger(index + 1)); try { toRet = targetMinion.endTurn(PlayerSide.WAITING_PLAYER, toRet); } catch(HSException e) { e.printStackTrace(); } } toRet = BoardStateFactoryBase.handleDeadMinions(toRet); return toRet.data_; } }