simple Durian
The code for playing the durian game with an AI you can implement yourself.
"""
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
logging.getLogger().setLevel(logging.INFO)
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, players:list[Player]):
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))
players = [HumanPlayer("Pieter"), FlipPlayer("Filip"), BellPlayer("Bella"), ThreeTurnPlayer('Trien')]
run_games(args.number_of_games, players)
license
Disclaimer
simple_durian.py is an independent, simplified Python
implementation inspired by Durian. It is not affiliated
with, endorsed by, or associated with Oink Games or any of
its subsidiaries. All trademarks, service marks, trade
names, trade dress, product names, and logos appearing in
this software are the property of their respective owners.
The use of these names, trademarks, and brands does not
imply endorsement.
License
This software is licensed under the MIT License. You may
obtain a copy of the License at:
https://opensource.org/license/mit
Fair Use
This software is created under the principles of fair use.
It is a transformative work that builds upon the original
board game concept for educational and non-commercial
purposes. The implementation does not replicate the
original game in its entirety and is intended to provide a
simplified version for learning and experimentation. The
use of any copyrighted material is limited and does not
impact the market value of the original work.