"""
Durian game is made by Oink Games https://oinkgames.com/
See LICENSE.txt
"""
import argparse
import random
import itertools
from collections import UserList
import logging
FRUITS = "🍈🍓🍌🍇"
class Card:
def __init__(self, left_count, left_fruit, right_count, right_fruit):
self.left_count = left_count
self.left_fruit = left_fruit
self.right_count = right_count
self.right_fruit = right_fruit
def __repr__(self):
return (
f"[{self.left_count}{self.left_fruit}|{self.right_count}{self.right_fruit}]"
)
def flip(self):
return Card(
self.right_count,
self.right_fruit,
self.left_count,
self.left_fruit,
)
class DeckEmptyException(Exception):
pass
class Deck:
def __init__(self):
fruit_counts = list(itertools.product([1, 2, 3], FRUITS))
#TODO: there can be cards with the same fruit twice
deck = list(
Card(*left, *right) for left, right in itertools.combinations(fruit_counts, 2)
)
deck = [card for card in deck if card.left_fruit != card.right_fruit]
random.shuffle(deck)
self.cards = deck
def pick(self, amount=1) -> list[Card]:
picked = self.cards[:amount]
del(self.cards[:amount])
if not picked:
raise DeckEmptyException()
return picked
class Orders(UserList):
def __init__(self, cards:list[Card]=None):
self.data = cards if cards else []
def __repr__(self):
return (
"\n 🦍 " +
"\n ✅ | ❌ \n" +
"\n".join([str(x) for x in self.data])
)
def accepted(self):
order_counts = {f:0 for f in FRUITS}
for card in self.data:
order_counts[card.left_fruit] += card.left_count
return order_counts
class Player:
visible_cards: list[Card] = None
def __init__(self, name: str = None):
self.name = name if name else self.__class__.__name__ + hex(id(self))
self.anger_chips = []
def __repr__(self):
anger = " ".join(["🦍"+"💢"*anger_chip for anger_chip in self.anger_chips])
return f"{self.name} {anger}"
def pick_or_bell(self, orders):
raise NotImplementedError("write this function yourself!")
def flip_or_not(self, card:Card):
return card
class HumanPlayer(Player):
def pick_or_bell(self, orders):
# player that always picks
action = "?"
while action not in "PB":
action = input(f"{self.name} (P)ick or (B)ell?")
action = action.upper()
return action
def flip_or_not(self, card):
action = "?"
while action not in "FN":
action = input(f"{self.name} (F)lip or (N)ot flip?")
action = action.upper()
if action == "F":
return card.flip()
else:
return card
class PickPlayer(Player):
def pick_or_bell(self, orders):
return "P"
class FlipPlayer(Player):
def pick_or_bell(self, orders):
return "P"
def flip_or_not(self, card):
return card.flip()
class BellPlayer(Player):
def pick_or_bell(self, orders):
return "B"
class RandomPlayer(Player):
def pick_or_bell(self, orders):
logging.debug("player that does random action")
class ThreeTurnPlayer(Player):
def pick_or_bell(self, orders):
if len(orders) >= 3:
return "B"
else:
return "P"
class DurianGame:
def __init__(
self,
players: list[Player],
deck:Deck,
anger_level = 0,
round = 0
):
self.players = players
self.deck = deck
self.anger_level = anger_level
self.orders = Orders()
self.round = round
self.inventory=self.deck.pick(len(self.players))
for player, card in zip(players, self.inventory):
player.visible_cards = [x for x in self.inventory if x != card]
def orders_possible(self):
inventory_counts = {f:0 for f in FRUITS}
order_counts = self.orders.accepted()
for card in self.inventory:
inventory_counts[card.left_fruit] += card.left_count
inventory_counts[card.right_fruit] += card.right_count
for fruit, count in order_counts.items():
if inventory_counts[fruit] < count:
return False
return True
def new_round(self):
self.__init__(self.players, Deck(), self.anger_level + 1, self.round + 1)
logging.debug(f"--- ROUND {self.round} ---")
logging.debug(f"inventory: {self.inventory}")
def is_game_over(self):
if max([sum(player.anger_chips) for player in self.players]) >= 7:
return True
return False
def determine_winners(self):
lowest_anger = sum(sorted(self.players, key=lambda p:sum(p.anger_chips))[0].anger_chips)
lowest_count = len(sorted(self.players, key=lambda p:len(p.anger_chips))[0].anger_chips)
return [p for p in self.players if len(p.anger_chips) == lowest_count and sum(p.anger_chips) == lowest_anger]
def run(self):
random.shuffle(self.players)
self.new_round()
previous_player = self.players[0]
while True:
for player in self.players:
action = player.pick_or_bell(self.orders)
if action == "P":
try:
picked_card = self.deck.pick()[0]
logging.debug(f"{player.name} picks {picked_card}.")
except DeckEmptyException:
logging.debug("deck empty, nobody wins")
return []
picked_card = player.flip_or_not(picked_card)
self.orders += [picked_card]
logging.debug(f"Orders:{self.orders}")
elif action == "B":
if self.orders_possible():
#manager gets angry at current player
player.anger_chips.append(self.anger_level)
logging.debug(f"{player.name} 🔔🔔🔔, but orders are possible. {player}")
else:
#manager gets angry at previous player
previous_player.anger_chips.append(self.anger_level)
logging.debug(f"{player.name} 🔔🔔🔔, and was right. {previous_player}")
if self.is_game_over():
winners = self.determine_winners()
for player in self.players:
player.__init__(player.name)
return winners
self.new_round()
break
previous_player = player
def run_games(number_of_games, verbose=False):
players = [PickPlayer("Pieter"), FlipPlayer("Filip"), BellPlayer("Bella"), ThreeTurnPlayer('Trien')]
wins = {p.name:0 for p in players}
logging.debug(wins)
for game_number in range(number_of_games):
logging.debug(f"start game {game_number}")
deck = Deck()
game = DurianGame(players, deck=deck)
winners = game.run()
logging.debug(f"winners for game{game_number}: {winners}")
for winner in winners:
wins[winner.name] += 1
logging.info("result:")
logging.info(wins)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog='Durian',
description='play a game of Durian by oink games'
)
parser.add_argument('-n', '--number_of_games', default=1, type=int)
parser.add_argument('-l', '--log_level', default="INFO", choices=list(logging.getLevelNamesMapping()))
args = parser.parse_args()
logging.getLogger().setLevel(logging.getLevelName(args.log_level))
run_games(args.number_of_games)