Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/werewolf cli/update docs and minor bugfix #71

Merged
merged 3 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -376,11 +376,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 @@ -407,19 +410,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 @@ -432,20 +435,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 @@ -456,14 +459,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 @@ -481,14 +484,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
Loading