diff --git a/agent.py b/agent.py new file mode 100644 index 0000000..8979106 --- /dev/null +++ b/agent.py @@ -0,0 +1,28 @@ +import random + +class Agent: + def __init__(self, name, pnr): + self.name = name + self.explanation = [] + def get_action(self, nr, hands, knowledge, trash, played, board, valid_actions, hints, hits, cards_left): + return random.choice(valid_actions) + def inform(self, action, player): + pass + def get_explanation(self): + return self.explanation + +agent_types = {} + +def register(id, name, agent): + agent_types[id] = (name,agent) + +register("random", "Random Player", Agent) + +def get(id): + return agent_types[id] + +def make(id, *args, **kwargs): + return agent_types[id][1](*args, **kwargs) + +def ids(): + return list(agent_types.keys()) \ No newline at end of file diff --git a/agents/__init__.py b/agents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agents/osawa.py b/agents/osawa.py new file mode 100644 index 0000000..1f344ac --- /dev/null +++ b/agents/osawa.py @@ -0,0 +1,136 @@ +from hanabi import * +import util +import agent +import random + +class InnerStatePlayer(agent.Agent): + def __init__(self, name, pnr): + self.name = name + self.explanation = [] + def get_action(self, nr, hands, knowledge, trash, played, board, valid_actions, hints, hits, cards_left): + my_knowledge = knowledge[nr] + + potential_discards = [] + for i,k in enumerate(my_knowledge): + if util.is_playable(k, board): + return Action(PLAY, card_index=i) + if util.is_useless(k, board): + potential_discards.append(i) + + if potential_discards: + return Action(DISCARD, card_index=random.choice(potential_discards)) + + if hints > 0: + for player,hand in enumerate(hands): + if player != nr: + for card_index,card in enumerate(hand): + if card.is_playable(board): + if random.random() < 0.5: + return Action(HINT_COLOR, player=player, color=card.color) + return Action(HINT_RANK, player=player, rank=card.rank) + + hints = util.filter_actions(HINT_COLOR, valid_actions) + util.filter_actions(HINT_RANK, valid_actions) + return random.choice(hints) + + return random.choice(util.filter_actions(DISCARD, valid_actions)) + +def format_hint(h): + if h == HINT_COLOR: + return "color" + return "rank" + +class OuterStatePlayer(agent.Agent): + def __init__(self, name, pnr): + self.name = name + self.hints = {} + self.pnr = pnr + self.explanation = [] + def get_action(self, nr, hands, knowledge, trash, played, board, valid_actions, hints, hits, cards_left): + for player,hand in enumerate(hands): + for card_index,_ in enumerate(hand): + if (player,card_index) not in self.hints: + self.hints[(player,card_index)] = set() + known = [""]*5 + for h in self.hints: + pnr, card_index = h + if pnr != nr: + known[card_index] = str(list(map(format_hint, self.hints[h]))) + self.explanation = [["hints received:"] + known] + + my_knowledge = knowledge[nr] + + potential_discards = [] + for i,k in enumerate(my_knowledge): + if util.is_playable(k, board): + return Action(PLAY, card_index=i) + if util.is_useless(k, board): + potential_discards.append(i) + + if potential_discards: + return Action(DISCARD, card_index=random.choice(potential_discards)) + + playables = [] + for player,hand in enumerate(hands): + if player != nr: + for card_index,card in enumerate(hand): + if card.is_playable(board): + playables.append((player,card_index)) + + playables.sort(key=lambda which: -hands[which[0]][which[1]].rank) + while playables and hints > 0: + player,card_index = playables[0] + knows_rank = True + real_color = hands[player][card_index].color + real_rank = hands[player][card_index].rank + k = knowledge[player][card_index] + + hinttype = [HINT_COLOR, HINT_RANK] + + + for h in self.hints[(player,card_index)]: + hinttype.remove(h) + + t = None + if hinttype: + t = random.choice(hinttype) + + if t == HINT_RANK: + for i,card in enumerate(hands[player]): + if card.rank == hands[player][card_index].rank: + self.hints[(player,i)].add(HINT_RANK) + return Action(HINT_RANK, player=player, rank=hands[player][card_index].rank) + if t == HINT_COLOR: + for i,card in enumerate(hands[player]): + if card.color == hands[player][card_index].color: + self.hints[(player,i)].add(HINT_COLOR) + return Action(HINT_COLOR, player=player, color=hands[player][card_index].color) + + playables = playables[1:] + + if hints > 0: + hints = util.filter_actions(HINT_COLOR, valid_actions) + util.filter_actions(HINT_RANK, valid_actions) + hintgiven = random.choice(hints) + if hintgiven.type == HINT_COLOR: + for i,card in enumerate(hands[hintgiven.player]): + if card.color == hintgiven.color: + self.hints[(hintgiven.player,i)].add(HINT_COLOR) + else: + for i,card in enumerate(hands[hintgiven.player]): + if card.rank == hintgiven.rank: + self.hints[(hintgiven.player,i)].add(HINT_RANK) + + return hintgiven + + return random.choice(util.filter_actions(DISCARD, valid_actions)) + + def inform(self, action, player): + if action.type in [PLAY, DISCARD]: + if (player,action.card_index) in self.hints: + self.hints[(player,action.card_index)] = set() + for i in range(5): + if (player,action.card_index+i+1) in self.hints: + self.hints[(player,action.card_index+i)] = self.hints[(player,action.card_index+i+1)] + self.hints[(player,action.card_index+i+1)] = set() + +agent.register("inner", "Inner State Player", InnerStatePlayer) +agent.register("outer", "Outer State Player", OuterStatePlayer) \ No newline at end of file diff --git a/hanabi.py b/hanabi.py new file mode 100644 index 0000000..2a7ded0 --- /dev/null +++ b/hanabi.py @@ -0,0 +1,293 @@ +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): + return board[self.color].rank + 1 > self.rank + + 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() diff --git a/httpui.py b/httpui.py new file mode 100644 index 0000000..ec2ed3e --- /dev/null +++ b/httpui.py @@ -0,0 +1,1008 @@ +import http.server +import socketserver +import threading +import time +import shutil +import os +import hanabi +import random +import hashlib +import tutorial +import sys +import traceback +import threading +import agent +from cgi import parse_header, parse_multipart +from urllib.parse import parse_qs +from serverconf import HOST_NAME, PORT_NUMBER +import importlib + +for f in os.listdir("agents"): + if f.endswith(".py") and f != "__init__.py": + importlib.import_module("agents."+f[:-3]) + +HAND = 0 +TRASH = 1 +BOARD = 2 +TRASHP = 3 + +debug = True + + +errlog = sys.stdout +if not debug: + errlog = open("hanabi.log", "w") + + + +template = """ + + + + + + +

+ + + + + +
+ + + + +
Hint tokens left: %s
Mistakes made so far: %s
Cards left in deck: %s
+ +
+

Discarded

+%s +
+%s +
+
+

Other player

+ + + + + + + +%s + + + + + + + +
%s
%s
%s
%s
%s
%s
%s
%s
%s
%s

You

%s
%s
%s
%s
%s
%s
%s
%s
%s
%s
+
+

Actions


+
+%s +
+""" + +board_template = """
%s
+%s + %s + %s + %s + %s +""" + +def format_board(game, show, gid): + if not game.started: + return '

