Skip to content

Commit

Permalink
Merge pull request #94 from evanofslack/result
Browse files Browse the repository at this point in the history
Return results from game and simulation
  • Loading branch information
evanofslack authored Feb 5, 2023
2 parents 2bbb2f7 + e4b9631 commit a8ba0d9
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 89 deletions.
15 changes: 12 additions & 3 deletions examples/bot_game.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""
Run a single game between two bots.
Run a single game between two bots.
Here we do not log the game to console.
Instead, we save the game output to a file (pyminion.log).
We only print the result of the game to console.
"""

Expand All @@ -14,8 +18,13 @@
game = Game(
players=[bm, bm_ultimate],
expansions=[base_set],
kingdom_cards=[smithy],
kingdom_cards=[smithy], # specific cards to add to the kingdom
random_order=True, # players start in random order
log_stdout=False, # log the output to stdout
log_file=True, # log the output to file
log_file_name="pyminion.log", # name of file to log output
)

if __name__ == "__main__":
game.play()
result = game.play()
print(result)
17 changes: 12 additions & 5 deletions examples/human_game.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
"""
Play a game through the terminal. Either by yourself, with another human, or against a bot.
Play a game through the terminal.
Either by yourself, with another human, or against a bot.
"""

from pyminion.bots.examples import BigMoney
from pyminion.expansions.base import base_set
from pyminion.expansions.base import artisan, bandit, base_set, witch
from pyminion.game import Game
from pyminion.players import Human

human = Human(player_id="Human")
bot = BigMoney(player_id="Bot 1")
bm = BigMoney(player_id="Big Money")

game = Game(players=[human, bot], expansions=[base_set])
game = Game(
players=[human, bm],
expansions=[base_set],
kingdom_cards=[artisan, bandit, witch], # specific cards to add to the kingdom
random_order=True, # players start in random order
)

if __name__ == "__main__":
game.play()
result = game.play()
14 changes: 11 additions & 3 deletions examples/simulation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Simulate multiple games between two or more bots.
Simulate multiple games between two or more bots.
"""
from pyminion.bots.examples import BigMoney, BigMoneySmithy
Expand All @@ -11,9 +11,17 @@
bm_smithy = BigMoneySmithy()


game = Game(players=[bm, bm_smithy], expansions=[base_set], kingdom_cards=[smithy])
game = Game(
players=[bm, bm_smithy],
expansions=[base_set],
kingdom_cards=[smithy],
random_order=False,
log_stdout=False,
log_file=False,
)

sim = Simulator(game, iterations=1000)

if __name__ == "__main__":
sim.run()
result = sim.run()
print(result)
19 changes: 4 additions & 15 deletions pyminion/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
__version__ = "0.2.2"
__author__ = "Evan Slack"


# INITIALIZE LOGGER

import logging

# initalize logger with no handler.
# handlers are added in the `Game` init
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

# Create handler
c_handler = logging.StreamHandler()
c_handler.setLevel(logging.DEBUG)

# Create formatter and add to handler
c_format = logging.Formatter("%(message)s")
c_handler.setFormatter(c_format)

# Add handler to the logger
logger.addHandler(c_handler)
logger.setLevel((logging.INFO))
logger.addHandler(logging.NullHandler())
122 changes: 93 additions & 29 deletions pyminion/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pyminion.expansions.base import (copper, curse, duchy, estate, gold,
province, silver)
from pyminion.players import Player
from pyminion.result import GameOutcome, GameResult, PlayerSummary

logger = logging.getLogger()

