/*
* This file is part of laurelin_client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
use std::collections::HashMap;
use fastrand::Rng;
use crate::{api::game::{Action, Command, Game}, game_status::SupplyPile};
use super::{GameStatus, PlayerState, PlayerStatus, Card};
/// funny unsafe wrapper
fn get_invoker_target<'a, K, V>(
players: &'a mut HashMap<K, V>,
invoker: &K,
target: &K
) -> (&'a mut V, &'a mut V)
where
K: Eq + std::hash::Hash,
{
unsafe {
// NOTE: soo... I don't really know the consequences of possibly
// having two mutable references to the same value, but I guess
// I'll find out!
// in many instances where people wanted multiple mutable references
// to Vec or HashMap values, they only gave one in wrappers like this,
// if the wanted values were the same.
// e.g. returning (V, None), if the keys were the same.
let invoker_ref = players.get_mut(invoker).unwrap() as *mut _;
let target_ref = players.get_mut(target).unwrap() as *mut _;
(&mut *invoker_ref, &mut *target_ref)
}
}
pub fn parse(game: &Game) -> Result<GameStatus, ()> {
let mut game_status = GameStatus {
actions: game.actions.as_ref().unwrap().to_vec(),
supply_piles: vec![],
players: HashMap::new()
};
game_status.players.insert(game.host_id.clone(), PlayerStatus {
state: PlayerState::Idle,
currency: 0,
vp: 2,
hand: vec![],
deck: vec![],
discard: vec![]
});
game_status.players.insert(game.guest_id.clone(), PlayerStatus {
state: PlayerState::Idle,
currency: 0,
vp: 2,
hand: vec![],
deck: vec![],
discard: vec![]
});
// TODO: a system for reparsing if needed, e.g. after something
// modifies the actions Vector.
for action in &game_status.actions {
// invoker: the one who invoked the action
// target: the one who the action affects, may also be the invoker, e.g. draw
let (invoker, target) = get_invoker_target(&mut game_status.players, &action.invoker, &action.target);
match &action.command {
Command::InitSupplyPile { card, amount } => {
let pile = SupplyPile {
card: card.clone(),
amount: *amount,
};
game_status.supply_piles.push(pile);
}
Command::TakeFromPile { index, for_cost } => {
// index should be within range
assert!(*index <= game_status.supply_piles.len());
let pile = &mut game_status.supply_piles.get_mut(*index)
.unwrap_or_else(|| unreachable!());
// pile should not be empty
assert!(pile.amount > 0);
pile.amount = pile.amount - 1;
// player should have enough
assert!(*for_cost <= target.currency);
target.currency = target.currency - for_cost;
target.discard.push(pile.card.clone());
}
Command::Draw { amount } => {
for _ in 0..*amount {
if target.deck.is_empty() {
shuffle_discard_to_deck(target, action.seed.parse::<u64>().unwrap());
}
target.hand.push(target.deck.pop()
.unwrap_or_else(|| unreachable!()));
}
}
Command::Discard { index } => {
// index should be within range
assert!(*index <= target.hand.len());
target.discard.push(target.hand.remove(*index));
}
Command::EndTurn { } => {
// NOTE: target will be the next player
// set player to idle
invoker.state = PlayerState::Idle;
// set the target to action phase
target.state = PlayerState::ActionPhase;
// clear currency
invoker.currency = 0;
// other?
}
Command::ChangePlayerState { state } => {
target.state = *state;
}
_ => todo!(),
}
}
Ok(game_status)
}
fn shuffle_discard_to_deck(target: &mut PlayerStatus, seed: u64) {
let cards = target.discard.to_vec();
target.discard.clear();
target.deck = cards;
let rng = Rng::with_seed(seed);
rng.shuffle(&mut target.deck);
}