diff --git a/game_evaluator.py b/game_evaluator.py index 076bbb7..7218286 100644 --- a/game_evaluator.py +++ b/game_evaluator.py @@ -12,8 +12,8 @@ def ResistanceGame(n_players): def AvalonGame(n_players): - full_set = [("Merlin", True), ("G", True), ("G", True), - ("E", False), ("ELance", False), ("GLance", True), + full_set = [("Merlin", True), ("Percival", True), ("G", True), + ("Morgana", False), ("ELance", False), ("GLance", True), ("Mordred", False), ("G", True), ("G", True), ("Oberon", False)] return full_set[:n_players] @@ -35,6 +35,13 @@ class DeceptionGame(object): self.tid = 0 self.lancelots_switch_at = [] + def get_fail_req(self, rnd): + if rnd is not 4: + return 1 + if self.n_players >= 7: + return 2 + return 1 + def player_is_good(self, deal, player, round): if round is None: return deal[player][1] @@ -150,13 +157,14 @@ class DeceptionGame(object): "round"]}) self.tid += 1 - def do_vote(self, team, votes, fail_req, r): + def do_vote(self, team, votes, fail_req, r, v, proposer): transaction = [] rnd = r - 1 + voten = v - 1 def obs(deal): self.model.set_deal(deal) - return self.model.votes(team, votes, fail_req, rnd) + return self.model.votes(team, votes, fail_req, rnd, voten, proposer) transaction.append(obs) self.observations.append(transaction) @@ -164,6 +172,8 @@ class DeceptionGame(object): "team": team, "votes": votes, "round": r, + "voten": v, + "proposer": proposer, "fails required": fail_req, "print_order": ["team", "votes", "round"]}) self.tid += 1 @@ -175,7 +185,9 @@ class DeceptionGame(object): self.do_vote(statement["team"], statement["votes"], statement["fails required"], - statement["round"]) + statement["round"], + statement["voten"], + statement["proposer"]) if type == "switch": self.switch_lancelots(statement["round"]) elif type == "mission": diff --git a/games/p1 b/games/p1 new file mode 100644 index 0000000..0a60bfa --- /dev/null +++ b/games/p1 @@ -0,0 +1,110 @@ +[ + { + "player_names": {}, + "game_size": 10 + }, + { + "proposer": 8, + "fails required": 1, + "votes": [ + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 0 + ], + "team": [ + 1, + 7, + 8 + ], + "print_order": [ + "team", + "votes", + "round" + ], + "type": "vote", + "round": 1, + "voten": 1 + }, + { + "proposer": 9, + "fails required": 1, + "votes": [ + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1 + ], + "team": [ + 2, + 7, + 9 + ], + "print_order": [ + "team", + "votes", + "round" + ], + "type": "vote", + "round": 1, + "voten": 2 + }, + { + "proposer": 0, + "fails required": 1, + "votes": [ + 1, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 1, + 0 + ], + "team": [ + 0, + 5, + 6 + ], + "print_order": [ + "team", + "votes", + "round" + ], + "type": "vote", + "round": 1, + "voten": 3 + }, + { + "fails": 1, + "type": "mission", + "print_order": [ + "team", + "fails", + "must fail", + "round" + ], + "team": [ + 0, + 5, + 6 + ], + "must fail": false, + "round": 1 + } +] \ No newline at end of file diff --git a/models/data_model.py b/models/data_model.py new file mode 100644 index 0000000..d3af60d --- /dev/null +++ b/models/data_model.py @@ -0,0 +1,93 @@ +from default_model import DefaultModel +from probs_from_game import TrainTable, approval + +translate = { + "Merlin": "merlin", + "Morgana": "morgana", + "Oberon": "oberon", + "Mordred": "mordred", + "Percival": "percival", + "GLance": "good", + "G": "good", + "ELance": "assassin" +} + +class DataModel(DefaultModel): + def __init__(self, game): + super(DataModel, self).__init__(game) + self.table = TrainTable("models/dataset.json") + + #def mission(self, team, fails, must_fail, rnd): + #pass + + def votes(self, team, votes, fail_req, rnd, voten, proposer): + total = 1.0 + n_actually_good_people = sum( + [int(self.is_good(x, rnd)) for x in team]) + n_spies = len(team) - n_actually_good_people + + prole = self.player_role(proposer) + for player, vote in enumerate(votes): + role = self.player_role(player) + if role == "Merlin": + m_spies = n_spies + roles = [self.player_role(x) for x in team] + if "Mordred" in roles: + m_spies -= 1 + data = self.table.MerlinVote(rnd + 1, voten + 1, + proposer_role=translate[prole], + evil_count=n_spies, + known_evil=m_spies, + size=len(votes)) + elif role == "Percival": + roles = [self.player_role(x) for x in team] + tc = "" + if "merlin" in roles: + tc += "merlin" + if "morgana" in roles: + tc += "morgana" + if tc == "": + tc = "neither" + data = self.table.PercivalVote(rnd + 1, voten + 1, + proposer_role=translate[prole], + evil_count=n_spies, + team_contains=tc, + size=len(votes)) + elif role == "G" or role == "GLance": + data = self.table.GoodVote(rnd + 1, voten + 1, + proposer_role=translate[prole], + evil_count=n_spies, + size=len(votes)) + elif role == "Mordred": + data = self.table.MordredVote(rnd + 1, voten + 1, + proposer_role=translate[prole], + evil_count=n_spies, + size=len(votes)) + elif role == "Morgana": + data = self.table.MorganaVote(rnd + 1, voten + 1, + proposer_role=translate[prole], + evil_count=n_spies, + size=len(votes)) + elif role == "Oberon": + data = self.table.OberonVote(rnd + 1, voten + 1, + proposer_role=translate[prole], + evil_count=n_spies, + size=len(votes)) + elif role == "ELance" or "E": + data = self.table.EvilVote(rnd + 1, voten + 1, + proposer_role=translate[prole], + evil_count=n_spies, + size=len(votes)) + else: + continue + app = approval(data) + if vote == 1: + total = total * app + else: + total = total * (1.0 - app) + return total + + + + + diff --git a/models/dataset.json.gz b/models/dataset.json.gz new file mode 100644 index 0000000..c787500 Binary files /dev/null and b/models/dataset.json.gz differ diff --git a/models/default_model.py b/models/default_model.py index f7a8257..e7beb05 100644 --- a/models/default_model.py +++ b/models/default_model.py @@ -51,7 +51,7 @@ class BaseModel(object): def mission(self, team, fails, must_fail, rnd): return True - def votes(self, team, votes, fail_req, rnd): + def votes(self, team, votes, fail_req, rnd, voten, proposer): return True @@ -130,7 +130,7 @@ class DefaultModel(BaseModel): return 1.0 return 1.0 - def votes(self, team, votes, fail_req, rnd): + def votes(self, team, votes, fail_req, rnd, voten, proposer): for player, vote in enumerate(votes): role = self.player_role(player) if role in special_votes: diff --git a/models/probs_from_game.py b/models/probs_from_game.py new file mode 100644 index 0000000..207cd66 --- /dev/null +++ b/models/probs_from_game.py @@ -0,0 +1,204 @@ +from collections import defaultdict as dd +import json + +roles = [u'oberon', u'merlin', u'mordred', u'morgana', u'percival', u'assassin'] + +ANY_GOOD = [u'merlin', u'percival', u'good'] +ANY_EVIL = [u'oberon', u'mordred', u'morgana', u'assassin', u'evil'] + + +def isSpy(x, game): + return game["players"][x]["spy"] + +def approval(x): + if x["Approve"] + x["Reject"] == 0: + return 0.0 + return (x["Approve"] * 1.0) / (x["Approve"] + x["Reject"]) + +def getRole(x, game): + if str(x) not in game["role_by_seat"]: + if isSpy(x, game): + return "evil" + return "good" + return game["role_by_seat"][str(x)] + +def sumKeys(a, b): + out = dd(int) + for k, v in a.items(): + out[k] += v + for k, v in b.items(): + out[k] += v + return out + +def sum_helper(table, constraint_list): + if len(constraint_list) == 0: + return table + c = constraint_list[0] + out = dd(int) + if isinstance(c, type([])): + for v in c: + out = sumKeys(out, sum_helper(table[v], constraint_list[1:])) + elif c is None: + for k in table: + out = sumKeys(out, sum_helper(table[k], constraint_list[1:])) + else: + return sumKeys(out, sum_helper(table[c], constraint_list[1:])) + return out + +class TrainTable(object): + """Docstring for TrainTable. """ + + # good: round, vote, proposer_role, evils on team + def __init__(self, path): + self.good_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int)))))) + self.percy_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int))))))) + self.merlin_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int))))))) + self.evil_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int)))))) + self.morgana_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int)))))) + self.assassin_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int)))))) + self.oberon_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int)))))) + self.mordred_vote = dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(lambda: dd(int)))))) + self._path = path + self._build_prob_table() + + def GoodVote(self, round=None, vote=None, proposer_role=None, evil_count=None, size=None): + return sum_helper(self.good_vote, [size, round, vote, proposer_role, evil_count]) + + def PercivalVote(self, round=None, vote=None, proposer_role=None, evil_count=None, team_contains=None, size=None): + return sum_helper(self.percy_vote, [size, round, vote, proposer_role, evil_count, team_contains]) + + def EvilVote(self, round=None, vote=None, proposer_role=None, evil_count=None, size=None): + return sum_helper(self.evil_vote, [size, round, vote, proposer_role, evil_count]) + + def MordredVote(self, round=None, vote=None, proposer_role=None, evil_count=None, size=None): + return sum_helper(self.mordred_vote, [size, round, vote, proposer_role, evil_count]) + + def MorganaVote(self, round=None, vote=None, proposer_role=None, evil_count=None, size=None): + return sum_helper(self.morgana_vote, [size, round, vote, proposer_role, evil_count]) + + def AssassinVote(self, round=None, vote=None, proposer_role=None, evil_count=None, size=None): + return sum_helper(self.asassin_vote, [size, round, vote, proposer_role, evil_count]) + + def OberonVote(self, round=None, vote=None, proposer_role=None, evil_count=None, size=None): + return sum_helper(self.oberon_vote, [size, round, vote, proposer_role, evil_count]) + + def MerlinVote(self, round=None, vote=None, proposer_role=None, evil_count=None, known_evil=None, size=None): + return sum_helper(self.merlin_vote, [size, round, vote, proposer_role, evil_count, known_evil]) + + + def _build_prob_table(self): + f = open(self._path) + self._data = json.load(f) + f.close() + self._build_good_vote(self.good_vote, "good") + self._build_percy_vote(self.percy_vote) + self._build_merlin_vote(self.merlin_vote) + self._build_evil_vote(self.evil_vote, "evil") + self._build_evil_vote(self.mordred_vote, "mordred") + self._build_evil_vote(self.morgana_vote, "morgana") + self._build_evil_vote(self.assassin_vote, "assassin") + self._build_evil_vote(self.oberon_vote, "oberon") + + def _build_good_vote(self, table, exprole): + for game in self._data: + size = len(game["players"]) + for player in game["players"]: + idx = player["seat"] + if player["spy"]: + continue + if getRole(idx, game) != exprole: + continue + for round_n, round in enumerate(game["log"]): + for vote_n in range(1, 6): + if str(vote_n) not in round: + break + vote = round[str(vote_n)] + prole = getRole(vote["proposer"], game) + if vote["proposer"] == idx: + prole = "self" + evils = sum([int(isSpy(x, game)) for x in vote["team"]]) + action = vote["votes"][idx] + table[size][round_n + 1][vote_n][prole][evils][action] += 1 + + def _build_evil_vote(self, table, exprole): + for game in self._data: + size = len(game["players"]) + for player in game["players"]: + idx = player["seat"] + if not player["spy"]: + continue + if getRole(idx, game) != exprole: + continue + for round_n, round in enumerate(game["log"]): + for vote_n in range(1, 6): + if str(vote_n) not in round: + break + vote = round[str(vote_n)] + prole = getRole(vote["proposer"], game) + if vote["proposer"] == idx: + prole = "self" + evils = sum([int(isSpy(x, game)) for x in vote["team"]]) + action = vote["votes"][idx] + table[size][round_n + 1][vote_n][prole][evils][action] += 1 + + def _build_percy_vote(self, table): + for game in self._data: + size = len(game["players"]) + for player in game["players"]: + idx = player["seat"] + if player["spy"]: + continue + if getRole(idx, game) != "percival": + continue + for round_n, round in enumerate(game["log"]): + for vote_n in range(1, 6): + if str(vote_n) not in round: + break + vote = round[str(vote_n)] + prole = getRole(vote["proposer"], game) + evils = sum([int(isSpy(x, game)) for x in vote["team"]]) + action = vote["votes"][idx] + tc = "" + rolesOnTeam = [getRole(x, game) for x in vote["team"]] + if "merlin" in rolesOnTeam: + tc += "merlin" + if "morgana" in rolesOnTeam: + tc += "morgana" + if tc == "": + tc = "neither" + table[size][round_n + 1][vote_n][prole][evils][tc][action] += 1 + + def _build_merlin_vote(self, table): + for game in self._data: + size = len(game["players"]) + for player in game["players"]: + idx = player["seat"] + if player["spy"]: + continue + if getRole(idx, game) != "merlin": + continue + for round_n, round in enumerate(game["log"]): + for vote_n in range(1, 6): + if str(vote_n) not in round: + break + vote = round[str(vote_n)] + prole = getRole(vote["proposer"], game) + evils = sum([int(isSpy(x, game)) for x in vote["team"]]) + action = vote["votes"][idx] + known_evils = sum([int(isSpy(x, game) and (getRole(x, game) != "mordred")) for x in vote["team"]]) + table[size][round_n + 1][vote_n][prole][evils][known_evils][action] += 1 + + + + + + + + + +def main(): + table = TrainTable("dataset.json") + + +if __name__ == '__main__': + main() diff --git a/tim.py b/tim.py index 8d69bb6..ac4831f 100644 --- a/tim.py +++ b/tim.py @@ -6,6 +6,7 @@ import json from colorama import Fore, Style from models.default_model import DefaultModel +from models.data_model import DataModel from game_evaluator import DeceptionGame, AvalonGame @@ -99,7 +100,7 @@ def main(): if command == "newgame": nplayers = raw_input("How many players? ") game = DeceptionGame( - AvalonGame(int(nplayers)), DefaultModel) + AvalonGame(int(nplayers)), DataModel) namemap = {} elif command == "load": if len(command_list) < 2: @@ -124,12 +125,14 @@ def main(): print "%d: %s" % (i, display_statement(statement, namemap)) continue elif command == "vote": + proposer = int(raw_input("Proposer? ").strip()) input = raw_input("Team? ").strip() team = [int(x) for x in input] votes = [int(x) for x in raw_input("Votes? ").strip()] round = int(raw_input("Round? ").strip()) - fail_req = int(raw_input("# Fails Required? ").strip()) - game.do_vote(team, votes, fail_req, round) + voten = int(raw_input("Vote Number? ").strip()) + fail_req = game.get_fail_req(round) + game.do_vote(team, votes, fail_req, round, voten, proposer) game.trace = {} continue