DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

770581bb212762e0a8ab491a5bb684c25bdb5821 — Jonni Liljamo 1 year, 7 months ago 83e9f07
feat(client): ability to insert new actions...

... while parsing existing actions.
3 files changed, 94 insertions(+), 73 deletions(-)

M client/src/api/game/mod.rs
M client/src/game_status/mod.rs
M client/src/game_status/parser.rs
M client/src/api/game/mod.rs => client/src/api/game/mod.rs +5 -3
@@ 31,7 31,7 @@ pub use create_action::*;
mod details;
pub use details::*;

#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Clone, PartialEq)]
pub enum Command {
    InitSupplyPile {
        card: Card,


@@ 50,15 50,17 @@ pub enum Command {
    Discard {
        index: usize,
    },
    /// end the targets turn
    /// end the invokers turn, and invoke StartTurn for the target
    EndTurn {},
    /// start the targets turn
    StartTurn {},
    /// change player state to another
    ChangePlayerState {
        state: PlayerState,
    },
}

#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Clone, PartialEq)]
pub struct Action {
    pub id: String,
    pub created_at: chrono::DateTime<chrono::Utc>,

M client/src/game_status/mod.rs => client/src/game_status/mod.rs +2 -2
@@ 14,14 14,14 @@ use crate::api::game::{Action, Command, Game};

mod parser;

#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Clone, PartialEq)]
pub struct CardAction {
    pub target: String,
    pub command: Command,
    pub seed: u64,
}

#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Clone, PartialEq)]
pub struct Card {
    pub name: String,
    /// short details shown on the card, e.g. Draw 2

M client/src/game_status/parser.rs => client/src/game_status/parser.rs +87 -68
@@ 11,8 11,9 @@ use std::collections::HashMap;
use fastrand::Rng;

use crate::{
    api::game::{Command, Game},
    api::game::{Action, Command, Game},
    game_status::SupplyPile,
    seed_gen,
};

use super::{GameStatus, PlayerState, PlayerStatus};


@@ 80,80 81,98 @@ pub fn parse(game: &Game) -> Result<GameStatus, ()> {
        },
    );

    // 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::<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
    for action in game_status.actions.clone() {
        parse_action(&action, game, &mut game_status);
    }

                // set player to idle
                invoker.state = PlayerState::Idle;
    Ok(game_status)
}

                // set the target to the play phase
                target.state = PlayerState::PlayPhase;
fn parse_action(action: &Action, game: &Game, game_status: &mut GameStatus) {
    // 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);

                // clear currency
                invoker.currency = 0;
    let Some(action_pos) = game_status.actions.iter().position(|a| *a == action.clone()) else {
        panic!("Action was not found in game_status.actions!");
    };

                // other?
            }
            Command::ChangePlayerState { state } => {
                target.state = *state;
    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::<u64>().unwrap());
                }

                target
                    .hand
                    .push(target.deck.pop().unwrap_or_else(|| unreachable!()));
            }
            #[allow(unreachable_patterns)]
            _ => todo!(),
        }
        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;

            // clear currency
            invoker.currency = 0;

            let start_turn_action = Action::new(
                &game.id,
                &action.invoker,
                &action.target,
                &Command::StartTurn {},
                seed_gen!(),
            );
            game_status
                .actions
                .insert(action_pos + 1, start_turn_action.clone());

            parse_action(&start_turn_action, game, game_status);
        }
        Command::StartTurn {} => {
            // set the target to the play phase
            target.state = PlayerState::PlayPhase;
        }
        Command::ChangePlayerState { state } => {
            target.state = *state;
        }
        #[allow(unreachable_patterns)]
        _ => todo!(),
    }

    Ok(game_status)
}

fn shuffle_discard_to_deck(target: &mut PlayerStatus, seed: u64) {