diff --git a/Cargo.toml b/Cargo.toml index a058c57..2651d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,3 +76,7 @@ path = "examples/online.rs" [[example]] name = "bin-werewolf" path = "examples/bin_werewolf.rs" + +[[example]] +name = "werewolf-cli" +path = "examples/werewolf_cli/main.rs" diff --git a/examples/werewolf_cli/game.rs b/examples/werewolf_cli/game.rs new file mode 100644 index 0000000..7154746 --- /dev/null +++ b/examples/werewolf_cli/game.rs @@ -0,0 +1,376 @@ +use ark_bls12_377::Fr; +use ark_crypto_primitives::commitment::pedersen; +use ark_ff::BigInteger; +use ark_ff::PrimeField; +use ark_ff::UniformRand; +use ark_marlin::IndexProverKey; +use mpc_algebra::commitment::CommitmentScheme; +use mpc_algebra::FromLocal; +use mpc_algebra::Reveal; +use nalgebra::DMatrix; +use player::Player; +use rand::Rng; +use zk_mpc::circuits::LocalOrMPC; +use zk_mpc::circuits::RoleAssignmentCircuit; +use zk_mpc::marlin::prove_and_verify; +use zk_mpc::marlin::LocalMarlin; +use zk_mpc::marlin::MFr; +use zk_mpc::werewolf::types::GroupingParameter; +use zk_mpc::werewolf::types::Role; +use zk_mpc::werewolf::utils::calc_shuffle_matrix; +use zk_mpc::werewolf::utils::generate_individual_shuffle_matrix; +use zk_mpc::werewolf::utils::load_random_commitment; +use zk_mpc::werewolf::utils::load_random_value; + +pub mod player; +pub mod role; + +pub struct Game { + pub state: GameState, + pub rules: GameRules, +} + +pub struct GameState { + pub players: Vec, + pub current_phase: GamePhase, + pub day: u32, +} + +pub struct GameRules { + pub min_players: usize, + pub max_players: usize, + pub werewolf_ratio: f32, + pub seer_count: usize, + pub grouping_parameter: GroupingParameter, +} + +pub enum GamePhase { + Night, + Morning, + Discussion, + Voting, +} + +impl Game { + pub fn new(player_names: Vec, rules: GameRules) -> Self { + let players = player::create_players(player_names, None); + + Self { + state: GameState { + players, + current_phase: GamePhase::Night, + day: 1, + }, + rules, + } + } + + pub fn role_assignment(&mut self, is_prove: bool) { + let role = role::calc_role(self.state.players.len(), &self.rules); + + for (player, role) in self.state.players.iter_mut().zip(role) { + player.role = Some(role); + } + + if is_prove { + // prove and verify + if let Err(e) = self.prove_and_verify() { + eprintln!("Failed to prove and verify: {}", e); + } + } + } + + fn prove_and_verify(&self) -> Result<(), std::io::Error> { + let n = self.state.players.len(); + let m = self.rules.grouping_parameter.get_num_groups(); + + let rng = &mut ark_std::test_rng(); + + let pedersen_param = >::PedersenComScheme::setup(rng).unwrap(); + + let player_randomness = load_random_value()?; + let player_commitment = load_random_commitment()?; + + // calc + let shuffle_matrix = vec![ + generate_individual_shuffle_matrix( + self.rules.grouping_parameter.get_num_players(), + self.rules.grouping_parameter.get_num_groups(), + rng, + ); + 2 + ]; + + let mut inputs = vec![]; + + for id in 0..n { + let (role, role_val, player_ids) = + calc_shuffle_matrix(&self.rules.grouping_parameter, &shuffle_matrix, id).unwrap(); + println!("role is {:?}", role); + println!("fellow is {:?}", player_ids); + inputs.push(Fr::from(role_val as i32)); + } + + println!("inputs is {:?}", inputs); + + let randomness = (0..n) + .map(|_| >::PedersenRandomness::rand(rng)) + .collect::>(); + + let commitment = (0..n) + .map(|i| { + >::PedersenComScheme::commit( + &pedersen_param, + &inputs[i].into_repr().to_bytes_le(), + &randomness[i], + ) + .unwrap() + }) + .collect::>(); + + let local_role_circuit = RoleAssignmentCircuit { + num_players: n, + max_group_size: self.rules.grouping_parameter.get_max_group_size(), + pedersen_param: pedersen_param.clone(), + tau_matrix: DMatrix::::zeros(n + m, n + m), + shuffle_matrices: vec![DMatrix::::zeros(n + m, n + m); 2], + role_commitment: commitment, + randomness, + player_randomness: player_randomness.clone(), + player_commitment: player_commitment.clone(), + }; + + let srs = LocalMarlin::universal_setup(1000000, 50000, 100000, rng).unwrap(); + let (index_pk, index_vk) = LocalMarlin::index(&srs, local_role_circuit).unwrap(); + let mpc_index_pk = IndexProverKey::from_public(index_pk); + + let mpc_pedersen_param = + >::PedersenParam::from_local(&pedersen_param); + + let mpc_randomness = (0..n) + .map(|_| >::PedersenRandomness::rand(rng)) + .collect::>(); + + let converted_inputs = inputs + .iter() + .map(|x| >::convert_input(&MFr::from_public(*x))) + .collect::>(); + + let role_commitment = (0..n) + .map(|i| { + >::PedersenComScheme::commit( + &mpc_pedersen_param, + &converted_inputs[i], + &mpc_randomness[i], + ) + .unwrap() + }) + .collect::>(); + + let mpc_role_circuit = RoleAssignmentCircuit { + num_players: n, + max_group_size: self.rules.grouping_parameter.get_max_group_size(), + pedersen_param: mpc_pedersen_param, + tau_matrix: self.rules.grouping_parameter.generate_tau_matrix(), + shuffle_matrices: shuffle_matrix, + randomness: mpc_randomness, + role_commitment: role_commitment.clone(), + player_randomness: player_randomness + .iter() + .map(|x| MFr::from_public(*x)) + .collect::>(), + player_commitment: player_commitment + .iter() + .map(|x| >::PedersenCommitment::from_public(*x)) + .collect::>(), + }; + + let mut inputs = player_commitment + .iter() + .flat_map(|c| vec![c.x, c.y]) + .collect::>(); + + role_commitment.iter().for_each(|x| { + inputs.push(x.reveal().x); + inputs.push(x.reveal().y); + }); + + assert!(prove_and_verify( + &mpc_index_pk, + &index_vk, + mpc_role_circuit.clone(), + inputs + )); + + Ok(()) + } + + pub fn next_phase(&mut self) { + self.state.current_phase = match self.state.current_phase { + GamePhase::Night => GamePhase::Morning, + GamePhase::Morning => GamePhase::Discussion, + GamePhase::Discussion => GamePhase::Voting, + GamePhase::Voting => { + self.state.day += 1; + GamePhase::Night + } + }; + } + + pub fn check_victory_condition(&self) -> Option { + let alive_players: Vec<&Player> = + self.state.players.iter().filter(|p| p.is_alive).collect(); + let werewolf_count = alive_players.iter().filter(|p| p.is_werewolf()).count(); + let villager_count = alive_players.len() - werewolf_count; + + if werewolf_count == 0 { + Some("村人".to_string()) + } else if werewolf_count >= villager_count { + Some("人狼".to_string()) + } else { + None + } + } + + pub fn werewolf_attack(&mut self, target_id: usize) -> Vec { + let mut events = Vec::new(); + + if let Some(_werewolf) = self + .state + .players + .iter() + .find(|p| p.is_werewolf() && p.is_alive) + { + if let Some(target) = self + .state + .players + .iter_mut() + .find(|p| p.id == target_id && p.is_alive && !p.is_werewolf()) + { + target.mark_for_death(); + events.push(format!("人狼が{}を襲撃対象に選びました。", target.name)); + } else { + events.push("無効な襲撃対象が選択されました。".to_string()); + } + } + + events + } + + pub fn seer_divination(&self, target_id: usize) -> Vec { + let mut events = Vec::new(); + + if let Some(seer) = self + .state + .players + .iter() + .find(|p| p.role == Some(Role::FortuneTeller) && p.is_alive) + { + if let Some(target) = self + .state + .players + .iter() + .find(|p| p.id == target_id && p.is_alive && p.id != seer.id) + { + let role_name = if target.is_werewolf() { + "人狼" + } else { + "人狼ではない" + }; + events.push(format!( + "占い師が{}を占いました。結果:{}", + target.name, role_name + )); + } else { + events.push("無効な占い対象が選択されました。".to_string()); + } + } + + events + } + + pub fn morning_phase(&mut self) -> Vec { + let mut events = Vec::new(); + + for player in &mut self.state.players { + if player.marked_for_death && player.is_alive { + player.kill(self.state.day); + events.push(format!("{}が無残な姿で発見されました。", player.name)); + player.marked_for_death = false; + } + } + + if events.is_empty() { + events.push("昨夜は誰も襲撃されませんでした。".to_string()); + } + + events + } + + pub fn discussion_phase(&self) -> Vec { + vec!["討論フェーズが始まりました。".to_string()] + } + + pub fn voting_phase(&mut self, votes: Vec) -> Vec { + let mut events = Vec::new(); + let mut vote_count = vec![0; self.state.players.len()]; + + for (voter, &target) in self.state.players.iter().zip(votes.iter()) { + if voter.is_alive { + vote_count[target] += 1; + events.push(format!( + "{}が{}に投票しました。", + voter.name, self.state.players[target].name + )); + } + } + + let max_votes = *vote_count.iter().max().unwrap(); + // 最大票数を持つプレイヤーを見つける。投票が同数の場合は + let max_voted_indexes = self + .state + .players + .iter() + .enumerate() + .filter_map(|(i, p)| { + if p.is_alive && vote_count[i] == max_votes { + Some(i) + } else { + None + } + }) + .collect::>(); + assert!(max_voted_indexes.len() >= 1); + let executed_index = if max_voted_indexes.len() == 1 { + max_voted_indexes[0] + } else { + // 投票が同数の場合は、ランダムに一人処刑される + 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 + } +} + +impl GameRules { + pub fn new( + min_players: usize, + max_players: usize, + werewolf_ratio: f32, + seer_count: usize, + grouping_parameter: GroupingParameter, + ) -> Self { + Self { + min_players, + max_players, + werewolf_ratio, + seer_count, + grouping_parameter, + } + } +} diff --git a/examples/werewolf_cli/game/player.rs b/examples/werewolf_cli/game/player.rs new file mode 100644 index 0000000..c7eb9c6 --- /dev/null +++ b/examples/werewolf_cli/game/player.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; +use zk_mpc::werewolf::types::Role; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Player { + pub id: usize, + pub name: String, + pub role: Option, + pub is_alive: bool, + pub death_day: Option, + pub marked_for_death: bool, +} + +impl Player { + pub fn new(id: usize, name: String, role: Option) -> Self { + Self { + id, + name, + role, + is_alive: true, + death_day: None, + marked_for_death: false, + } + } + + pub fn kill(&mut self, day: u32) { + self.is_alive = false; + self.death_day = Some(day); + } + + pub fn is_werewolf(&self) -> bool { + self.role.unwrap().is_werewolf() + } + + pub fn mark_for_death(&mut self) { + self.marked_for_death = true; + } +} + +pub fn create_players(names: Vec, roles: Option>) -> Vec { + let roles = match roles { + Some(roles) => roles.into_iter().map(Some).collect(), + None => vec![None; names.len()], + }; + + names + .into_iter() + .zip(roles) + .enumerate() + .map(|(id, (name, role))| Player::new(id, name, role)) + .collect() +} diff --git a/examples/werewolf_cli/game/role.rs b/examples/werewolf_cli/game/role.rs new file mode 100644 index 0000000..9157aea --- /dev/null +++ b/examples/werewolf_cli/game/role.rs @@ -0,0 +1,58 @@ +use ark_bls12_377::Fr; +use ark_std::test_rng; +use rand::thread_rng; +use rand::{rngs::StdRng, seq::SliceRandom}; +use zk_mpc::werewolf::types::Role; +use zk_mpc::werewolf::utils::{calc_shuffle_matrix, generate_individual_shuffle_matrix}; + +use super::GameRules; + +pub fn assign_roles(player_count: usize, rules: &GameRules) -> Vec { + let mut roles = vec![Role::Villager; player_count]; + let mut rng = thread_rng(); + + let werewolf_count = (player_count as f32 * rules.werewolf_ratio).round() as usize; + let seer_count = rules.seer_count; + + // 役割を割り当てる + for role in roles.iter_mut().take(werewolf_count) { + *role = Role::Werewolf; + } + + for role in roles.iter_mut().skip(werewolf_count).take(seer_count) { + *role = Role::FortuneTeller; + } + + // ランダムに並び替える + roles.shuffle(&mut rng); + + roles +} + +pub(super) fn calc_role(player_count: usize, rules: &GameRules) -> Vec { + let rng = &mut test_rng(); + + let grouping_parameter = &rules.grouping_parameter; + + // 1. generate shuffle matrix for each player. + let shuffle_matrix = vec![ + generate_individual_shuffle_matrix::( + // grouping_parameter.get_num_players(), + player_count, + grouping_parameter.get_num_groups(), + rng, + ); + 2 + ]; + + // 2. calc role for each player. + let mut outputs = vec![]; + + for id in 0..player_count { + let (role, _role_val, _player_ids) = + calc_shuffle_matrix(grouping_parameter, &shuffle_matrix, id).unwrap(); + outputs.push(role); + } + + outputs +} diff --git a/examples/werewolf_cli/main.rs b/examples/werewolf_cli/main.rs new file mode 100644 index 0000000..f21889e --- /dev/null +++ b/examples/werewolf_cli/main.rs @@ -0,0 +1,276 @@ +use game::{player::Player, Game, GameRules}; +use mpc_algebra::channel::MpcSerNet; +use mpc_net::{MpcMultiNet as Net, MpcNet}; +use std::io::{self, Write}; +use std::path::PathBuf; +use structopt::StructOpt; +use zk_mpc::werewolf::types::{GroupingParameter, Role}; + +pub mod game; + +#[derive(Debug, StructOpt)] +struct Opt { + // Player Id + id: Option, + + // Input address file + #[structopt(parse(from_os_str))] + input: Option, +} + +fn main() -> Result<(), Box> { + // let stream = TcpStream::connect("127.0.0.1:8080").await?; + // println!("サーバーに接続しました。"); + + let opt = Opt::from_args(); + + // init + Net::init_from_file( + opt.input.clone().unwrap().to_str().unwrap(), + opt.id.unwrap(), + ); + + let game_rule = GameRules { + min_players: 4, + max_players: 10, + werewolf_ratio: 0.3, + seer_count: 1, + grouping_parameter: GroupingParameter::new( + vec![ + (Role::Villager, (2, false)), + (Role::Werewolf, (1, false)), + (Role::FortuneTeller, (1, false)), + ] + .into_iter() + .collect(), + ), + }; + + let mut game = Game::new(register_players(), game_rule); + + game.role_assignment(false); + + println!("{:?}", game.state.players); + + loop { + night_phase(&mut game); + morning_phase(&mut game); + discussion_phase(&game); + voting_phase(&mut game); + + if let Some(winner) = game.check_victory_condition() { + println!("ゲーム終了!{}の勝利です!", winner); + break; + } + + game.next_phase(); + } + + Ok(()) +} + +fn register_players() -> Vec { + let mut name; + + loop { + print!("Please enter your player name (Press Enter to finish): "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + name = input.trim().to_string(); + + if name.is_empty() { + println!("Player name is empty."); + continue; + } else { + break; + } + } + + println!( + "Registered player name \"{}\". Waiting for other players to register.", + name + ); + + Net::broadcast(&name) +} + +fn night_phase(game: &mut Game) { + println!("\n--- 夜のフェーズ ---"); + let players = game.state.players.clone(); + for player in &players { + if player.is_alive { + let mut events = Vec::new(); + match player.role { + Some(Role::Werewolf) => { + let werewolf_target = get_werewolf_target(game, player); + events.extend(game.werewolf_attack(werewolf_target)); + } + Some(Role::FortuneTeller) => { + let seer_target = get_seer_target(game, player); + events.extend(game.seer_divination(seer_target)); + } + Some(Role::Villager) => { + println!( + "{}さん、あなたは村人です。次の人に渡してください。", + player.name + ); + } + None => unreachable!(), + } + for event in events { + println!("{}", event); + } + wait_for_enter(); + // 各プレイヤーのフェーズ後にCLIをフラッシュ + clear_screen(); + } + } +} + +fn wait_for_enter() { + println!("Enterキーを押して次に進んでください。"); + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); +} + +fn clear_screen() { + print!("\x1B[2J\x1B[1;1H"); // ANSI escape code for clearing the screen + io::stdout().flush().unwrap(); +} + +fn get_werewolf_target(game: &Game, werewolf: &Player) -> usize { + println!( + "{}さん、あなたは人狼です。襲撃する対象を選んでください:", + werewolf.name + ); + game.state + .players + .iter() + .filter(|p| p.is_alive && !p.is_werewolf()) + .for_each(|p| println!("{}: {}", p.id, p.name)); + + loop { + print!("対象のIDを入力してください: "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + let target_id: usize = input.trim().parse().unwrap_or(0); + + if game + .state + .players + .iter() + .any(|p| p.id == target_id && p.is_alive && !p.is_werewolf()) + { + return target_id; + } else { + println!("無効な選択です。もう一度選んでください。"); + } + } +} + +fn get_seer_target(game: &Game, seer: &Player) -> usize { + println!( + "{}さん、あなたは占い師です。占う対象を選んでください:", + seer.name + ); + game.state + .players + .iter() + .filter(|p| p.is_alive && p.id != seer.id) + .for_each(|p| println!("{}: {}", p.id, p.name)); + + loop { + print!("対象のIDを入力してください: "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + let target_id: usize = input.trim().parse().unwrap_or(0); + + if game + .state + .players + .iter() + .any(|p| p.id == target_id && p.is_alive && p.id != seer.id) + { + return target_id; + } else { + println!("無効な選択です。もう一度選んでください。"); + } + } +} + +fn morning_phase(game: &mut Game) { + let events = game.morning_phase(); + for event in events { + println!("{}", event); + } +} + +fn discussion_phase(game: &Game) { + println!("\n--- 討論フェーズ ---"); + let events = game.discussion_phase(); + for event in events { + println!("{}", event); + } + + println!("生存しているプレイヤー:"); + game.state + .players + .iter() + .filter(|p| p.is_alive) + .for_each(|p| println!("{}: {}", p.id, p.name)); + + println!("討論を行ってください。準備ができたらEnterキーを押してください。"); + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); +} + +fn voting_phase(game: &mut Game) { + println!("\n--- 投票フェーズ ---"); + let mut votes = Vec::new(); + + for player in &game.state.players { + if player.is_alive { + println!("{}さん、投票する対象を選んでください:", 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を入力してください: "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + let target_id: usize = input.trim().parse().unwrap_or(usize::MAX); + + if game + .state + .players + .iter() + .any(|p| p.id == target_id && p.is_alive && p.id != player.id) + { + votes.push(target_id); + break; + } else { + println!("無効な選択です。もう一度選んでください。"); + } + } + } else { + votes.push(usize::MAX); // 死亡したプレイヤーの投票は無効 + } + clear_screen(); + } + + let events = game.voting_phase(votes); + for event in events { + println!("{}", event); + } +} diff --git a/src/werewolf/types.rs b/src/werewolf/types.rs index 0c96583..281443a 100644 --- a/src/werewolf/types.rs +++ b/src/werewolf/types.rs @@ -1,15 +1,29 @@ use ark_ff::PrimeField; use nalgebra; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -#[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub enum Role { FortuneTeller, Werewolf, Villager, } +impl Role { + pub fn description(&self) -> &'static str { + match self { + Role::Villager => "村人:特別な能力はありませんが、議論と投票に参加します。", + Role::Werewolf => "人狼:夜に村人を襲撃します。昼は村人のふりをします。", + Role::FortuneTeller => "占い師:夜に一人のプレイヤーの役割を知ることができます。", + } + } + + pub fn is_werewolf(&self) -> bool { + matches!(self, Role::Werewolf) + } +} + #[derive(Debug, Deserialize)] pub struct RoleData { role: String,