/* * This file is part of laurelin_client * Copyright (C) 2023 Jonni Liljamo * * Licensed under GPL-3.0-only. * See LICENSE for licensing information. */ use std::collections::HashMap; use fastrand::Rng; use crate::{ api::game::{Command, Game}, game_status::SupplyPile, }; use super::{GameStatus, PlayerState, PlayerStatus}; /// funny unsafe wrapper fn get_invoker_target<'a, K, V>( players: &'a mut HashMap, 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 { 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 { display_name: game.host.as_ref().unwrap().username.clone(), state: PlayerState::Idle, plays: 0, buys: 0, currency: 0, vp: 2, hand: vec![], deck: vec![], discard: vec![], }, ); game_status.players.insert( game.guest_id.clone(), PlayerStatus { display_name: game.guest.as_ref().unwrap().username.clone(), state: PlayerState::Idle, plays: 0, buys: 0, 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 -= 1; // player should have enough assert!(*for_cost <= 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::().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 the play phase target.state = PlayerState::PlayPhase; // 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); }