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: