Skip to content

Commit

Permalink
Merge pull request #67 from Yoii-Inc/feat/werewolf_cli
Browse files Browse the repository at this point in the history
Feat/werewolf cli
  • Loading branch information
taskooh authored Nov 11, 2024
2 parents 8de42c8 + 131845e commit e82fd0f
Show file tree
Hide file tree
Showing 6 changed files with 782 additions and 2 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
376 changes: 376 additions & 0 deletions examples/werewolf_cli/game.rs
Original file line number Diff line number Diff line change
@@ -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<Player>,
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<String>, 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 = <Fr as LocalOrMPC<Fr>>::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(|_| <Fr as LocalOrMPC<Fr>>::PedersenRandomness::rand(rng))
.collect::<Vec<_>>();

let commitment = (0..n)
.map(|i| {
<Fr as LocalOrMPC<Fr>>::PedersenComScheme::commit(
&pedersen_param,
&inputs[i].into_repr().to_bytes_le(),
&randomness[i],
)
.unwrap()
})
.collect::<Vec<_>>();

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::<Fr>::zeros(n + m, n + m),
shuffle_matrices: vec![DMatrix::<Fr>::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 =
<MFr as LocalOrMPC<MFr>>::PedersenParam::from_local(&pedersen_param);

let mpc_randomness = (0..n)
.map(|_| <MFr as LocalOrMPC<MFr>>::PedersenRandomness::rand(rng))
.collect::<Vec<_>>();

let converted_inputs = inputs
.iter()
.map(|x| <MFr as LocalOrMPC<MFr>>::convert_input(&MFr::from_public(*x)))
.collect::<Vec<_>>();

let role_commitment = (0..n)
.map(|i| {
<MFr as LocalOrMPC<MFr>>::PedersenComScheme::commit(
&mpc_pedersen_param,
&converted_inputs[i],
&mpc_randomness[i],
)
.unwrap()
})
.collect::<Vec<_>>();

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::<Vec<_>>(),
player_commitment: player_commitment
.iter()
.map(|x| <MFr as LocalOrMPC<MFr>>::PedersenCommitment::from_public(*x))
.collect::<Vec<_>>(),
};

let mut inputs = player_commitment
.iter()
.flat_map(|c| vec![c.x, c.y])
.collect::<Vec<_>>();

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<String> {
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<String> {
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<String> {
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<String> {
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<String> {
vec!["討論フェーズが始まりました。".to_string()]
}

pub fn voting_phase(&mut self, votes: Vec<usize>) -> Vec<String> {
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::<Vec<_>>();
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,
}
}
}
Loading

0 comments on commit e82fd0f

Please sign in to comment.