Start Game

'%gid + title = "

Board

" + if game.done(): + if game.dopostsurvey: + title = "

Game End

Points: " + str(game.score()) + '
Continue'%gid + elif game.study: + title = "

Game End

Points: " + str(game.score()) + '
Play again'%gid + else: + title = "

Game End

Points: " + str(game.score()) + '
New game' + def make_board_image(xxx_todo_changeme): + (i,card) = xxx_todo_changeme + return make_card_image(card, [], (BOARD,0,i) in show) + boardcards = list(map(make_board_image, enumerate(game.board))) + args = tuple([title] + boardcards) + return board_template%args + +def format_action(action_log, gid, replay=None): + (i, (action, player, card)) = action_log + result = "You " + other = "the AI" + otherp = "their" + if player == 0: + result = "AI " + other = "you" + otherp = "your" + if i <= 1: + result += " just " + + if action.type == hanabi.PLAY: + result += " played " + hanabi.format_card(card) + "" + elif action.type == hanabi.DISCARD: + result += " discarded " + hanabi.format_card(card) + "" + else: + result += " hinted %s about all %s "%(other, otherp) + if action.type == hanabi.HINT_COLOR: + result += hanabi.COLORNAMES[action.color] + " cards" + else: + result += str(action.rank) + "s" + if i == 0: + link = '' + if debug: + if replay: + (gid,round,info) = replay + explainlink = '(Explain)'%(gid, round) + else: + explainlink = '(Explain)'%gid + + return "" + result + '%s