Expand All @@ -22,7 +23,8 @@ class Game:
kingdom_cards: Specify any specific cards to be used in the supply.
start_deck: List of cards each player will start the game with. Default = [7 Coppers + 3 Estates].
random_order: If True, scrambles the order of players (to offset first player advantage).
use_logger: If True, logs the game to a log file.
log_stdout: If True, logs game to stdout.
log_file: If True, logs game to log file.
log_file_name: Name of the file to be logged to. Default = "game.log"
"""
Expand All @@ -34,7 +36,8 @@ def __init__(
kingdom_cards: Optional[List[Card]] = None,
start_deck: Optional[List[Card]] = None,
random_order: bool = True,
use_logger: bool = True,
log_stdout: bool = True,
log_file: bool = False,
log_file_name: str = "game.log",
):

Expand All @@ -49,7 +52,15 @@ def __init__(
self.random_order = random_order
self.trash = Trash()

if use_logger:
if log_stdout:
# Set up a handler that logs to stdout
c_handler = logging.StreamHandler()
c_handler.setLevel(logging.INFO)
c_format = logging.Formatter("%(message)s")
c_handler.setFormatter(c_format)
logger.addHandler(c_handler)

if log_file:
# Set up a handler that dumps the log to a file
f_handler = logging.FileHandler(log_file_name, mode="w")
f_handler.setLevel(logging.INFO)
Expand Down Expand Up @@ -113,7 +124,8 @@ def _create_basic_piles(self) -> List[Pile]:

def _create_kingdom_piles(self) -> List[Pile]:
"""
Create the kingdom piles that vary from kingdom to kingdom. This should be 10 piles each with 10 cards.
Create the kingdom piles that vary from kingdom to kingdom.
This should be 10 piles each with 10 cards.
"""
PILE_LENGTH: int = 10
Expand Down Expand Up @@ -151,7 +163,9 @@ def _create_kingdom_piles(self) -> List[Pile]:

def _create_supply(self) -> Supply:
"""
Create a supply consisting of the basic cards avaliable in every kingdom as well as the kingdom specific cards.
Create a supply consisting of basic cards
avaliable in every kingdom as well
as the kingdom specific cards.
"""

Expand Down Expand Up @@ -199,53 +213,103 @@ def is_over(self) -> bool:

return False

def play(self):
def play(self) -> GameResult:
self.start()
while True:
for player in self.players:
player.take_turn(self)
if self.is_over():
self.get_stats()
return
result = self.summerize_game()
logging.info(f"\n{result}")
return result

def get_winner(self) -> Optional[Player]:
def get_winners(self) -> List[Player]:
"""
The player with the most victory points wins.
If the highest scores are tied at the end of the game,
the tied player who has had the fewest turns wins the game.
If the tied players have had the same number of turns, they tie.
Returns the winning player or None if there is a tie.
Returns a list of players. If there is a single player in
the list, that is the sole winner. If there are multiple players
in the list, they are have tied for first.
"""
# if one player only, they win by default
if len(self.players) == 1:
return self.players[0]
return [self.players[0]]

# temporarily set first player as winner
high_score = self.players[0].get_victory_points()
winner = self.players[0]
tie = False
winners = [self.players[0]]

# iterate the rest of the players in the game
for player in self.players[1:]:
score = player.get_victory_points()

# if this player scored more,
# mark them as winner and high score
if score > high_score:
high_score = score
winner = player
winners = [player]

# if scores are equal
elif score == high_score:
if player.turns < winner.turns:
winner = player
tie = False
elif player.turns == winner.turns:
tie = True
return None if tie else winner

def get_stats(self):
if winner := self.get_winner():
logger.info(f"\n{winner} won in {winner.turns} turns!")
else:
logger.info(f"\nGame ended in a tie after {self.players[0].turns} turns")

for player in self.players:
logger.info(
f"\n\nPlayer: {player} \nScore: {player.get_victory_points()} \nDeck: {DeckCounter(player.get_all_cards())}"
# players tie if number of turns is equal
if player.turns == winners[0].turns:
winners.append(player)

# otherwise, player with fewer turns wins
elif player.turns < winners[0].turns:
winners = [player]

# note
# we can compare to just the first player in winners,
# because if there were multiple players in winners
# they would have equal score and turns

return winners

def summerize_game(self) -> GameResult:
"""
Called at the end of the game,
this creates a summary of the game
"""

player_summaries = []
winners = self.get_winners()

for order, player in enumerate(self.players):

# player won
if player in winners and len(winners) == 1:
result = GameOutcome.win

# player tied
elif player in winners:
result = GameOutcome.tie

# player lost
else:
result = GameOutcome.loss

summary = PlayerSummary(
player=player,
result=result,
score=player.get_victory_points(),
turns=player.turns,
shuffles=player.shuffles,
turn_order=order + 1,
deck=DeckCounter(player.get_all_cards()),
)
player_summaries.append(summary)

game_result = GameResult(
game=self,
turns=winners[0].turns,
winners=winners,
player_summaries=player_summaries,
)
return game_result
1 change: 1 addition & 0 deletions pyminion/players.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def reset(self):
"""
self.turns = 0
self.shuffles = 0
self.deck.cards = []
self.discard_pile.cards = []
self.hand.cards = []
Expand Down
Loading

0 comments on commit a8ba0d9

Please sign in to comment.