GameAILab2/hanabi.py

294 lines
12 KiB
Python
Raw Permalink Normal View History

2022-11-19 01:23:49 -08:00
import random
import sys
import copy
import time
GREEN = 0
YELLOW = 1
WHITE = 2
BLUE = 3
RED = 4
ALL_COLORS = [GREEN, YELLOW, WHITE, BLUE, RED]
COLORNAMES = ["green", "yellow", "white", "blue", "red"]
class Card:
def __init__(self, color, rank):
self.color = color
self.rank = rank
def isColor(self, color):
return self.color == color
def isRank(self, rank):
return self.rank == rank
def __eq__(self, other):
if other is None: return False
if type(other) == tuple:
return (self.color,self.rank) == other
return (self.color,self.rank) == (other.color,other.rank)
def __getitem__(self, idx):
if idx == 0: return self.color
return self.rank
def __str__(self):
return COLORNAMES[self.color] + " " + str(self.rank)
def __repr__(self):
return str((self.color,self.rank))
def is_useless(self, board):
2022-11-19 02:51:52 -08:00
return board[self.color].rank + 1 > self.rank
2022-11-19 01:23:49 -08:00
def is_playable(self, board):
return board[self.color].rank + 1 == self.rank
def __iter__(self):
return iter([self.color, self.rank])
COUNTS = [3,2,2,2,1]
# semi-intelligently format cards in any format
def f(something):
if type(something) == list:
return list(map(f, something))
elif type(something) == dict:
return {k: something(v) for (k,v) in something.items()}
elif type(something) == Card:
return str(something)
elif type(something) == tuple and len(something) == 2:
return (COLORNAMES[something[0]],something[1])
return something
def make_deck():
deck = []
for color in ALL_COLORS:
for rank, cnt in enumerate(COUNTS):
for i in range(cnt):
deck.append(Card(color, rank+1))
random.shuffle(deck)
return deck
def initial_knowledge():
knowledge = []
for color in ALL_COLORS:
knowledge.append(COUNTS[:])
return knowledge
def hint_color(knowledge, color, truth):
result = []
for col in ALL_COLORS:
if truth == (col == color):
result.append(knowledge[col][:])
else:
result.append([0 for i in knowledge[col]])
return result
def hint_rank(knowledge, rank, truth):
result = []
for col in ALL_COLORS:
colknow = []
for i,k in enumerate(knowledge[col]):
if truth == (i + 1 == rank):
colknow.append(k)
else:
colknow.append(0)
result.append(colknow)
return result
HINT_COLOR = 0
HINT_RANK = 1
PLAY = 2
DISCARD = 3
class Action(object):
def __init__(self, type, player=None, color=None, rank=None, card_index=None):
self.type = type
self.player = player
self.color = color
self.rank = rank
self.card_index = card_index
def __str__(self):
if self.type == HINT_COLOR:
return "hints " + str(self.player) + " about all their " + COLORNAMES[self.color] + " cards"
if self.type == HINT_RANK:
return "hints " + str(self.player) + " about all their " + str(self.rank)+"s"
if self.type == PLAY:
return "plays card at index " + str(self.card_index)
if self.type == DISCARD:
return "discards card at index " + str(self.card_index)
def __eq__(self, other):
if other is None: return False
return (self.type, self.player, self.color, self.rank, self.card_index) == (other.type, other.player, other.color, other.rank, other.card_index)
def format_card(card):
return str(card)
def format_hand(hand):
return ", ".join(map(format_card, hand))
class Game(object):
def __init__(self, players, log=sys.stdout, format=0):
self.players = players
self.hits = 3
self.hints = 8
self.current_player = 0
self.board = [Card(c,0) for c in ALL_COLORS]
self.played = []
self.deck = make_deck()
self.extra_turns = 0
self.hands = []
self.knowledge = []
self.make_hands()
self.trash = []
self.log = log
self.turn = 1
self.format = format
self.dopostsurvey = False
self.study = False
if self.format:
print(self.deck, file=self.log)
def make_hands(self):
handsize = 4
if len(self.players) < 4:
handsize = 5
for i, p in enumerate(self.players):
self.hands.append([])
self.knowledge.append([])
for j in range(handsize):
self.draw_card(i)
def draw_card(self, pnr=None):
if pnr is None:
pnr = self.current_player
if not self.deck:
return
self.hands[pnr].append(self.deck[0])
self.knowledge[pnr].append(initial_knowledge())
del self.deck[0]
def perform(self, action):
for p in self.players:
p.inform(action, self.current_player)
if format:
print("MOVE:", self.current_player, action.type, action.card_index, action.player, action.color, action.rank, file=self.log)
if action.type == HINT_COLOR:
self.hints -= 1
print(self.players[self.current_player].name, "hints", self.players[action.player].name, "about all their", COLORNAMES[action.color], "cards", "hints remaining:", self.hints, file=self.log)
print(self.players[action.player].name, "has", format_hand(self.hands[action.player]), file=self.log)
for card,knowledge in zip(self.hands[action.player],self.knowledge[action.player]):
if card.color == action.color:
for i, k in enumerate(knowledge):
if i != card.color:
for i in range(len(k)):
k[i] = 0
else:
for i in range(len(knowledge[action.color])):
knowledge[action.color][i] = 0
elif action.type == HINT_RANK:
self.hints -= 1
print(self.players[self.current_player].name, "hints", self.players[action.player].name, "about all their", action.rank, "hints remaining:", self.hints, file=self.log)
print(self.players[action.player].name, "has", format_hand(self.hands[action.player]), file=self.log)
for card,knowledge in zip(self.hands[action.player],self.knowledge[action.player]):
if card.rank == action.rank:
for k in knowledge:
for i in range(len(COUNTS)):
if i+1 != card.rank:
k[i] = 0
else:
for k in knowledge:
k[action.rank-1] = 0
elif action.type == PLAY:
card = self.hands[self.current_player][action.card_index]
print(self.players[self.current_player].name, "plays", format_card(card), end=' ', file=self.log)
if self.board[card.color][1] == card.rank-1:
self.board[card.color] = card
self.played.append(card)
if card.rank == 5:
self.hints += 1
self.hints = min(self.hints, 8)
print("successfully! Board is now", format_hand(self.board), file=self.log)
else:
self.trash.append(card)
self.hits -= 1
print("and fails. Board was", format_hand(self.board), file=self.log)
del self.hands[self.current_player][action.card_index]
del self.knowledge[self.current_player][action.card_index]
self.draw_card()
print(self.players[self.current_player].name, "now has", format_hand(self.hands[self.current_player]), file=self.log)
else:
self.hints += 1
self.hints = min(self.hints, 8)
self.trash.append(self.hands[self.current_player][action.card_index])
print(self.players[self.current_player].name, "discards", format_card(self.hands[self.current_player][action.card_index]), file=self.log)
print("trash is now", format_hand(self.trash), file=self.log)
del self.hands[self.current_player][action.card_index]
del self.knowledge[self.current_player][action.card_index]
self.draw_card()
print(self.players[self.current_player].name, "now has", format_hand(self.hands[self.current_player]), file=self.log)
def valid_actions(self):
valid = []
for i in range(len(self.hands[self.current_player])):
valid.append(Action(PLAY, card_index=i))
valid.append(Action(DISCARD, card_index=i))
if self.hints > 0:
for i, p in enumerate(self.players):
if i != self.current_player:
for color in set([card[0] for card in self.hands[i]]):
valid.append(Action(HINT_COLOR, player=i, color=color))
for rank in set([card[1] for card in self.hands[i]]):
valid.append(Action(HINT_RANK, player=i, rank=rank))
return valid
def run(self, turns=-1):
self.turn = 1
while not self.done() and (turns < 0 or self.turn < turns):
self.turn += 1
if not self.deck:
self.extra_turns += 1
hands = []
for i, h in enumerate(self.hands):
if i == self.current_player:
hands.append([])
else:
hands.append(h)
valid = self.valid_actions()
action = None
while action not in valid:
action = self.players[self.current_player].get_action(self.current_player, hands, copy.deepcopy(self.knowledge), self.trash[:], self.played[:], self.board[:], valid, self.hints, self.hits, len(self.deck))
if action not in valid:
print("Tried to perform illegal action, retrying")
self.perform(action)
self.current_player += 1
self.current_player %= len(self.players)
print("Game done, hits left:", self.hits, file=self.log)
points = self.score()
print("Points:", points, file=self.log)
return points
def score(self):
return sum([card.rank for card in self.board])
def single_turn(self):
if not self.done():
if not self.deck:
self.extra_turns += 1
hands = []
for i, h in enumerate(self.hands):
if i == self.current_player:
hands.append([])
else:
hands.append(h)
action = self.players[self.current_player].get_action(self.current_player, hands, self.knowledge, self.trash, self.played, self.board, self.valid_actions(), self.hints, self.hits, len(self.deck))
self.perform(action)
self.current_player += 1
self.current_player %= len(self.players)
def external_turn(self, action):
if not self.done():
if not self.deck:
self.extra_turns += 1
self.perform(action)
self.current_player += 1
self.current_player %= len(self.players)
def done(self):
if self.extra_turns == len(self.players) or self.hits == 0:
return True
for card in self.board:
if card.rank != 5:
return False
return True
def finish(self):
if self.format:
print("Score", self.score(), file=self.log)
self.log.close()