'%explainlink + if i == 1: + return result + '

' + return '
' + result + '
' + +def show_game_state(game, player, turn, gid, replay=False): + + def make_ai_card(i, card, highlight): + hintlinks = [("Hint Rank", "/gid%s/%d/hintrank/%d"%(gid,turn,i)), ("Hint Color", "/gid%s/%d/hintcolor/%d"%(gid,turn,i))] + if replay: + (pgid,round,info) = replay + hintlinks = [("Hint Rank", "/takeover/%s/%d/hintrank/%d"%(pgid,round,i)), ("Hint Color", "/takeover/%s/%d/hintcolor/%d"%(pgid,round,i))] + if game.hints == 0 or game.done() or not game.started: + hintlinks = [] + highlight = False + return make_card_image(card, hintlinks, highlight) + aicards = [] + for i,c in enumerate(game.hands[0]): + aicards.append(make_ai_card(i, c, (HAND, 0, i) in player.show)) + aicards.append(", ".join(player.aiknows[i])) + + while len(aicards) < 10: + aicards.append("") + def make_your_card(i, card, highlight): + playlinks = [("Play", "/gid%s/%d/play/%d"%(gid,turn,i)), ("Discard", "/gid%s/%d/discard/%d"%(gid,turn,i))] + if replay: + (pgid,round,info) = replay + playlinks = [("Play", "/takeover/%s/%d/play/%d"%(pgid,round,i)), ("Discard", "/takeover/%s/%d/discard/%d"%(pgid,round,i))] + if game.done() or not game.started: + playlinks = [] + return unknown_card_image(playlinks, highlight) + yourcards = [] + for i,c in enumerate(game.hands[1]): + if game.done(): + yourcards.append(make_ai_card(i,c, False)) + else: + yourcards.append(make_your_card(i,c, (HAND, 1, i) in player.show)) + yourcards.append(", ".join(player.knows[i])) + while len(yourcards) < 10: + yourcards.append("") + board = format_board(game, player.show, gid) + foundtrash = [] + def format_trash(c): + result = hanabi.format_card(c) + if (TRASH, 0, -1) in player.show and c == game.trash[-1] and not foundtrash[0]: + foundtrash[0] = True + return result + "(just discarded)" + if (TRASHP, 0, -1) in player.show and c == game.trash[-1] and not foundtrash[0]: + foundtrash[0] = True + return result + "(just played)" + return result + discarded = {} + trashhtml = '\n' + for i,c in enumerate(hanabi.ALL_COLORS): + style = "border-bottom: 1px solid #000" + if i > 0: + style += "; border-left: 1px solid #000" + trashhtml += '\n'%(style, hanabi.COLORNAMES[c]) + discarded[c] = [] + for (col,num) in game.trash: + if col == c: + if (TRASH, 0, -1) in player.show and (col,num) == game.trash[-1] and (col,num) not in foundtrash: + foundtrash.append((col,num)) + discarded[c].append('
%d
'%(num)) + elif (TRASH, 0, -2) in player.show and (col,num) == game.trash[-2] and (col,num) not in foundtrash: + foundtrash.append((col,num)) + discarded[c].append('
%d
'%(num)) + else: + discarded[c].append('
%d
'%num) + discarded[c].sort() + trashhtml += '\n' + for i,c in enumerate(hanabi.ALL_COLORS): + style= ' style="vertical-align:top"' + if i > 0: + style= ' style="border-left: 1px solid #000; vertical-align:top"' + trashhtml += '\n'%(style, "\n".join(discarded[c])) + trashhtml += "
%s
%s

" + if foundtrash: + trashhtml += 'Cards written in red have been discarded or misplayed since your last turn.' + + trash = [trashhtml] + hints = game.hints + if hints == 0: + hints = '
0
' + mistakes = 3-game.hits + if mistakes == 2: + mistakes = '
2
' + cardsleft = len(game.deck) + if cardsleft < 5: + cardsleft = '
%d
'%cardsleft + replaycontrol = "" + if replay: + (gid,round,info) = replay + replaycontrol = "



