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 += '
%s
\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 += '
"
+ 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 = '
"
+ 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 = """
+
+"""
+ 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 = """
+
+"""
+ 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('
')
+ 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
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:
')
+ replays = []
+
+ def match(f, ai, deck, score):
+ if "ai" in f and ai != f["ai"]:
+ return False
+ if "score" in f and (score is None or score/5 != int(f["score"])):
+ return False
+ if "deck" in f and ((str(deck) != f["deck"] and f["deck"] != "other") or (f["deck"] == "other" and deck <= 5)):
+ return False
+
+ return True
+
+ for f in os.listdir("log/"):
+ if f.startswith("game"):
+ gid = f[4:20]
+ fname = os.path.join("log", f)
+ (ai,deck,score) = get_replay_info(fname)
+ if ai and deck and match(filters, ai, deck, score) and score is not None:
+ entry = '
")
+ return
+
+ if gid is None or game is None or path.startswith("/restart/"):
+ if not debug:
+ s.respond("Hanabi\n")
+ s.respond('
Invalid Game ID
\n')
+ s.respond("")
+ return
+ if game is not None:
+ del game
+ gameslock.acquire()
+ if gid is not None and gid in games:
+ del games[gid]
+ gameslock.release()
+ # AIList
+ # /new/ will look up in the agent ids dictionary, so make sure the names match
+ s.respond("Hanabi\n")
+ s.respond('
')
+ s.respond('')
+ return
+
+ if path.startswith("/explain") and debug:
+ s.show_explanation(game)
+ return
+
+ if path.startswith("/start/"):
+ game.single_turn()
+ game.started = True
+
+
+ parts = path.strip("/").split("/")
+ if parts[0] == str(turn):
+ actionname = parts[1]
+ index = int(parts[2])
+ action = None
+ if actionname == "hintcolor" and game.hints > 0:
+ col = game.hands[0][index][0]
+ action = hanabi.Action(hanabi.HINT_COLOR, player=0, color=col)
+ elif actionname == "hintrank" and game.hints > 0:
+ nr = game.hands[0][index][1]
+ action = hanabi.Action(hanabi.HINT_RANK, player=0, rank=nr)
+ elif actionname == "play":
+ action = hanabi.Action(hanabi.PLAY, player=1, card_index=index)
+ elif actionname == "discard":
+ action = hanabi.Action(hanabi.DISCARD, player=1, card_index=index)
+
+ if action:
+ turn += 1
+ gameslock.acquire()
+ games[gid] = (game,player,turn)
+ gameslock.release()
+ game.external_turn(action)
+ game.single_turn()
+
+
+
+
+ s.respond("Hanabi")
+ s.respond('')
+
+ s.respond(show_game_state(game, player, turn, gid, replay))
+
+ s.respond("")
+ if game.done() and gid is not None and gid in games:
+ errlog.write("%s game done. Score: %d\n"%(str(game.treatment), game.score()))
+ errlog.flush()
+ game.finish()
+ del[gid]
+
+ def show_explanation(s, game):
+ s.respond("Hanabi - AI Explanation")
+ s.respond('')
+
+ s.respond('
')
+ s.respond('
Description
Card 1
Card 2
Card 3
Card 4
Card 5
\n')
+ for line in game.players[0].get_explanation():
+ s.respond('
\n')
+ for item in line:
+ s.respond('\t
%s
\n'%(str(item).replace("\n", " ")))
+ s.respond('
\n')
+ s.respond("
\n")
+
+
+ s.respond("")
+
+ def parse_POST(self):
+ ctype, pdict = parse_header(self.headers['content-type'])
+ if ctype == 'multipart/form-data':
+ postvars = parse_multipart(self.rfile, pdict)
+ elif ctype == 'application/x-www-form-urlencoded':
+ length = int(self.headers['content-length'])
+ postvars = parse_qs(
+ self.rfile.read(length),
+ keep_blank_values=1)
+ else:
+ postvars = {}
+ return postvars
+
+ def getgid(s):
+ peer = str(s.connection.getpeername()) + str(time.time()) + str(os.urandom(4))
+ return hashlib.sha224(peer.encode("utf-8")).hexdigest()[:16]
+
+
+class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
+ def finish_request(self, request, client_address):
+ request.settimeout(30)
+ # "super" can not be used because BaseServer is not created from object
+ http.server.HTTPServer.finish_request(self, request, client_address)
+
+if __name__ == '__main__':
+ server_class = ThreadingHTTPServer
+ if not os.path.exists("log/"):
+ os.makedirs("log")
+ httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
+ errlog.write(time.asctime() + " Server Starts - %s:%s\n" % (HOST_NAME, PORT_NUMBER))
+ errlog.flush()
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+ httpd.server_close()
+ errlog.write(time.asctime() + " Server Stops - %s:%s\n" % (HOST_NAME, PORT_NUMBER))
+ errlog.flush()
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..b2fa093
--- /dev/null
+++ b/main.py
@@ -0,0 +1,85 @@
+from hanabi import Game
+import agent
+import random
+import os
+import importlib
+import sys
+import math
+import argparse
+
+
+for f in os.listdir("agents"):
+ if f.endswith(".py") and f != "__init__.py":
+ importlib.import_module("agents."+f[:-3])
+
+
+
+class NullStream(object):
+ def write(self, *args):
+ pass
+
+names = ["Shangdi", "Nu Wa", "Yu Di", "Tian", "Pangu"]
+
+def main(n=100, seed=0, agents=[]):
+
+ random.shuffle(names)
+ if not agents:
+ agents = []
+
+ while len(agents) < 2:
+ agents.append("random")
+
+
+
+ out = NullStream()
+ if n < 6:
+ out = sys.stdout
+ pts = []
+ for i in range(n):
+ if (i+1)%100 == 0:
+ print("Starting game", i+1)
+
+ if seed is not None:
+ random.seed(seed+i+1)
+ players = []
+ for i,a in enumerate(agents):
+ players.append(agent.get(a)[1](names[i], i))
+
+ g = Game(players, out)
+ try:
+ pts.append(g.run())
+ if (i+1)%100 == 0:
+ print("score", pts[-1])
+ except Exception:
+ import traceback
+ traceback.print_exc()
+ if n < 10:
+ print("Scores:", pts)
+
+ if n > 1:
+ mean = sum(pts)*1.0/len(pts)
+ print("mean: %.2f"%(mean))
+ ssqs = [(p-mean)**2 for p in pts]
+ print("stddev: %.2f"%(math.sqrt(sum(ssqs)/(len(pts)-1))))
+ print("range", min(pts), max(pts))
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='Simulate several games of Hanabi.')
+ parser.add_argument('agents', metavar='A', nargs='*',
+ help='the agent types that should play (minimum 2)')
+ parser.add_argument('--list', dest='list', action='store_true',
+ default=False, help='Show available agent types and quit')
+ parser.add_argument('-n', '--count', '--games', dest='n', action='store',
+ type=int, default=100, help='How many games should the agents play?')
+ parser.add_argument('-s', '--seed', dest='seed', action='store',
+ type=int, default=0, help='The random seed to be used')
+ parser.add_argument('-r', '--random', dest='rand', action='store_true',
+ default=False, help='Do not use random seed; make games truly random.')
+ args = parser.parse_args()
+ if args.list:
+ print("Available agents:")
+ for id in agent.ids():
+ print(" %s: %s"%(id, agent.get(id)[0]))
+ else:
+ main(args.n, (None if args.rand else args.seed), args.agents)
\ No newline at end of file
diff --git a/serverconf.py b/serverconf.py
new file mode 100644
index 0000000..5515b47
--- /dev/null
+++ b/serverconf.py
@@ -0,0 +1,3 @@
+
+HOST_NAME = "127.0.0.1"
+PORT_NUMBER = 31337
diff --git a/tutorial.py b/tutorial.py
new file mode 100644
index 0000000..c8db17a
--- /dev/null
+++ b/tutorial.py
@@ -0,0 +1,38 @@
+
+
+intro = """
+
Short introduction to the Hanabi User Interface
+
+
You will play a game of the card game Hanabi in a browser-based implementation. This tutorial will describe how to use the user interface, so please read it carefully.
+
If you are not familiar with the card game Hanabi or need a refresher on the rules, you can read a short summary at the end of this page or refer to the official rules here
+
The user interface you will be playing in looks like this:
+
+
On the left you can see how many hint tokens are currently available, how many mistakes have been made so far and how many cards are left in the deck. If you reach 2 mistakes, as in the picture above, the number will turn red and be shown in bold to draw your attention to that fact.
+
+
In the center you see the AI player's hand on top, the board in the center and a representation of your hand (which you can't see) on the bottom. To play or discard one of your own cards, click the link
+on that card to do so. To hint the AI about all their cards of a particular color or rank, click the link on any card that matches that color or rank. For example, if you click the "hint color" link on the yellow 4, they
+will be hinted about all their yellow cards. Note that the "Hint Color" and "Hint Rank" links will not be shown when no hint tokens are available. Underneath each card in your hand you can see what you have been told about that card in the past. The same goes for the cards in the AI's hand. Note that you will not be reminded
+of information that you can infer from a hint. In particular, if you are told that some of your cards are 1s, they will be marked as such, but the other cards will not be marked as "not 1s".
+
+
On the right side of the screen, finally, you will see the last actions that happened, with the newest action on top of the list. The cards that were affected by the last two actions (your last action and the last action the AI performed) will also be highlighted in red. For hints that were given this will appear as a red frame around the card or cards in a player's hand. For a card that was successfully played a red frame will be drawn around the stack on the board on which the card was played. Otherwise, if a card is unsuccessfully played or discarded, that card will be highlighted in red in the list of cards in the trash.
+
+
When you click "Continue" you and the AI will immediately be dealt cards. When you click "Start Game" the AI will take the first turn, and then it is your turn.
+
+"""
+
+summary = """
+
Hanabi rules summary
+
+
Hanabi is a cooperative card game in which you don't see your own cards, but you see the cards the other player has in their hand. There are 5 colors in the game: yellow, red, blue, green and white, and each color has cards in the ranks 1 to 5. Each color has three copies of the 1s, two copies of the 2s, 3s and 4s and only a single copy of the 5s.
+
+
The goal of the game is to play the cards on five stacks, one for each color, in ascending order, starting with the 1s. At the end of the game you will receive one point for each card that was successfully played.
+
On your turn you have the choice between one of three actions:
+
+
Play a card: Choose a card from your hand and play it. If it is the next card in ascending order for any stack on the board, it will be placed there, otherwise it will be counted as a mistake and the car will be put in the trash.
+
Give a hint: Tell the other player about all cards in their hand that have a particular color or a particular rank. For example, you can tell the other player which of their cards are yellow, but you have to tell them all their yellow cards. Likewise, if you want to tell the other player which of their cards are 3s, you have to tell them all their 3s. You can also not tell them that they have zero of a particular color or rank. Giving a hint consumes one hint token, of which there are initially 8.
+
Discard a card: Choose a card from your hand and put it in the trash pile. This will regenerate one hint token, but you can never have more than the initial 8.
+
+The game lasts until either the last card is drawn, plus one additional turn for each player, or until 3 mistakes have been made.
+
+"""
+
diff --git a/util.py b/util.py
new file mode 100644
index 0000000..ea50699
--- /dev/null
+++ b/util.py
@@ -0,0 +1,66 @@
+from hanabi import *
+
+def is_playable(knowledge, board):
+ possible = get_possible(knowledge)
+ return all(map(playable(board), possible))
+
+def maybe_playable(knowledge, board):
+ possible = get_possible(knowledge)
+ return any(map(playable(board), possible))
+
+def is_useless(knowledge, board):
+ possible = get_possible(knowledge)
+ return all(map(useless(board), possible))
+
+def maybe_useless(knowledge, board):
+ possible = get_possible(knowledge)
+ return any(map(useless(board), possible))
+
+def has_property(predicate, knowledge):
+ possible = get_possible(knowledge)
+ return all(map(predicate, possible))
+
+def may_have_property(predicate, knowledge):
+ possible = get_possible(knowledge)
+ return any(map(predicate, possible))
+
+def probability(predicate, knowledge):
+ num = 0.0
+ denom = 0.0
+ for col in ALL_COLORS:
+ for i,cnt in enumerate(knowledge[col]):
+ if predicate(Card(col,i+1)):
+ num += cnt
+ denom += cnt
+ return num/denom
+
+def playable(board):
+ def playable_inner(card):
+ return card.is_playable(board)
+ return playable_inner
+
+def useless(board):
+ def useless_inner(card):
+ return card.is_useless(board)
+ return useless_inner
+
+def has_rank(rank):
+ def has_rank_inner(card):
+ return card.rank == rank
+ return has_rank_inner
+
+def has_color(color):
+ def has_color_inner(card):
+ return card.color == color
+ return has_color_inner
+
+def get_possible(knowledge):
+ result = []
+ for col in ALL_COLORS:
+ for i,cnt in enumerate(knowledge[col]):
+ if cnt > 0:
+ result.append(Card(col,i+1))
+ return result
+
+def filter_actions(type, actions):
+ return [act for act in actions if act.type == type]
\ No newline at end of file