diff --git a/STORY_VN.md b/STORY_VN.md index c1ef673f..c6bcbe16 100644 --- a/STORY_VN.md +++ b/STORY_VN.md @@ -24,6 +24,7 @@ Vậy bên nào sẽ chiến thắng, Thường dân, Werewolf, hay là phe Ph - [Seer] Tiên tri: Soi một người chơi có phải là sói hay không. Có thể giết chết Cáo nếu soi trúng Cáo. - [Guard] Bảo vệ: Bảo vệ được chọn 1 người khác nhau mỗi đêm trừ bản thân và người được chọn sẽ bất tử đêm đó. - [Lycan] Người hóa sói: Người hóa sói thuộc Phe dân làng, nhưng nếu được chỉ định bởi Tiên tri, thì sẽ bị thông báo là Sói. +- [Hunter] Thợ săn: Vào ban đêm, được chọn một người chơi khác để chết chung nếu bản thân bị giết. ### II. Phe sói: Chiến thắng nếu giết hết dân làng. Nhận biết được **sói** cùng phe. diff --git a/commands/command.py b/commands/command.py index e68c3b1e..abec4cb3 100644 --- a/commands/command.py +++ b/commands/command.py @@ -127,7 +127,7 @@ async def parse_command(client, game, message): if not game.is_started(): await message.reply(text_templates.generate_text("game_not_started_text")) return - if message.channel.name not in (config.GAMEPLAY_CHANNEL, config.LOBBY_CHANNEL): # Only use in common channels, no spamming + if message.channel.name not in (config.GAMEPLAY_CHANNEL, config.LOBBY_CHANNEL): # Only use in common channels, no spamming await message.reply(text_templates.generate_text("invalid_channel_text", channel=f"#{config.LOBBY_CHANNEL} #{config.GAMEPLAY_CHANNEL}")) return msg = await game.self_check_channel() diff --git a/game/__init__.py b/game/__init__.py index 6a5684d3..d9b225e2 100644 --- a/game/__init__.py +++ b/game/__init__.py @@ -243,8 +243,8 @@ async def delete_channel(self): async def self_check_channel(self): try: await asyncio.gather( - *[player.create_personal_channel(self_check=True) for player in self.players.values()] - ) + *[player.create_personal_channel(self_check=True) for player in self.players.values()] + ) return text_templates.generate_text('self_check_text') except Exception as e: @@ -611,6 +611,17 @@ async def do_end_daytime_phase(self): "couple_died_on_day_text", config.GAMEPLAY_CHANNEL, died_player=f"<@{lynched}>", follow_player=f"<@{cupid_couple}>" ) + # Kill anyone who is hunted if hunter dies with his couple + hunted = await self.get_hunted_target_on_hunter_death(cupid_couple) + if hunted: + await self.interface.send_action_text_to_channel( + "hunter_killed_text", config.GAMEPLAY_CHANNEL, target=f"<@{hunted}>" + ) + hunted = await self.get_hunted_target_on_hunter_death(lynched) + if hunted: + await self.interface.send_action_text_to_channel( + "hunter_killed_text", config.GAMEPLAY_CHANNEL, target=f"<@{hunted}>" + ) else: await self.interface.send_action_text_to_channel("execution_none_text", config.GAMEPLAY_CHANNEL) @@ -665,6 +676,14 @@ async def do_end_nighttime_phase(self): final_kill_list.append(_id) if self.cupid_dict.get(_id): cupid_couple = self.cupid_dict[_id] + hunted = await self.get_hunted_target_on_hunter_death(cupid_couple) + if hunted: + final_kill_list.append(hunted) + + # Kill anyone who is hunted if hunter is killed + hunted = await self.get_hunted_target_on_hunter_death(_id) + if hunted: + final_kill_list.append(hunted) kills = ", ".join(f"<@{_id}>" for _id in final_kill_list) self.night_pending_kill_list = [] # Reset killed list for next day @@ -844,6 +863,8 @@ async def do_player_action(self, cmd, author_id, *targets_id): return await self.kill(author, targets[0]) if cmd == "guard": return await self.guard(author, targets[0]) + if cmd == "hunt": + return await self.hunt(author, targets[0]) if cmd == "seer": return await self.seer(author, targets[0]) if cmd == "reborn": @@ -1016,6 +1037,31 @@ async def ship(self, author, target1, target2): return text_templates.generate_text("cupid_after_ship_text", target1=f"<@{target1_id}>", target2=f"<@{target2_id}>") + async def hunt(self, author, target): + if not isinstance(author, roles.Hunter): + return text_templates.generate_text("invalid_author_text") + + if self.game_phase != const.GamePhase.NIGHT: + return text_templates.generate_text("invalid_nighttime_text") + + # author_id = author.player_id + target_id = target.player_id + + if not target.is_alive(): + return text_templates.generate_text("invalid_hunter_target_text", user=f"<@{target_id}>") + + author.set_hunted_target(target_id) + return text_templates.generate_text("hunter_after_voting_text", target=f"<@{target_id}>") + + # Kill anyone who is hunted + async def get_hunted_target_on_hunter_death(self, hunter): + if isinstance(self.players[hunter], roles.Hunter): + hunted = self.players[hunter].get_hunted_target() + if hunted and hunted != hunter: + if await self.players[hunted].get_killed(): + return hunted + return None + async def register_auto(self, author, subcmd): def check(pred): def wrapper(f): diff --git a/game/roles/__init__.py b/game/roles/__init__.py index 3fb95ad1..04b96400 100644 --- a/game/roles/__init__.py +++ b/game/roles/__init__.py @@ -11,6 +11,7 @@ from game.roles.zombie import Zombie from game.roles.cupid import Cupid from game.roles.chief import Chief +from game.roles.hunter import Hunter import utils @@ -18,7 +19,7 @@ def get_all_roles(): - return Villager, Werewolf, Seer, Guard, Lycan, Betrayer, Superwolf, Fox, Witch, Zombie, Cupid, Chief + return Villager, Werewolf, Seer, Guard, Lycan, Betrayer, Superwolf, Fox, Witch, Zombie, Cupid, Chief, Hunter def get_role_type(name): diff --git a/game/roles/character.py b/game/roles/character.py index 37d9f9ac..1119ece1 100644 --- a/game/roles/character.py +++ b/game/roles/character.py @@ -64,7 +64,7 @@ def on_use_mana(self): def get_mana(self): return self.mana - async def create_personal_channel(self, self_check = False): + async def create_personal_channel(self, self_check=False): await self.interface.create_channel(self.channel_name) await self.interface.add_user_to_channel(self.player_id, self.channel_name, is_read=True, is_send=True) if not self_check: diff --git a/game/roles/hunter.py b/game/roles/hunter.py new file mode 100644 index 00000000..fbb6e1a4 --- /dev/null +++ b/game/roles/hunter.py @@ -0,0 +1,19 @@ +from game.roles.villager import Villager + + +class Hunter(Villager): + ''' Hunter is basic Villager with ability to kill anyone on his death (die with him) ''' + + def __init__(self, interface, player_id, player_name): + super().__init__(interface, player_id, player_name) + self.hunted_target = None + + async def on_action(self, embed_data): + await self.interface.send_action_text_to_channel("hunter_before_voting_text", self.channel_name) + await self.interface.send_embed_to_channel(embed_data, self.channel_name) + + def get_hunted_target(self): + return self.hunted_target + + def set_hunted_target(self, hunted): + self.hunted_target = hunted diff --git a/game/text_template.py b/game/text_template.py index 120fe9f5..c4358949 100644 --- a/game/text_template.py +++ b/game/text_template.py @@ -33,7 +33,7 @@ def generate_id_player_list(player_list, alive_status, reveal_role=False): for user in player_list: if alive_status is None and user.status == CharacterStatus.KILLED: # Handle for dead players in All list - id_player_list.append(f"💀 -> <@{user.player_id}>" + (f" - {user.get_role()}" if reveal_role else "")) # Do not increase row_id when user is dead + id_player_list.append(f"💀 -> <@{user.player_id}>" + (f" - {user.get_role()}" if reveal_role else "")) # Do not increase row_id when user is dead else: # Show player id for: alive players in All list, Alive list; dead players in Dead list. # Also show role info if it's a dead list reveal mode is enabled. diff --git a/json/command_info.json b/json/command_info.json index 5b4915e2..91c733cc 100644 --- a/json/command_info.json +++ b/json/command_info.json @@ -152,6 +152,14 @@ "exclusive_roles": ["Guard"], "required_params": ["player_id"] }, + "hunt": { + "description": { + "vi": "Săn một ai đó.", + "en": "Hunt a player." + }, + "exclusive_roles": ["Hunter"], + "required_params": ["player_id"] + }, "seer": { "description": { "vi": "Soi một người bất kỳ.", diff --git a/json/role_config.json b/json/role_config.json index 064f1a15..35415d94 100644 --- a/json/role_config.json +++ b/json/role_config.json @@ -23,26 +23,28 @@ {"Werewolf": 1, "Seer": 1, "Guard": 1, "Witch": 1, "Cupid": 1, "Fox": 1, "Betrayer": 1}, {"Werewolf": 2, "Seer": 1, "Guard": 1, "Zombie": 1, "Cupid": 1 , "Witch": 1}, {"Werewolf": 1, "Seer": 1, "Guard": 1, "Chief": 1, "Witch": 1, "Cupid": 1, "Fox": 1, "Lycan": 1}, - {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 1, "Witch": 1, "Cupid": 1, "Chief": 1}, + {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Hunter": 1, "Witch": 1, "Cupid": 1, "Chief": 1}, {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 3, "Witch": 1}, - {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Witch": 1, "Villager": 1, "Fox": 1, "Lycan": 1}, - {"Werewolf": 2, "Seer": 1, "Guard": 1, "Villager": 1, "Witch" : 1, "Cupid": 1, "Chief": 1}, + {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Witch": 1, "Hunter": 1, "Fox": 1, "Lycan": 1}, + {"Werewolf": 2, "Seer": 1, "Guard": 1, "Hunter": 1, "Witch" : 1, "Cupid": 1, "Chief": 1}, {"Werewolf": 2, "Seer": 1, "Guard": 1, "Chief": 1, "Witch": 1, "Cupid": 1, "Fox": 1, "Zombie": 1}, {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Zombie": 1, "Fox": 1, "Lycan": 1, "Cupid": 1, "Witch": 1}, - {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 2, "Fox": 1, "Witch": 1, "Chief": 1}, - {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 2, "Fox": 1, "Cupid": 1, "Chief": 1}, + {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 1, "Fox": 1, "Witch": 1, "Chief": 1, "Hunter": 1}, + {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 1, "Fox": 1, "Cupid": 1, "Chief": 1, "Hunter":1}, {"Werewolf": 3, "Seer": 1, "Guard": 1, "Witch": 1, "Villager": 3, "Cupid": 1, "Chief": 1}, {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Zombie": 1, "Fox": 1, "Chief": 1, "Cupid": 1, "Witch": 1, "Betrayer":1}, {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Zombie": 1, "Fox": 1, "Lycan": 1, "Cupid": 1, "Witch": 1, "Betrayer":1, "Chief": 1}, {"Werewolf": 2, "Seer": 1, "Guard": 1, "Villager": 2, "Witch": 1, "Fox": 1, "Lycan": 1, "Cupid": 1, "Chief": 1}, - {"Werewolf": 3, "Seer": 1, "Guard": 1, "Villager": 1, "Witch": 1, "Fox": 1, "Lycan": 1, "Cupid": 1, "Chief": 1}, - {"Superwolf": 1, "Werewolf": 2, "Seer": 1, "Guard": 1, "Villager": 2, "Witch": 1, "Cupid": 1, "Fox": 1, "Chief": 1}, - {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 6, "Lycan": 1}, + {"Werewolf": 3, "Seer": 1, "Guard": 1, "Hunter": 1, "Witch": 1, "Fox": 1, "Lycan": 1, "Cupid": 1, "Chief": 1}, + {"Superwolf": 1, "Werewolf": 2, "Seer": 1, "Guard": 1, "Villager": 1, "Witch": 1, "Cupid": 1, "Fox": 1, "Chief": 1, "Hunter": 1}, {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Villager": 3, "Lycan": 2, "Witch": 1, "Cupid": 1}, {"Werewolf": 2, "Seer": 1, "Guard": 1, "Fox": 1, "Witch": 1, "Villager": 4, "Lycan": 1}, {"Superwolf": 1, "Werewolf": 2, "Seer": 1, "Guard": 1, "Villager": 4, "Witch": 1, "Cupid": 1}, {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Fox": 1, "Witch": 1, "Villager": 1, "Lycan": 2, "Cupid": 1, "Betrayer": 1, "Zombie": 1}, + {"Superwolf": 1, "Werewolf": 1, "Seer": 1, "Guard": 1, "Fox": 1, "Witch": 1, "Villager": 1, "Lycan": 2, "Cupid": 1, "Betrayer": 1, "Zombie": 1, "Hunter": 1}, {"Werewolf": 3, "Seer": 1, "Guard": 1, "Witch": 1, "Villager": 4, "Lycan": 1, "Cupid": 1}, + {"Werewolf": 3, "Seer": 1, "Guard": 1, "Witch": 1, "Villager": 4, "Lycan": 1, "Cupid": 1, "Hunter": 1}, + {"Werewolf": 3, "Seer": 1, "Guard": 1, "Witch": 1, "Villager": 4, "Lycan": 1, "Cupid": 1, "Hunter": 1, "Chief": 1}, {"Superwolf": 1, "Werewolf": 2, "Seer": 1, "Guard": 1, "Villager": 5, "Lycan": 1, "Cupid": 1, "Witch": 1}, {"Superwolf": 1, "Werewolf": 2, "Seer": 1, "Guard": 1, "Fox": 1, "Witch": 1, "Villager": 4, "Lycan": 1, "Cupid": 1}, {"Superwolf": 1, "Werewolf": 2, "Seer": 1, "Guard": 1, "Witch": 1, "Villager": 5, "Lycan": 1, "Fox": 1, "Cupid": 1} diff --git a/json/role_info.json b/json/role_info.json index 944b24dd..924f700d 100644 --- a/json/role_info.json +++ b/json/role_info.json @@ -39,6 +39,16 @@ "daytime_command": "vote", "nighttime_commands": ["guard"] }, + "Hunter": { + "name_vi": "Thợ săn", + "party": "Villager", + "description": { + "vi": "Vào ban đêm, chọn 1 người khác để chết chung với mình. Ghi nhận lệnh cuối cùng.", + "en": "Target a player to die together at night. Accept latest command." + }, + "daytime_command": "vote", + "nighttime_commands": ["hunt"] + }, "Lycan": { "name_vi": "Người hóa sói", "party": "Villager", diff --git a/json/text_template.json b/json/text_template.json index 1fdce547..b2b9ef6a 100644 --- a/json/text_template.json +++ b/json/text_template.json @@ -411,6 +411,42 @@ "en": ["You protected this person yesterday. Let's change the target today!"] } }, + "hunter_before_voting_text": { + "params": [], + "template": { + "vi": [ + "🔫 Bạn muốn ai chết chung với mình? Hãy nhập `{bot_prefix}hunt ID` để chọn đối tượng.", + "💡 Ví dụ: `{bot_prefix}hunter 2`", + "⚠️ Bạn được sử dụng kỹ năng này bất kỳ lúc nào, có thể dùng lên bản thân nếu đổi ý. Nhưng đừng giết bậy bạn nhé!" + ], + "en": [ + "🔫 Choose someone to die with, enter `{bot_prefix}hunt ID` to let that person die with you.", + "💡 Example: `{bot_prefix}hunt 2`", + "⚠️ You can use this skill anytime! Tagert yourself if you change your mind." + ] + } + }, + "hunter_after_voting_text": { + "params": ["target"], + "template": { + "vi": ["🔫 Thợ săn đã xác định đối tượng thành công {target}"], + "en": ["🔫 Hunter has targeted victim {target}"] + } + }, + "invalid_hunter_target_text": { + "params": [], + "template": { + "vi": ["Người ta đã chết rồi còn cố giết nữa 😡"], + "en": ["Don't target dead person 😡"] + } + }, + "hunter_killed_text": { + "params": ["target"], + "template": { + "vi": ["🔫 Trước khi chết, thợ săn bắn phát súng cuối cùng vào {target}"], + "en": ["🔫 Before dying, the hunter shot {target}"] + } + }, "witch_before_voting_text": { "params": [], "template": { diff --git a/test.py b/test.py index c02135d6..bf041cb8 100644 --- a/test.py +++ b/test.py @@ -94,9 +94,12 @@ async def test_game(): game = Game(None, interface.ConsoleInterface(None)) # Run single test - - await test_case(game, "testcases/case-chief-vote-break-draw.json") - + await test_case(game, "testcases/case-hunter-couple-die-together-by-kill.json") + await test_case(game, "testcases/case-hunter-couple-die-together-by-vote.json") + await test_case(game, "testcases/case-hunter-hunt-fox.json") + await test_case(game, "testcases/case-hunter-hunt-wolf.json") + await test_case(game, "testcases/case-hunter-simple.json") + await test_case(game, "testcases/case-hunter-hunt-night1.json") # Run all tests directory = "testcases" for filename in os.listdir(directory): diff --git a/testcases/case-hunter-couple-die-together-by-kill.json b/testcases/case-hunter-couple-die-together-by-kill.json new file mode 100644 index 00000000..6dd0829c --- /dev/null +++ b/testcases/case-hunter-couple-die-together-by-kill.json @@ -0,0 +1,74 @@ +{ + "name": "Hunter die together by kill", + "player_list": { + "w1": "Werewolf", + "s1": "Seer", + "h1": "Hunter", + "c1": "Cupid", + "sw1": "Superwolf", + "wi1": "Witch" + }, + "timeline": [ + { + "time": "day1", + "alive": [ + "w1", + "h1", + "c1", + "sw1", + "wi1", + "s1" + ], + "action": [ + "w1 vote h1", + "h1 vote w1", + "c1 ship h1 s1", + "sw1 vote c1", + "wi1 vote sw1" + ] + }, + { + "time": "night1", + "alive": [ + "w1", + "s1", + "h1", + "c1", + "wi1", + "sw1" + ], + "action": [ + "w1 kill wi1", + "s1 seer h1", + "h1 hunt c1" + ] + }, + { + "time": "day2", + "alive": [ + "w1", + "s1", + "h1", + "c1", + "sw1" + ], + "action": [ + "w1 vote sw1", + "c1 vote sw1" + ] + }, + { + "time": "night2", + "alive": [ + "w1", + "s1", + "h1", + "c1" + ], + "action": [ + "w1 kill s1" + ] + } + ], + "win": "Werewolf" +} diff --git a/testcases/case-hunter-couple-die-together-by-vote.json b/testcases/case-hunter-couple-die-together-by-vote.json new file mode 100644 index 00000000..ce352aeb --- /dev/null +++ b/testcases/case-hunter-couple-die-together-by-vote.json @@ -0,0 +1,63 @@ +{ + "name": "Hunter die together by vote", + "player_list": { + "w1": "Werewolf", + "s1": "Seer", + "h1": "Hunter", + "c1": "Cupid", + "sw1": "Superwolf", + "wi1": "Witch" + }, + "timeline": [ + { + "time": "day1", + "alive": [ + "w1", + "h1", + "c1", + "sw1", + "wi1", + "s1" + ], + "action": [ + "w1 vote h1", + "h1 vote w1", + "c1 ship h1 sw1", + "sw1 vote c1", + "wi1 vote sw1" + ] + }, + { + "time": "night1", + "alive": [ + "w1", + "s1", + "h1", + "c1", + "wi1", + "sw1" + ], + "action": [ + "w1 kill s1", + "s1 seer h1", + "h1 hunt c1" + ] + }, + { + "time": "day2", + "alive": [ + "w1", + "c1", + "wi1", + "h1", + "sw1" + ], + "action": [ + "w1 vote sw1", + "c1 vote sw1", + "wi1 vote sw1" + ] + } + ], + "win": "Werewolf" +} diff --git a/testcases/case-hunter-hunt-fox.json b/testcases/case-hunter-hunt-fox.json new file mode 100644 index 00000000..6ed898d4 --- /dev/null +++ b/testcases/case-hunter-hunt-fox.json @@ -0,0 +1,72 @@ +{ + "name": "Hunter hunt fox test", + "player_list": { + "w1": "Werewolf", + "f1": "Fox", + "g1": "Guard", + "h1": "Hunter", + "wi1": "Witch" + }, + "timeline": [ + { + "time": "day1", + "alive": [ + "w1", + "f1", + "g1", + "h1", + "wi1" + ], + "action": [ + "w1 vote h1", + "f1 vote f1", + "g1 vote h1", + "h1 vote f1", + "wi1 vote h1" + ] + }, + { + "time": "night1", + "alive": [ + "w1", + "g1", + "f1", + "wi1" + ], + "action": [ + "w1 kill g1", + "g1 guard g1", + "wi1 reborn h1" + ] + }, + { + "time": "day2", + "alive": [ + "w1", + "wi1", + "h1", + "f1", + "g1" + ], + "action": [ + "g1 vote g1", + "f1 vote wi1", + "wi1 vote wi1" + ] + }, + { + "time": "night2", + "alive": [ + "w1", + "g1", + "h1", + "f1" + ], + "action": [ + "w1 kill h1", + "h1 hunt f1" + ] + } + ], + "win": "Werewolf" +} diff --git a/testcases/case-hunter-hunt-night1.json b/testcases/case-hunter-hunt-night1.json new file mode 100644 index 00000000..f076bb61 --- /dev/null +++ b/testcases/case-hunter-hunt-night1.json @@ -0,0 +1,69 @@ +{ + "name": "Hunter hunt night1 test", + "player_list": { + "w1": "Werewolf", + "f1": "Fox", + "g1": "Guard", + "h1": "Hunter", + "wi1": "Witch" + }, + "timeline": [ + { + "time": "day1", + "alive": [ + "w1", + "f1", + "g1", + "h1", + "wi1" + ], + "action": [ + "w1 vote h1", + "f1 vote f1", + "g1 vote f1", + "h1 vote f1", + "wi1 vote h1" + ] + }, + { + "time": "night1", + "alive": [ + "w1", + "g1", + "h1", + "wi1" + ], + "action": [ + "w1 kill g1", + "g1 guard g1", + "h1 hunt w1" + ] + }, + { + "time": "day2", + "alive": [ + "w1", + "wi1", + "h1", + "g1" + ], + "action": [ + "g1 vote g1", + "h1 vote wi1", + "wi1 vote wi1" + ] + }, + { + "time": "night2", + "alive": [ + "w1", + "g1", + "h1" + ], + "action": [ + "w1 kill h1" + ] + } + ], + "win": "Villager" +} \ No newline at end of file diff --git a/testcases/case-hunter-hunt-wolf.json b/testcases/case-hunter-hunt-wolf.json new file mode 100644 index 00000000..cdacd0d0 --- /dev/null +++ b/testcases/case-hunter-hunt-wolf.json @@ -0,0 +1,72 @@ +{ + "name": "Hunter hunt other test", + "player_list": { + "w1": "Werewolf", + "f1": "Fox", + "g1": "Guard", + "h1": "Hunter", + "wi1": "Witch" + }, + "timeline": [ + { + "time": "day1", + "alive": [ + "w1", + "f1", + "g1", + "h1", + "wi1" + ], + "action": [ + "w1 vote h1", + "f1 vote f1", + "g1 vote h1", + "h1 vote f1", + "wi1 vote h1" + ] + }, + { + "time": "night1", + "alive": [ + "w1", + "g1", + "f1", + "wi1" + ], + "action": [ + "w1 kill g1", + "g1 guard g1", + "wi1 reborn h1" + ] + }, + { + "time": "day2", + "alive": [ + "w1", + "wi1", + "h1", + "f1", + "g1" + ], + "action": [ + "g1 vote g1", + "f1 vote wi1", + "wi1 vote wi1" + ] + }, + { + "time": "night2", + "alive": [ + "w1", + "g1", + "h1", + "f1" + ], + "action": [ + "w1 kill h1", + "h1 hunt w1" + ] + } + ], + "win": "Fox" +} \ No newline at end of file diff --git a/testcases/case-hunter-simple.json b/testcases/case-hunter-simple.json new file mode 100644 index 00000000..99496653 --- /dev/null +++ b/testcases/case-hunter-simple.json @@ -0,0 +1,84 @@ +{ + "name": "Hunter simple test", + "player_list": { + "w1": "Werewolf", + "f1": "Fox", + "g1": "Guard", + "h1": "Hunter", + "wi1": "Witch" + }, + "timeline": [ + { + "time": "day1", + "alive": [ + "w1", + "f1", + "g1", + "h1", + "wi1" + ], + "action": [ + "w1 vote h1", + "f1 vote f1", + "g1 vote h1", + "h1 vote f1", + "wi1 vote h1" + ] + }, + { + "time": "night1", + "alive": [ + "w1", + "g1", + "f1", + "wi1" + ], + "action": [ + "w1 kill g1", + "g1 guard g1", + "wi1 reborn h1" + ] + }, + { + "time": "day2", + "alive": [ + "w1", + "wi1", + "h1", + "f1", + "g1" + ], + "action": [ + "g1 vote g1", + "f1 vote wi1", + "wi1 vote wi1" + ] + }, + { + "time": "night2", + "alive": [ + "w1", + "g1", + "h1", + "f1" + ], + "action": [ + "w1 kill f1", + "g1 guard h1" + ] + }, + { + "time": "day3", + "alive": [ + "w1", + "h1", + "g1" + ], + "action": [ + "w1 vote w1", + "h1 vote w1" + ] + } + ], + "win": "Villager" +} \ No newline at end of file