" + if not foundtrash: + replaycontrol += "


" + replaycontrol += '' + replaycontrol += '\n' + replaycontrol += '" + ai,deck,score = info + + replaycontrol += ''%(ai, format_score(score), deck) + root = get_replay_root("log/game%s.log"%gid) + #if root == gid: + # replaycontrol += ''%root + #else: + # replaycontrol += ''%root + replaycontrol += "
Replay of game ' + gid + '
' + if round > 2: + replaycontrol += '<<<'%(gid,round-2) + else: + replaycontrol += "<<<" + replaycontrol += '' + replaycontrol += " Turn " + str(round) + replaycontrol += '' + if game.done(): + replaycontrol += ">>>" + else: + replaycontrol += '>>>'%(gid,round+2) + replaycontrol += "
%s AI, %s, deck %d
Show survey answers
Show survey answers
" + args = tuple([str(hints), str(mistakes), str(cardsleft)] + trash + [replaycontrol] + aicards + [board] + yourcards + ["\n".join([format_action(x,gid, replay) for x in enumerate(list(reversed(player.actions))[:15])])]) + return template%args + + +def make_circle(x, y, col): + x += random.randint(-5,5) + y += random.randint(-5,5) + r0 = random.randint(0,180) + r1 = r0 + 360 + result = """ + + + + + + """ + return result%(x,y,col, r0, x,y, r1, x,y) + + +def make_card_image(card, links=[], highlight=False): + image = """ + + + %s + %s + %s + %s + %s + +""" + ly = 130 + linktext = "" + for (text,target) in links: + linktext += """ + %s + + """%(target, ly, text) + ly += 23 + l = 35 # left + r = 90 # right + c = 62 # center (horizontal) + + t = 45 # top + m = 70 # middle (vertical) + b = 95 # bottom + circles = {0: [], 1: [(c,m)], 2: [(l,t),(r,b)], 3: [(l,b), (r,b), (c,t)], 4: [(l,b), (r,b), (l,t), (r,t)], 5:[(l,b), (r,b), (l,t), (r,t), (c,m)]} + circ = "\n".join([make_circle(x_y[0],x_y[1],hanabi.COLORNAMES[card.color]) for x_y in circles[card.rank]]) + highlighttext = "" + if highlight: + highlighttext = ' stroke="red" stroke-width="4"' + return image%(highlighttext, hanabi.COLORNAMES[card.color],str(card.rank), hanabi.COLORNAMES[card.color], hanabi.COLORNAMES[card.color], circ, linktext, hanabi.COLORNAMES[card.color],str(card.rank)) + + +def unknown_card_image(links=[], highlight=False): + image = """ + + + %s + ? + +""" + ly = 130 + linktext = "" + for (text,target) in links: + linktext += """ + %s + + """%(target, ly, text) + ly += 23 + highlighttext= "" + if highlight: + highlighttext = ' stroke="red" stroke-width="4"' + return image%(highlighttext,linktext) + +gameslock = threading.Lock() +games = {} +participantslock = threading.Lock() +participants = {} +participantstarts = {} + + + +class HTTPPlayer(agent.Agent): + def __init__(self, name, pnr): + self.name = name + self.pnr = pnr + self.actions = [] + self.knows = [set() for i in range(5)] + self.aiknows = [set() for i in range(5)] + self.show = [] + + def inform(self, action, player): + if player == 1: + self.show = [] + card = None + if action.type in [hanabi.PLAY, hanabi.DISCARD]: + card = self.game.hands[player][action.card_index] + self.actions.append((action, player,card)) + if player != self.pnr: + if action.type == hanabi.HINT_COLOR: + for i, (col,num) in enumerate(self.game.hands[self.pnr]): + if col == action.color: + self.knows[i].add(hanabi.COLORNAMES[col]) + self.show.append((HAND,self.pnr,i)) + elif action.type == hanabi.HINT_RANK: + for i, (col,num) in enumerate(self.game.hands[self.pnr]): + if num == action.rank: + self.knows[i].add(str(num)) + self.show.append((HAND,self.pnr,i)) + else: + if action.type == hanabi.HINT_COLOR: + for i, (col,num) in enumerate(self.game.hands[action.player]): + if col == action.color: + self.aiknows[i].add(hanabi.COLORNAMES[col]) + self.show.append((HAND,action.player,i)) + elif action.type == hanabi.HINT_RANK: + for i, (col,num) in enumerate(self.game.hands[action.player]): + if num == action.rank: + self.aiknows[i].add(str(num)) + self.show.append((HAND,action.player,i)) + + if action.type in [hanabi.PLAY, hanabi.DISCARD] and player == 0: + newshow = [] + for (where,who,what) in self.show: + if who == 0 and where == HAND: + if what < action.card_index: + newshow.append((where,who,what)) + elif what > action.card_index: + newshow.append((where,who,what-1)) + else: + newshow.append((where,who,what)) + self.show = newshow + if action.type == hanabi.DISCARD: + newshow = [] + for (t,w1,w2) in self.show: + if t == TRASH: + newshow.append((t,w1,w2-1)) + else: + newshow.append((t,w1,w2)) + self.show = newshow + self.show.append((TRASH,0,-1)) + + elif action.type == hanabi.PLAY: + (col,num) = self.game.hands[player][action.card_index] + if self.game.board[col][1] + 1 == num: + self.show.append((BOARD,0,col)) + else: + newshow = [] + for (t,w1,w2) in self.show: + if t == TRASH: + newshow.append((t,w1,w2-1)) + else: + newshow.append((t,w1,w2)) + self.show = newshow + self.show.append((TRASH,0,-1)) + if player == self.pnr and action.type in [hanabi.PLAY, hanabi.DISCARD]: + del self.knows[action.card_index] + self.knows.append(set()) + if player != self.pnr and action.type in [hanabi.PLAY, hanabi.DISCARD]: + del self.aiknows[action.card_index] + self.aiknows.append(set()) + +class ReplayHTTPPlayer(HTTPPlayer): + def __init__(self, name, pnr): + super(ReplayHTTPPlayer, self).__init__(name,pnr) + self.actions = [] + def get_action(self, nr, hands, knowledge, trash, played, board, valid_actions, hints, hits, cards_left): + return self.actions.pop(0) + +class ReplayPlayer(agent.Agent): + def __init__(self, name, pnr): + super(ReplayPlayer, self).__init__(name,pnr) + self.actions = [] + self.realplayer = None + def get_action(self, nr, hands, knowledge, trash, played, board, valid_actions, hints, hits, cards_left): + if self.realplayer: + self.realplayer.get_action(nr, hands, knowledge, trash, played, board, valid_actions, hints, hits, cards_left) + return self.actions.pop(0) + def inform(self, action, player): + if self.realplayer: + self.realplayer.inform(action, player) + def get_explanation(self): + if self.realplayer: + return self.realplayer.get_explanation() + return [] + +def get_replay_info(fname): + f = open(fname) + ai = None + deck = None + score = None + try: + for l in f: + if l.startswith("Treatment:"): + try: + items = l.strip().split() + ai = items[-2].strip("'(,") + deck = int(items[-1].strip(")")) + except Exception: + deck = None + elif l.startswith("Score"): + items = l.strip().split() + score = int(items[1]) + except Exception: + f.close() + return (None, None, None) + f.close() + + return (ai, deck, score) + +def get_replay_root(fname): + f = open(fname) + parent = None + for l in f: + if l.startswith("Old GID:"): + parent = l[8:].strip() + break + f.close() + if parent: + return get_replay_root("log/game%s.log"%parent) + return fname[8:24] + +def format_score(sc): + if sc is None: + return "not finished" + return "%d points"%sc + +class NullStream(object): + def write(self, *args): + pass + def close(self): + pass + + +class MyHandler(http.server.BaseHTTPRequestHandler): + def respond(s, response): + s.wfile.write(response.encode("utf-8")) + + def do_HEAD(s): + s.send_response(200) + s.send_header("Content-type", "text/html") + s.end_headers() + def do_GET(s): + try: + return s.perform_response() + except Exception: + errlog.write(traceback.format_exc()) + errlog.flush() + + def invalid(s, gid): + if len(gid) != 16: + return True + for c in gid: + if c not in "0123456789abcdef": + return True + if not os.path.exists("log/game%s.log"%gid): + return True + return False + + def perform_response(s): + """Respond to a GET request.""" + global games + + game = None + player = None + turn = None + gid = None + path = s.path + if s.path.startswith("/gid"): + gid = s.path[4:20] + gameslock.acquire() + if gid in games: + game, player, turn = games[gid] + gameslock.release() + path = s.path[20:] + + if s.path == "/hanabiui.png": + f = open("hanabiui.png", "rb") + s.send_response(200) + s.send_header("Content-type", "image/png") + s.end_headers() + shutil.copyfileobj(f, s.wfile) + f.close() + return + + if s.path.startswith("/favicon"): + s.send_response(200) + s.end_headers() + return + + # I honestly don't know why, but I already received a request for http://www.google.com + if s.path.startswith("http://"): + s.send_response(400) + s.end_headers() + return + + if s.path.startswith("/robots.txt"): + s.send_response(200) + s.send_header("Content-type", "text/plain") + s.end_headers() + s.respond("User-agent: *\n") + s.respond("Disallow: /\n") + return + + + + s.send_response(200) + s.send_header("Content-type", "text/html") + s.end_headers() + + + if path.startswith("/tutorial"): + gid = s.getgid() + todelete = [] + participantslock.acquire() + try: + for g in participantstarts: + if participantstarts[g] + 7200 < time.time(): + todelete.append(g) + for d in todelete: + del participants[d] + del participantstarts[d] + except Exception: + errlog.write("Error cleaning participants:\n") + errlog.write(traceback.format_exc()) + participants[gid] = open("log/survey%s.log"%gid, "w") + participantstarts[gid] = time.time() + participantslock.release() + s.respond("Hanabi") + s.respond('
') + s.respond(tutorial.intro) + if not path.startswith("/tutorial/newtab"): + s.respond('
If you want to open this tutorial in a new tab for reference during the game, click here here
\n') + s.respond('
\n'%(gid)) + + s.respond(tutorial.summary) + + s.respond('
') + return + + if s.path.startswith("/postsurvey/"): + gid = s.path[12:] + if gid in participants: + s.postsurvey(gid) + return + + doaction = True + replay = False + if path.startswith("/new/") and debug: + + + type = s.path[5:] + if type in agent.ids(): + ai = agent.make(type, type, 0) + turn = 1 + player = HTTPPlayer("You", 1) + random.seed(None) + nr = random.randint(6,10000) + + t = (type,nr) + gid = s.getgid() + log = open("log/game%s.log"%gid, "w") + print("Treatment:", t, file=log) + random.seed(nr) + game = hanabi.Game([ai,player], log=log, format=1) + player.game = game + game.treatment = t + game.ping = time.time() + game.started = False + todelete = [] + gameslock.acquire() + for g in games: + if games[g][0].ping + 3600 < time.time(): + todelete.append(g) + for g in todelete: + del games[g] + games[gid] = (game,player,turn) + gameslock.release() + + elif path.startswith("/replay/") and debug: + gid = path[8:24] + rest = path[25:] + + items = rest.split("/") + round = items[0] + if len(items) > 1 and items[1] == "explain": + path = "/explain" + + fname = "log/game%s.log"%gid + try: + round = int(round) + except Exception: + import traceback + traceback.print_exc() + round = None + if "/" in gid or "\\" in gid or round is None or not os.path.exists(fname): + s.respond("Hanabi\n") + s.respond('

Invalid Game ID

\n') + s.respond("") + return + + info = get_replay_info(fname) + + replay = (gid,round,info) + f = open(fname) + players = [ReplayPlayer("AI", 0), ReplayHTTPPlayer("You", 1)] + i = 0 + def convert(s): + if s == "None": + return None + return int(s) + for l in f: + if l.startswith("Treatment:"): + try: + items = l.strip().split() + ai = items[-2].strip("'(,") + players[0].realplayer = agent.make(ai, ai, 0) + deck = int(items[-1].strip(")")) + + except Exception: + deck = None + elif l.startswith("MOVE:"): + items = [s.strip() for s in l.strip().split()] + const, pnum, type, cnr, pnr, col, num = items + a = hanabi.Action(convert(type), convert(pnr), convert(col), convert(num), convert(cnr)) + players[int(pnum)].actions.append(a) + i += 1 + if i >= round: + break + if not deck: + s.respond("Hanabi\n") + s.respond('

Invalid Game Log

\n') + s.respond("") + return + player = players[1] + random.seed(deck) + game = hanabi.Game(players, log=NullStream()) + player.game = game + game.started = time.time() + for i in range(round): + game.single_turn() + doaction = False + turn = -1 + gid = "" + elif path.startswith("/starttakeover/"): + items = [_f for _f in path.split("/") if _f] + if len(items) < 6: + s.respond("Hanabi\n") + s.respond('

Invalid Game ID

\n') + s.respond("") + return + gid, round, ai, action, arg = items[1:] + oldgid = gid + fname = "log/game%s.log"%gid + try: + round = int(round) + except Exception: + import traceback + traceback.print_exc() + round = None + if "/" in gid or "\\" in gid or round is None or not os.path.exists(fname): + s.respond("Hanabi\n") + s.respond('

Invalid Game ID

\n') + s.respond("") + return + + info = get_replay_info(fname) + f = open(fname) + players = [ReplayPlayer(ai.capitalize(), 0), ReplayHTTPPlayer("You", 1)] + players[0].realplayer = agent.make(ai, ai.capitalize(), 0) + i = 0 + def convert(s): + if s == "None": + return None + return int(s) + for l in f: + if l.startswith("Treatment:"): + try: + items = l.strip().split() + deck = int(items[-1].strip(")")) + except Exception: + deck = None + elif l.startswith("MOVE:"): + items = [s.strip() for s in l.strip().split()] + const, pnum, type, cnr, pnr, col, num = items + a = hanabi.Action(convert(type), convert(pnr), convert(col), convert(num), convert(cnr)) + players[int(pnum)].actions.append(a) + i += 1 + if i >= round: + break + if not deck: + s.respond("Hanabi\n") + s.respond('

Invalid Game Log

\n') + s.respond("") + return + player = players[1] + + gid = s.getgid() + t = (ai,deck) + log = NullStream() + random.seed(deck) + game = hanabi.Game(players, log=log, format=1) + player.game = game + game.treatment = t + game.ping = time.time() + game.started = True + for i in range(round): + game.single_turn() + game.players[0] = game.players[0].realplayer + game.current_player = 1 + doaction = False + turn = round+1 + gameslock.acquire() + games[gid] = (game,players[1],turn) + gameslock.release() + path = "/%d/%s/%s"%(turn, action, arg) + + elif path.startswith("/selectreplay/"): + filters = path[14:].split("/") + filters = dict(list(zip(filters[::2], filters[1::2]))) + s.respond("Hanabi\n") + s.respond('') + s.respond('

Replay selection

') + s.respond('

Filters:

') + def format_filters(f): + result = "" + for k in f: + result += "%s/%s/"%(k,f[k]) + return result + + def update_filters(f, k, v): + result = dict(f) + if v: + result[k] = v + elif k in result: + del result[k] + return result + + s.respond('

AI: ') + + ais = [] + for id in agent.ids(): + ais.append((agent.get(id)[0],id)) + for i,(display,value) in enumerate(ais): + s.respond(' %s - '%(format_filters(update_filters(filters, "ai", value)), display)) + s.respond(' any

'%(format_filters(update_filters(filters, "ai", "")))) + + s.respond('

Score: ') + + for i,(display,value) in enumerate([("0-4", "0"), ("5-9", "1"), ("10-14", "2"), ("15-19", "3"), ("20-24", "4"), ("25", "5")]): + s.respond(' %s - '%(format_filters(update_filters(filters, "score", value)), display)) + s.respond(' any

'%(format_filters(update_filters(filters, "score", "")))) + + s.respond('

Deck: ') + + for i,(display,value) in enumerate([("1", "1"), ("2", "2"), ("3", "3"), ("4", "4"), ("5", "5"), ("other", "other")]): + s.respond(' %s - '%(format_filters(update_filters(filters, "deck", value)), display)) + s.respond(' any

'%(format_filters(update_filters(filters, "deck", "")))) + + s.respond('Select a replay to view: