Skip to content

Instantly share code, notes, and snippets.

@tkmharris
Last active May 24, 2021 22:53
Show Gist options
  • Save tkmharris/3e59432f29f4227eeecd4776c5b7dffe to your computer and use it in GitHub Desktop.
Save tkmharris/3e59432f29f4227eeecd4776c5b7dffe to your computer and use it in GitHub Desktop.
Simulate a game of Dreidel
# coding: utf8
import random
class OutOfTimeError(Exception):
"""Raised when game passes maximum number of turns"""
pass
class Player:
def __init__(self, name, tokens=0):
self.name = name
self.tokens = tokens
def __str__(self):
return f"{self.name} ({self.tokens} tokens)"
def gain_tokens(self, gained_tokens):
"""Player gains {gained_tokens} tokens."""
self.tokens += gained_tokens
def lose_tokens(self, lost_tokens):
"""Player loses {lost_tokens} tokens."""
self.tokens -= lost_tokens
class Pot(Player):
def __init__(self, tokens=0):
super().__init__("Pot", tokens=tokens)
class Dreidel:
def __init__(self):
self.sides = {
u"ב (nun)": "Nisht",
u"ג (gimel)": "Ganz",
u"ה (hei)": "Halb",
u"ש (shin)": "Shtel"
}
def spin(self):
"""Spin Dreidel.
Return string, one of the four Dreidel sides (nun, gimel, hei, shin)."""
return random.choice(list(self.sides.keys()))
class DreidelGame:
def __init__(self, num_players, player_tokens, pot_tokens):
"""
Initialize Dreidel game.
num_players: int (assumed >0)
player_tokens: int
pot_tokens: int (assumed >=0)
"""
# record input params
self.num_players = num_players
self.init_player_tokens = player_tokens
self.init_pot_tokens = pot_tokens
# intialize game
if num_players > 0:
self.players = [Player(name=f"Player {i+1}", tokens=player_tokens)
for i in range(num_players)]
else:
raise ValueError("Game cannot have zero players.")
if pot_tokens >= 0:
self.pot = Pot(tokens=pot_tokens)
else:
raise ValueError("Pot cannot have negative tokens.")
self.dreidel = Dreidel()
self.num_turns = 0
def reset(self):
"""Reset game to its initial state."""
self.__init__(self.num_players, self.init_player_tokens, self.init_pot_tokens)
def remove_player(self, player):
"""Remove player from game."""
self.players.remove(player)
def ante_up(self):
"""All players add one token to the pot, remove any players without tokens."""
for player in self.players:
if player.tokens > 0:
player.lose_tokens(1)
self.pot.gain_tokens(1)
else:
self.remove_player(player)
def is_game_over(self):
"""
Returns True if only one player remains, False otherwise.
"""
if len(self.players) > 1:
return False
elif len(self.players) == 1:
return True
else:
raise ValueError("Game cannot have zero players.")
def player_spin(self, player, verbose=False):
"""
Player spins the Dreidel, player, pot & game are updated according to the result.
player: Player object
verbose: boolean,
if True, prints result of the spin.
"""
if player not in self.players:
raise PlayersError(f"{player} is not a player in this game.")
# player spins dreidel
symbol = self.dreidel.spin()
if verbose:
print(f"{player.name} spins {symbol}")
print(self.dreidel.sides[symbol])
# appropriate action takes place
if symbol == u"ב (nun)":
pass
elif symbol == u"ג (gimel)":
whole_pot = self.pot.tokens
self.pot.lose_tokens(whole_pot)
player.gain_tokens(whole_pot)
self.ante_up()
elif symbol == u"ה (hei)":
half_pot = int((self.pot.tokens + 1)/2)
self.pot.lose_tokens(half_pot)
player.gain_tokens(half_pot)
if self.pot.tokens == 0:
self.ante_up()
elif symbol == u"ש (shin)":
if player.tokens > 0:
player.lose_tokens(1)
self.pot.gain_tokens(1)
else:
self.remove_player(player)
else:
raise ValueError("Something's wrong with your dreidel")
def print_game_state(self, message=None):
"""
Prints the state of the game, preceded by an optional message.
message: string
"""
if message is not None:
print(message)
print(self.pot)
for player in self.players:
print(player)
print("\n")
def play(self, turns_limit=None, verbose=False, reset=True):
"""
Play a full game of Dreidel.
turns_limit: int/numeric,
limit on number of game turns (raise OutOfTimeError if exceeded)
verbose: boolean,
if True, prints summary of each game turn (default False)
reset: boolean,
if True, resets internal state of game on completion (default True)
return:
winner: Player, last player remaining in the game
turns: int, number of turns taken for game to end.
"""
if turns_limit is None:
turns_limit = float("inf")
# if verbose flag is True, show pot and players
if verbose:
self.print_game_state("Game intitial state")
# first ante up
self.ante_up()
if verbose:
self.print_game_state("Initial ante up")
# n.b. block below is inelegant, improve this
exit_game = False
while not exit_game:
for player in self.players:
# raise error if we pass the turn limit
if self.num_turns >= turns_limit:
raise OutOfTimeError(f"Maximum number of turns ({turns_limit}) passed.")
# if only one player remains, finish game
elif self.is_game_over():
exit_game = True
break
# otherwise next player takes their turn
else:
self.player_spin(player, verbose=verbose)
self.num_turns += 1
if verbose:
self.print_game_state(f"After turn {self.num_turns}")
# game ended
winner = self.players[0].name
turns = self.num_turns
# print results if verbose flagged
if verbose:
print(f"Winner is {winner} (number of game turns: {turns})")
# reset DreidelGame if reset flagged
if reset:
self.reset()
return winner, turns
"""
## main
if __name__=="__main__":
game = DreidelGame(num_players=4,
player_tokens=10,
pot_tokens=0)
try:
winner, turns = game.play(
verbose=False)
print(winner)
print(turns)
except OutOfTimeError:
print("Timeout")
"""
# coding: utf8
import unittest
import dreidel_game
class TestPlayer(unittest.TestCase):
def test_create_player(self):
player = dreidel_game.Player("Beauchamp")
output = (type(player) == dreidel_game.Player)
self.assertTrue(output)
def test_player_gain_tokens(self):
player = dreidel_game.Player("Coldmoon", tokens=5)
player.gain_tokens(5)
output = player.tokens
expected = 10
self.assertEqual(output, expected)
def test_player_lose_tokens(self):
player = dreidel_game.Player("Waverhouse", tokens=8)
player.lose_tokens(1)
output = player.tokens
expected = 7
self.assertEqual(output, expected)
def test_print_player(self):
player = dreidel_game.Player("Sneaze")
output = str(player)
expected = "Sneaze (0 tokens)"
self.assertEqual(output, expected)
class TestDreidel(unittest.TestCase):
def test_create_dreidel(self):
dreidel = dreidel_game.Dreidel()
output = (type(dreidel) == dreidel_game.Dreidel)
self.assertTrue(output)
def test_dreidel_spin(self):
dreidel = dreidel_game.Dreidel()
output = dreidel.spin()
expected = [
u"ב (nun)",
u"ג (gimel)",
u"ה (hei)",
u"ש (shin)"
]
self.assertIn(output, expected)
# TODO:
# write tests for dreidel game
if __name__ == '__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment