Skip to content

Commit

Permalink
Merge pull request #71 from Yoii-Inc/fix/werewolf_cli/update-docs-and…
Browse files Browse the repository at this point in the history
…-minor-bugfix

Fix/werewolf cli/update docs and minor bugfix
  • Loading branch information
taskooh authored Nov 11, 2024
2 parents be1c470 + 0dbee2c commit 6d78fb3
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 40 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 18 additions & 15 deletions examples/werewolf_cli/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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());
}
}

Expand All @@ -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());
}
}
}
Expand All @@ -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<String> {
vec!["討論フェーズが始まりました。".to_string()]
vec!["The discussion phase has begun.".to_string()]
}

pub fn voting_phase(&mut self, votes: Vec<usize>, is_prove: bool) -> Vec<String> {
Expand All @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions examples/werewolf_cli/game/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub fn assign_roles(player_count: usize, rules: &GameRules) -> Vec<Role> {
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;
}
Expand All @@ -23,7 +23,7 @@ pub fn assign_roles(player_count: usize, rules: &GameRules) -> Vec<Role> {
*role = Role::FortuneTeller;
}

// ランダムに並び替える
// Shuffle randomly
roles.shuffle(&mut rng);

roles
Expand Down
45 changes: 25 additions & 20 deletions examples/werewolf_cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ struct Opt {
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
// let stream = TcpStream::connect("127.0.0.1:8080").await?;
// println!("サーバーに接続しました。");

let opt = Opt::from_args();

// init
Expand Down Expand Up @@ -98,6 +95,9 @@ fn register_players() -> Vec<String> {
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;
}
Expand All @@ -108,11 +108,16 @@ fn register_players() -> Vec<String> {
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();
Expand Down Expand Up @@ -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();
}
Expand All @@ -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
Expand All @@ -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();
Expand All @@ -193,7 +198,7 @@ fn get_werewolf_target(game: &Game, player: &Player) -> MFr {
{
break;
} else {
println!("無効な選択です。もう一度選んでください。");
println!("Invalid selection. Please choose again.");
}
}
} else {
Expand All @@ -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
Expand All @@ -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();
Expand All @@ -233,7 +238,7 @@ fn get_seer_target(game: &Game, player: &Player) -> MFr {
{
break;
} else {
println!("無効な選択です。もう一度選んでください。");
println!("Invalid selection. Please choose again.");
}
}
} else {
Expand All @@ -251,42 +256,42 @@ 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();

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()
.filter(|p| p.is_alive && p.id != player.id)
.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();
Expand All @@ -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();

Expand Down
6 changes: 3 additions & 3 deletions src/werewolf/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
}
}

Expand Down

0 comments on commit 6d78fb3

Please sign in to comment.