def test_fiendish_servant_no_target(self): game = setup_game(124, "fiendishServant | minion") game.battle(1, 0) self.maxDiff = None self.assertEqual( game.test_log, "Started combat between Player 0 and Player 1.\n" "2/1 Fiendish Servant (Deathrattle) attacks 1/1 Dummy Minion.\n" "1/-1 Dummy Minion took 2 damage.\n" "2/0 Fiendish Servant (Deathrattle) took 1 damage.\n" "1/-1 Dummy Minion died.\n" "2/0 Fiendish Servant (Deathrattle) died.\n" "2/x Fiendish Servant (Deathrattle) triggers its deathrattle effect.\n" "Combat is over.\n" "Player 0 and Player 1 tied.\n")
def test_fiendish_servant(self): game = setup_game(123, "fiendishServant, minion | minion (taunt)") game.battle(1, 0) self.maxDiff = None self.assertEqual( game.test_log, "Started combat between Player 0 and Player 1.\n" "2/1 Fiendish Servant (Deathrattle) attacks 1/1 Dummy Minion (Taunt).\n" "1/-1 Dummy Minion (Taunt) took 2 damage.\n" "2/0 Fiendish Servant (Deathrattle) took 1 damage.\n" "2/0 Fiendish Servant (Deathrattle) died.\n" "1/-1 Dummy Minion (Taunt) died.\n" "2/x Fiendish Servant (Deathrattle) triggers its deathrattle effect.\n" "Buffed 3/1 Dummy Minion with (2/0).\n" "Combat is over.\n" "Player 0 dealt 2 damage to Player 1.\n")
def test_golden_fiendish_servant(self): game = setup_game(123, "4/2 golden fiendishServant, minion | 2/2 minion") game.battle(1, 0) self.maxDiff = None self.assertEqual( game.test_log, "Started combat between Player 0 and Player 1.\n" "4/2 golden Fiendish Servant (Deathrattle) attacks 2/2 Dummy Minion.\n" "2/-2 Dummy Minion took 4 damage.\n" "4/0 golden Fiendish Servant (Deathrattle) took 2 damage.\n" "4/0 golden Fiendish Servant (Deathrattle) died.\n" "2/-2 Dummy Minion died.\n" "4/x golden Fiendish Servant (Deathrattle) triggers its deathrattle effect.\n" "Buffed 5/1 Dummy Minion with (4/0).\n" "Buffed 9/1 Dummy Minion with (4/0).\n" "Combat is over.\n" "Player 0 dealt 2 damage to Player 1.\n")
def test_acolythe_of_cthun(self): game = setup_game(123, "acolytheOfCThun | 2/5 minion") game.battle(1, 0) self.maxDiff = None self.assertEqual( game.test_log, "Started combat between Player 0 and Player 1.\n" "2/5 Dummy Minion attacks 2/2 Acolythe of C´Thun (Taunt, Reborn).\n" "2/0 Acolythe of C´Thun (Taunt, Reborn) took 2 damage.\n" "2/3 Dummy Minion took 2 damage.\n" "2/0 Acolythe of C´Thun (Taunt, Reborn) died.\n" "2/x Acolythe of C´Thun (Taunt, Reborn) gets reborn.\n" "2/x Acolythe of C´Thun (Taunt, Reborn) summoned 2/1 Acolythe of C´Thun (Taunt) at position 1.\n" "2/1 Acolythe of C´Thun (Taunt) attacks 2/3 Dummy Minion.\n" "2/1 Dummy Minion took 2 damage.\n" "2/-1 Acolythe of C´Thun (Taunt) took 2 damage.\n" "2/-1 Acolythe of C´Thun (Taunt) died.\n" "Combat is over.\n" "Player 1 dealt 2 damage to Player 0.\n")
def test_golden_acolythe_of_cthun(self): game = setup_game(123, "4/4 golden acolytheOfCThun | 4/9 minion") game.battle(1, 0) self.maxDiff = None self.assertEqual( game.test_log, "Started combat between Player 0 and Player 1.\n" "4/9 Dummy Minion attacks 4/4 golden Acolythe of C´Thun (Taunt, Reborn).\n" "4/0 golden Acolythe of C´Thun (Taunt, Reborn) took 4 damage.\n" "4/5 Dummy Minion took 4 damage.\n" "4/0 golden Acolythe of C´Thun (Taunt, Reborn) died.\n" "4/x golden Acolythe of C´Thun (Taunt, Reborn) gets reborn.\n" "4/x golden Acolythe of C´Thun (Taunt, Reborn) summoned 4/1 golden Acolythe of C´Thun (Taunt) at position 1.\n" "4/1 golden Acolythe of C´Thun (Taunt) attacks 4/5 Dummy Minion.\n" "4/1 Dummy Minion took 4 damage.\n" "4/-3 golden Acolythe of C´Thun (Taunt) took 4 damage.\n" "4/-3 golden Acolythe of C´Thun (Taunt) died.\n" "Combat is over.\n" "Player 1 dealt 2 damage to Player 0.\n")
def test_attack_order(self): game = setup_game( 123, "1/199 minion, 1/299 minion, 1/399 minion, 1/499 minion, 1/599 minion, 1/699 minion | " "1/1 minion, 2/1 minion, 3/1 minion, 4/1 minion, 5/1 minion, 6/1 minion, 7/1 minion" ) game.battle(1, 0) self.maxDiff = None self.assertEqual( game.test_log, "Started combat between Player 0 and Player 1.\n" "1/1 Dummy Minion attacks 1/199 Dummy Minion.\n" "1/198 Dummy Minion took 1 damage.\n" "1/0 Dummy Minion took 1 damage.\n" "1/0 Dummy Minion died.\n" "1/198 Dummy Minion attacks 3/1 Dummy Minion.\n" "3/0 Dummy Minion took 1 damage.\n" "1/195 Dummy Minion took 3 damage.\n" "3/0 Dummy Minion died.\n" "2/1 Dummy Minion attacks 1/195 Dummy Minion.\n" "1/193 Dummy Minion took 2 damage.\n" "2/0 Dummy Minion took 1 damage.\n" "2/0 Dummy Minion died.\n" "1/299 Dummy Minion attacks 7/1 Dummy Minion.\n" "7/0 Dummy Minion took 1 damage.\n" "1/292 Dummy Minion took 7 damage.\n" "7/0 Dummy Minion died.\n" "4/1 Dummy Minion attacks 1/399 Dummy Minion.\n" "1/395 Dummy Minion took 4 damage.\n" "4/0 Dummy Minion took 1 damage.\n" "4/0 Dummy Minion died.\n" "1/395 Dummy Minion attacks 5/1 Dummy Minion.\n" "5/0 Dummy Minion took 1 damage.\n" "1/390 Dummy Minion took 5 damage.\n" "5/0 Dummy Minion died.\n" "6/1 Dummy Minion attacks 1/390 Dummy Minion.\n" "1/384 Dummy Minion took 6 damage.\n" "6/0 Dummy Minion took 1 damage.\n" "6/0 Dummy Minion died.\n" "Combat is over.\n" "Player 0 dealt 7 damage to Player 1.\n")
def test_attack_order_multiple_rounds(self): game = setup_game( 123, "1/30 minion, 2/30 minion, 3/30 minion | 4/30 minion, 5/30 minion, 6/30 minion" ) game.battle(1, 0) self.maxDiff = None self.assertEqual( game.test_log, "Started combat between Player 0 and Player 1.\n" "4/30 Dummy Minion attacks 1/30 Dummy Minion.\n" "1/26 Dummy Minion took 4 damage.\n" "4/29 Dummy Minion took 1 damage.\n" "1/26 Dummy Minion attacks 6/30 Dummy Minion.\n" "6/29 Dummy Minion took 1 damage.\n" "1/20 Dummy Minion took 6 damage.\n" "5/30 Dummy Minion attacks 1/20 Dummy Minion.\n" "1/15 Dummy Minion took 5 damage.\n" "5/29 Dummy Minion took 1 damage.\n" "2/30 Dummy Minion attacks 4/29 Dummy Minion.\n" "4/27 Dummy Minion took 2 damage.\n" "2/26 Dummy Minion took 4 damage.\n" "6/29 Dummy Minion attacks 1/15 Dummy Minion.\n" "1/9 Dummy Minion took 6 damage.\n" "6/28 Dummy Minion took 1 damage.\n" "3/30 Dummy Minion attacks 5/29 Dummy Minion.\n" "5/26 Dummy Minion took 3 damage.\n" "3/25 Dummy Minion took 5 damage.\n" "4/27 Dummy Minion attacks 2/26 Dummy Minion.\n" "2/22 Dummy Minion took 4 damage.\n" "4/25 Dummy Minion took 2 damage.\n" "1/9 Dummy Minion attacks 6/28 Dummy Minion.\n" "6/27 Dummy Minion took 1 damage.\n" "1/3 Dummy Minion took 6 damage.\n" "5/26 Dummy Minion attacks 2/22 Dummy Minion.\n" "2/17 Dummy Minion took 5 damage.\n" "5/24 Dummy Minion took 2 damage.\n" "2/17 Dummy Minion attacks 5/24 Dummy Minion.\n" "5/22 Dummy Minion took 2 damage.\n" "2/12 Dummy Minion took 5 damage.\n" "6/27 Dummy Minion attacks 2/12 Dummy Minion.\n" "2/6 Dummy Minion took 6 damage.\n" "6/25 Dummy Minion took 2 damage.\n" "3/25 Dummy Minion attacks 4/25 Dummy Minion.\n" "4/22 Dummy Minion took 3 damage.\n" "3/21 Dummy Minion took 4 damage.\n" "4/22 Dummy Minion attacks 2/6 Dummy Minion.\n" "2/2 Dummy Minion took 4 damage.\n" "4/20 Dummy Minion took 2 damage.\n" "1/3 Dummy Minion attacks 5/22 Dummy Minion.\n" "5/21 Dummy Minion took 1 damage.\n" "1/-2 Dummy Minion took 5 damage.\n" "1/-2 Dummy Minion died.\n" "5/21 Dummy Minion attacks 3/21 Dummy Minion.\n" "3/16 Dummy Minion took 5 damage.\n" "5/18 Dummy Minion took 3 damage.\n" "2/2 Dummy Minion attacks 4/20 Dummy Minion.\n" "4/18 Dummy Minion took 2 damage.\n" "2/-2 Dummy Minion took 4 damage.\n" "2/-2 Dummy Minion died.\n" "6/25 Dummy Minion attacks 3/16 Dummy Minion.\n" "3/10 Dummy Minion took 6 damage.\n" "6/22 Dummy Minion took 3 damage.\n" "3/10 Dummy Minion attacks 4/18 Dummy Minion.\n" "4/15 Dummy Minion took 3 damage.\n" "3/6 Dummy Minion took 4 damage.\n" "4/15 Dummy Minion attacks 3/6 Dummy Minion.\n" "3/2 Dummy Minion took 4 damage.\n" "4/12 Dummy Minion took 3 damage.\n" "3/2 Dummy Minion attacks 6/22 Dummy Minion.\n" "6/19 Dummy Minion took 3 damage.\n" "3/-4 Dummy Minion took 6 damage.\n" "3/-4 Dummy Minion died.\n" "Combat is over.\n" "Player 1 dealt 4 damage to Player 0.\n")
def execute_commandline_input(self, _): cmd = self.command.get() self.command.set("") self.always_add_to_log(cmd, is_cmd=True) split = cmd.split(" ") if split[0] == "help": if len(split) != 1: self.always_add_to_log("\"help\" doesn't need parameters.") return self.always_add_to_log( "Help:\n" " Control Commands:\n" " newgame: Starts a new game.\n" " setupgame seed (args): Complex command to setup a specific game.\n" " player x: Changes the Player you control.\n" " startturn: Skips straight to the start of the next turn.\n" " endturn: Triggers all \"end of turn\" events.\n" " battle x (mode): Battle player x (mode = fast (default) / slow / debug).\n" " step: Does a Step in debug-mode of battle.\n" " bobsbuddy x (iter.): Finds combat outcome percentages against Player x.\n" " combatphase: Does a full combat-phase.\n" " timeisup: Ends this turn, does a combatphase and starts the next turn.\n" " get x: gets you a minion (e.g. x = 1/1 golden redWhelp (taunt,plants))\n" " history: Shows active players combat history.\n" " standings: Shows current standings.\n" " log x: En-/Disable logging (x = 'on'/'off').\n" " pool: Shows what is left in the pool.\n" " Player Commands:\n" " play (x) (y) (z): Plays card in hand position x to board position\n" " y targeting z (defaults = 0).\n" " move x y: Moves minion from position x by y to the right.\n" " buy x: Buys the minion from shop position x.\n" " sell (x): Sells the minion on position x (default = 0)\n" " tierup: Levels the tavern up.\n" " roll: Rerolls the taverns offers.\n" " freeze: (Un-)freezes the tavern.\n" " A.I. Commands:\n" " aiload: Loads agents from files in /Skynet/V1/AgentModels.\n" " aisave: Saves agents to files in /Skynet/V1/AgentModels.\n" " aitrain x (y): Trains with x moves per turn. Lasts y games or until aistop.\n" " aistop: Stops the training.\n" " agent x: Chooses active agent (x = 0-7).\n" " aieval x: Active agent evaluates actions (x = moves left before combat).\n" " aimove x: Active agent performs one action (x = moves left before combat).\n" " aiturn x: Active agent performs one full turn (x = moves per turn).\n" ) elif split[0] == "newgame": if len(split) != 1: self.always_add_to_log("\"newgame\" doesn't need parameters.") return self.game = Game(self) self.always_add_to_log("Started a new game.") self.show_active_player() # setupgame 1337 3/3 alleycat (taunt) | | 4/2 golden fiendishServant (taunt,deathrattle), baronRivendare elif split[0] == "setupgame": if not 2 <= len(split) or not split[1].isnumeric(): self.always_add_to_log( "\"setupgame\" needs 1 - 2 parameters (seed and game state)." ) return if len(split) == 2: self.game = Game(self, split[1]) else: args = cmd.removeprefix("setupgame " + split[1] + " ") self.game = setup_game(int(split[1]), args, mf=self) self.show_active_player() elif split[0] == "player": if len(split) != 2 or not split[1].isnumeric() or not 0 <= int( split[1]) <= 7: self.always_add_to_log( "\"player\" needs exactly one parameter between 0 and 7.") return self.game.choose_player(int(split[1])) self.show_active_player() elif split[0] == "startturn": if len(split) != 1: self.always_add_to_log( "\"startturn\" doesn't need parameters.") return self.game.start_turn() self.show_active_player() elif split[0] == "endturn": if len(split) != 1: self.always_add_to_log("\"endturn\" doesn't need parameters.") return self.game.end_turn() self.show_active_player() elif split[0] == "battle": if not 2 <= len(split) <= 3: self.always_add_to_log( "\"battle\" command needs 1 or 2 parameters.") return if not split[1].isnumeric() or not 0 <= int(split[1]) <= 7: self.always_add_to_log( "First parameter should be the opponents player number (0-7)." ) return if len(split) == 3 and split[2] != "fast" and split[ 2] != "slow" and split[2] != "debug": self.always_add_to_log( "Second optional parameter should be the mode ('fast', 'slow' or 'debug')." ) return if len(split) == 2: self.game.battle(int(split[1])) else: self.game.battle(int(split[1]), mode=split[2]) if len(split) != 3 or split[2] != "debug": self.show_active_player() elif split[0] == "step": if self.game.curCombat is None: self.always_add_to_log("No combat is happening right now.") return if len(split) != 1: self.always_add_to_log("\"step\" doesn't need parameters.") return self.game.step() if self.game.curCombat is not None: self.show_on_monitor(str(self.game.curCombat)) else: self.show_active_player() elif split[0] == "bobsbuddy": if not 2 <= len(split) <= 3: self.always_add_to_log( "\"bobsbuddy\" command needs 1 or 2 parameters.") return if not split[1].isnumeric() or not 0 <= int(split[1]) <= 7: self.always_add_to_log( "First parameter should be the opponents player number (0-7)." ) return if len(split) == 3 and not split[2].isnumeric(): self.always_add_to_log( "Second optional parameter should be the number of iterations." ) return if len(split) == 2: self.game.bobs_buddy(int(split[1])) else: self.game.bobs_buddy(int(split[1]), iterations=int(split[2])) elif split[0] == "combatphase": if len(split) != 1: self.always_add_to_log( "\"combatphase\" doesn't need parameters.") return self.game.combat_phase() self.show_active_player() elif split[0] == "timeisup": if len(split) != 1: self.always_add_to_log("\"timeisup\" doesn't need parameters.") return self.game.time_is_up() self.show_active_player() elif split[0] == "get": if not len(split) >= 2: self.always_add_to_log("\"get\" needs 1 parameter.") return args = cmd.removeprefix("get ") self.game.cheat_in_minion(args) self.show_active_player() elif split[0] == "history": if len(split) != 1: self.always_add_to_log("\"history\" doesn't need parameters.") return self.always_add_to_log(self.game.activePlayer.get_history()) elif split[0] == "standings": if len(split) != 1: self.always_add_to_log( "\"standings\" doesn't need parameters.") return self.always_add_to_log(self.game.get_standings()) elif split[0] == "log": if not 1 <= len(split) <= 2: self.always_add_to_log("\"log\" needs 0 or 1 parameters.") return if len(split) == 1: if self.doLog: self.always_add_to_log("Logging is enabled.") else: self.always_add_to_log("Logging is disabled.") else: if split[1] == "on": self.doLog = True self.always_add_to_log("Logging enabled.") elif split[1] == "off": self.doLog = False self.always_add_to_log("Logging disabled.") else: self.always_add_to_log( "Argument of \"log\" can be None, 'on' or 'off'.") elif split[0] == "pool": if len(split) != 1: self.always_add_to_log("\"pool\" doesn't need parameters.") return self.always_add_to_log(str(self.game.minionPool)) # ############################# Active Players Commands ############################# # elif split[0] == "play": if not 1 <= len(split) <= 4: self.always_add_to_log("\"play\" needs 0-3 parameters.") return if len(split) >= 2 and (not split[1].isnumeric() or not 0 <= int(split[1]) <= 9): self.always_add_to_log( "First parameter should be the cards index in hand (0-9).") return if len(split) >= 3 and (not split[2].isnumeric() or not 0 <= int(split[2]) <= 6): self.always_add_to_log( "Second parameter should be the cards destination index (0-6)." ) return if len(split) == 4 and (not split[3].isnumeric() or not 0 <= int(split[3]) <= 6): self.always_add_to_log( "Third parameter should be the battlecry targets index (0-6)." ) return if len(split) == 1: self.game.activePlayer.play() elif len(split) == 2: self.game.activePlayer.play(int(split[1])) elif len(split) == 3: self.game.activePlayer.play(int(split[1]), int(split[2])) else: self.game.activePlayer.play(int(split[1]), int(split[2]), int(split[3])) self.show_active_player() elif split[0] == "move": if not len(split) == 3 or not split[1].isnumeric() or not 0 <= int(split[1]) <= 6 \ or not split[2].lstrip('-').isnumeric() or not -6 <= int(split[2]) <= 6: self.always_add_to_log( "\"move\" needs exactly 2 parameters (0-6 and (-6)-6).") return self.game.activePlayer.move(int(split[1]), int(split[2])) self.show_active_player() elif split[0] == "buy": if len(split) != 2 or not split[1].isnumeric() or not 0 <= int( split[1]) <= 6: self.always_add_to_log( "\"buy\" needs exactly one parameter between 0 and 6.") return self.game.activePlayer.buy(int(split[1])) self.show_active_player() elif split[0] == "sell": if len(split) != 2 or not split[1].isnumeric() or not 0 <= int( split[1]) <= 6: self.always_add_to_log( "\"sell\" needs exactly one parameter between 0 and 6.") return self.game.activePlayer.sell(int(split[1])) self.show_active_player() elif split[0] == "tierup": if len(split) != 1: self.always_add_to_log("\"tierup\" doesn't need parameters.") return self.game.activePlayer.tier_up() self.show_active_player() elif split[0] == "roll": if len(split) != 1: self.always_add_to_log("\"roll\" doesn't need parameters.") return self.game.activePlayer.roll() self.show_active_player() elif split[0] == "freeze": if len(split) != 1: self.always_add_to_log("\"freeze\" doesn't need parameters.") return self.game.activePlayer.freeze() self.show_active_player() elif split[0] == "choose": if len(split) != 2 or not split[1].isnumeric(): self.always_add_to_log( "\"choose\" needs exactly one parameter (index of the choice)." ) return self.game.activePlayer.choose(int(split[1])) self.show_active_player() # ############################# A.I. Commands ############################# # elif split[0] == "aiload": if len(split) != 1: self.always_add_to_log("\"aiload\" doesn't need parameters.") return self.dojo.load_agents() self.always_add_to_log( "Loaded agents from /Skynet/V1/AgentModels.") elif split[0] == "aisave": if len(split) != 1: self.always_add_to_log("\"aisave\" doesn't need parameters.") return self.dojo.save_agents() self.always_add_to_log("Saved agents to /Skynet/V1/AgentModels.") elif split[0] == "aitrain": if not 2 <= len(split) <= 3: self.always_add_to_log( "\"aitrain\" command needs 1 or 2 parameters.") return if not split[1].isnumeric() or 1 > int(split[1]): self.always_add_to_log( "First parameter should be the number of moves per turn.") return if len(split) == 3 and (not split[2].isnumeric() or 1 > int(split[2])): self.always_add_to_log( "Second optional parameter should be the number of games.") return no_moves = int(split[1]) no_games = None if len(split) == 3: no_games = int(split[2]) t = threading.Thread(target=self.dojo.train_agents, args=(no_moves, no_games)) if no_games is None: self.always_add_to_log("Starting training with " + split[1] + " moves per turn.") else: self.always_add_to_log("Starting training with " + split[1] + " moves per turn and " + split[2] + " games.") t.start() elif split[0] == "aistop": if len(split) != 1: self.always_add_to_log("\"aistop\" doesn't need parameters.") return self.dojo.continue_training = False self.always_add_to_log("Stopped training.") elif split[0] == "agent": if len(split) != 2 or not split[1].isnumeric() or not 0 <= int( split[1]) <= 7: self.always_add_to_log( "\"agent\" needs exactly 1 parameter between 0 and 7.") return self.active_agent = IgnorantAgent(self.dojo.agents[int( split[1])].q_eval) self.always_add_to_log("You're now using agent " + split[1] + ".") elif split[0] == "aieval": if len(split) != 2 or not split[1].isnumeric(): self.always_add_to_log( "\"aieval\" needs exactly 1 parameter (moves left until combat)." ) return observation = get_observation(int(split[1]), self.game.activePlayer) action_qualities = self.active_agent.evaluate_actions(observation) self.always_add_to_log(action_no_to_string(action_qualities)) elif split[0] == "aimove": if len(split) != 2 or not split[1].isnumeric(): self.always_add_to_log( "\"aimove\" needs exactly 1 parameter (moves left until combat)." ) return observation = get_observation(int(split[1]), self.game.activePlayer) action_no = self.active_agent.choose_action(observation) execute_action(self.game.activePlayer, action_no) self.show_active_player() elif split[0] == "aiturn": if len(split) != 2 or not split[1].isnumeric(): self.always_add_to_log( "\"aiturn\" needs exactly 1 parameter (moves per turn).") return moves = int(split[1]) for i in range(moves, 0, -1): observation = get_observation(i, self.game.activePlayer) action_no = self.active_agent.choose_action(observation) execute_action(self.game.activePlayer, action_no) self.show_active_player() else: self.always_add_to_log("Invalid command.")