diff --git a/README.md b/README.md index 1fce590..b0ecdd1 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,20 @@ The command is written in Default zsh file, that player allocated `fortune telle ./run_werewolf.zsh night ``` +## Example - Werewolf CLI + +When playing with n players, in the i-th (0-indexed) terminal, enter the following command: + +``` +cargo run --example werewolf-cli --release i ./data/n +``` + +For example, for a 4-players game, in the first terminal, you would enter: + +``` +cargo run --example werewolf-cli --release 0 ./data/4 +``` + ## Technical Details ### SHE (Somewhat Homomorphic Encryption) protocol diff --git a/examples/werewolf_cli/game.rs b/examples/werewolf_cli/game.rs index 6023ca6..c08acb5 100644 --- a/examples/werewolf_cli/game.rs +++ b/examples/werewolf_cli/game.rs @@ -245,9 +245,9 @@ impl Game { } if werewolf_count == 0 { - Some("村人".to_string()) + Some("Villagers side".to_string()) } else if werewolf_count >= villager_count { - Some("人狼".to_string()) + Some("Werewolves side".to_string()) } else { None } @@ -388,11 +388,14 @@ impl Game { { target.mark_for_death(); if am_werewolf { - events.push(format!("人狼が{}を襲撃対象に選びました。", target.name)); + events.push(format!( + "The werewolf has chosen {} as their target.", + target.name + )); } } else { if am_werewolf { - events.push("無効な襲撃対象が選択されました。".to_string()); + events.push("Invalid target was selected.".to_string()); } } @@ -419,19 +422,19 @@ impl Game { Fr::from(p.id as i32) == target_id.reveal() && p.is_alive && p.id != seer.id }) { let role_name = if target.is_werewolf() { - "人狼" + "Werewolf" } else { - "人狼ではない" + "Not Werewolf" }; if am_fortune_teller { events.push(format!( - "占い師が{}を占いました。結果:{}", + "The fortune teller divined {}. Result: {}", target.name, role_name )); } } else { if am_fortune_teller { - events.push("無効な占い対象が選択されました。".to_string()); + events.push("Invalid divination target was selected.".to_string()); } } } @@ -444,20 +447,20 @@ impl Game { for player in &mut self.state.players { if player.marked_for_death.reveal().is_one() && player.is_alive { player.kill(self.state.day); - events.push(format!("{}が無残な姿で発見されました。", player.name)); + events.push(format!("{} was found dead.", player.name)); player.marked_for_death = MpcBooleanField::pub_false(); } } if events.is_empty() { - events.push("昨夜は誰も襲撃されませんでした。".to_string()); + events.push("No one was attacked last night.".to_string()); } events } pub fn discussion_phase(&self) -> Vec { - vec!["討論フェーズが始まりました。".to_string()] + vec!["The discussion phase has begun.".to_string()] } pub fn voting_phase(&mut self, votes: Vec, is_prove: bool) -> Vec { @@ -468,14 +471,14 @@ impl Game { if voter.is_alive { vote_count[target] += 1; events.push(format!( - "{}が{}に投票しました。", + "{} voted for {}.", voter.name, self.state.players[target].name )); } } let max_votes = *vote_count.iter().max().unwrap(); - // 最大票数を持つプレイヤーを見つける。投票が同数の場合は + // Find the player with the maximum number of votes. let max_voted_indexes = self .state .players @@ -493,14 +496,14 @@ impl Game { let executed_index = if max_voted_indexes.len() == 1 { max_voted_indexes[0] } else { - // 投票が同数の場合は、ランダムに一人処刑される + // If there are multiple players with the same number of votes, one player is executed randomly. let random_index = rand::thread_rng().gen_range(0..max_voted_indexes.len()); max_voted_indexes[random_index] }; let player = &mut self.state.players[executed_index]; player.kill(self.state.day); - events.push(format!("{}が処刑されました。", player.name)); + events.push(format!("{} was executed.", player.name)); if is_prove { let votes = votes diff --git a/examples/werewolf_cli/game/role.rs b/examples/werewolf_cli/game/role.rs index 9157aea..71f37ea 100644 --- a/examples/werewolf_cli/game/role.rs +++ b/examples/werewolf_cli/game/role.rs @@ -14,7 +14,7 @@ pub fn assign_roles(player_count: usize, rules: &GameRules) -> Vec { let werewolf_count = (player_count as f32 * rules.werewolf_ratio).round() as usize; let seer_count = rules.seer_count; - // 役割を割り当てる + // assign roles for role in roles.iter_mut().take(werewolf_count) { *role = Role::Werewolf; } @@ -23,7 +23,7 @@ pub fn assign_roles(player_count: usize, rules: &GameRules) -> Vec { *role = Role::FortuneTeller; } - // ランダムに並び替える + // Shuffle randomly roles.shuffle(&mut rng); roles diff --git a/examples/werewolf_cli/main.rs b/examples/werewolf_cli/main.rs index 4a22736..3d54c65 100644 --- a/examples/werewolf_cli/main.rs +++ b/examples/werewolf_cli/main.rs @@ -24,9 +24,6 @@ struct Opt { } fn main() -> Result<(), Box> { - // let stream = TcpStream::connect("127.0.0.1:8080").await?; - // println!("サーバーに接続しました。"); - let opt = Opt::from_args(); // init @@ -98,6 +95,9 @@ fn register_players() -> Vec { if name.is_empty() { println!("Player name is empty."); continue; + } else if name.len() >= 20 { + println!("Player name must be less than 20 characters."); + continue; } else { break; } @@ -108,11 +108,16 @@ fn register_players() -> Vec { name ); - Net::broadcast(&name) + let mut bytes = vec![0u8; 20]; + bytes[..name.len()].copy_from_slice(name.as_bytes()); + Net::broadcast(&bytes) + .into_iter() + .map(|b| String::from_utf8_lossy(&b[..]).to_string()) + .collect() } fn night_phase(game: &mut Game) { - println!("\n--- 夜のフェーズ ---"); + println!("\n--- Night Phase ---"); let players = game.state.players.clone(); let player = players.iter().find(|p| p.id == Net::party_id()).unwrap(); @@ -148,7 +153,7 @@ fn night_phase(game: &mut Game) { } fn wait_for_enter() { - println!("Enterキーを押して次に進んでください。"); + println!("Press Enter to continue."); let mut input = String::new(); io::stdin().read_line(&mut input).unwrap(); } @@ -168,7 +173,7 @@ fn get_werewolf_target(game: &Game, player: &Player) -> MFr { if player.role.unwrap().is_werewolf() { println!( - "{}さん、あなたは人狼です。襲撃する対象を選んでください:", + "{}, you are a werewolf. Choose your target to attack:", player.name ); game.state @@ -178,7 +183,7 @@ fn get_werewolf_target(game: &Game, player: &Player) -> MFr { .for_each(|p| println!("{}: {}", p.id, p.name)); loop { - print!("対象のIDを入力してください: "); + print!("Enter the ID of your target: "); io::stdout().flush().unwrap(); let mut input = String::new(); @@ -193,7 +198,7 @@ fn get_werewolf_target(game: &Game, player: &Player) -> MFr { { break; } else { - println!("無効な選択です。もう一度選んでください。"); + println!("Invalid selection. Please choose again."); } } } else { @@ -208,7 +213,7 @@ fn get_seer_target(game: &Game, player: &Player) -> MFr { if player.role.unwrap() == Role::FortuneTeller { println!( - "{}さん、あなたは占い師です。占う対象を選んでください:", + "{}, you are a fortune teller. Choose a target to divine:", player.name ); game.state @@ -218,7 +223,7 @@ fn get_seer_target(game: &Game, player: &Player) -> MFr { .for_each(|p| println!("{}: {}", p.id, p.name)); loop { - print!("対象のIDを入力してください: "); + print!("Enter the ID of your target: "); io::stdout().flush().unwrap(); let mut input = String::new(); @@ -233,7 +238,7 @@ fn get_seer_target(game: &Game, player: &Player) -> MFr { { break; } else { - println!("無効な選択です。もう一度選んでください。"); + println!("Invalid selection. Please choose again."); } } } else { @@ -251,26 +256,26 @@ fn morning_phase(game: &mut Game) { } fn discussion_phase(game: &Game) { - println!("\n--- 討論フェーズ ---"); + println!("\n--- Discussion Phase ---"); let events = game.discussion_phase(); for event in events { println!("{}", event); } - println!("生存しているプレイヤー:"); + println!("Players still alive:"); game.state .players .iter() .filter(|p| p.is_alive) .for_each(|p| println!("{}: {}", p.id, p.name)); - println!("討論を行ってください。準備ができたらEnterキーを押してください。"); + println!("Please discuss. Press Enter when you are ready."); let mut input = String::new(); io::stdin().read_line(&mut input).unwrap(); } fn voting_phase(game: &mut Game) { - println!("\n--- 投票フェーズ ---"); + println!("\n--- Voting Phase ---"); let vote; let players = game.state.players.clone(); @@ -278,7 +283,7 @@ fn voting_phase(game: &mut Game) { let player = players.iter().find(|p| p.id == Net::party_id()).unwrap(); if player.is_alive { - println!("{}さん、投票する対象を選んでください:", player.name); + println!("{} please choose who to vote for:", player.name); game.state .players .iter() @@ -286,7 +291,7 @@ fn voting_phase(game: &mut Game) { .for_each(|p| println!("{}: {}", p.id, p.name)); loop { - print!("対象のIDを入力してください: "); + print!("Please enter the ID of the player you want to vote for: "); io::stdout().flush().unwrap(); let mut input = String::new(); @@ -302,11 +307,11 @@ fn voting_phase(game: &mut Game) { vote = target_id; break; } else { - println!("無効な選択です。もう一度選んでください。"); + println!("Invalid selection. Please choose again."); } } } else { - vote = usize::MAX; // 死亡したプレイヤーの投票は無効 + vote = usize::MAX; // The vote of a dead player is invalid. } clear_screen(); diff --git a/src/werewolf/types.rs b/src/werewolf/types.rs index 281443a..4cdc6b0 100644 --- a/src/werewolf/types.rs +++ b/src/werewolf/types.rs @@ -13,9 +13,9 @@ pub enum Role { impl Role { pub fn description(&self) -> &'static str { match self { - Role::Villager => "村人:特別な能力はありませんが、議論と投票に参加します。", - Role::Werewolf => "人狼:夜に村人を襲撃します。昼は村人のふりをします。", - Role::FortuneTeller => "占い師:夜に一人のプレイヤーの役割を知ることができます。", + Role::Villager => "Villager: They have no special abilities, but they participate in discussions and voting.", + Role::Werewolf => "Werewolf: They attack villagers at night. They pretend to be villagers during the day.", + Role::FortuneTeller => "Fortune Teller: They can know whether a player is a werewolf or not at night.", } }