aboutsummaryrefslogtreecommitdiff
path: root/planetwars-rules/src/lib.rs
blob: 021711c73641ce1025dde47e8e55e1aba864526b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#[macro_use]
extern crate serde;
extern crate serde_json;

pub mod config;
pub mod protocol;
pub mod rules;
pub mod serializer;

pub use config::Config as PwConfig;
pub use protocol::CommandError;
pub use rules::{Dispatch, PwState};
use std::collections::HashMap;

pub struct PlanetWars {
    /// Game state
    state: rules::PwState,
    /// Map planet names to their ids
    planet_map: HashMap<String, usize>,
}

impl PlanetWars {
    pub fn create(config: PwConfig, num_players: usize) -> Self {
        let state = config.create_state(num_players);

        let planet_map = state
            .planets
            .iter()
            .map(|p| (p.name.clone(), p.id))
            .collect();

        PlanetWars { state, planet_map }
    }

    /// Proceed to next turn
    pub fn step(&mut self) {
        self.state.repopulate();
        self.state.step();
    }

    pub fn is_finished(&self) -> bool {
        self.state.is_finished()
    }

    pub fn serialize_state(&self) -> protocol::State {
        serializer::serialize(&self.state)
    }

    pub fn serialize_player_state(&self, player_id: usize) -> protocol::State {
        serializer::serialize_rotated(&self.state, player_id - 1)
    }

    pub fn state(&self) -> &PwState {
        &self.state
    }

    /// Execute a command
    pub fn execute_command(
        &mut self,
        player_num: usize,
        cmd: &protocol::Command,
    ) -> Result<(), CommandError> {
        let dispatch = self.parse_command(player_num, cmd)?;
        self.state.dispatch(&dispatch);
        Ok(())
    }

    /// Check the given command for validity.
    /// If it is valid, return an internal representation of the dispatch
    /// described by the command.
    pub fn parse_command(
        &self,
        player_id: usize,
        cmd: &protocol::Command,
    ) -> Result<Dispatch, CommandError> {
        let origin_id = *self
            .planet_map
            .get(&cmd.origin)
            .ok_or(CommandError::OriginDoesNotExist)?;

        let target_id = *self
            .planet_map
            .get(&cmd.destination)
            .ok_or(CommandError::DestinationDoesNotExist)?;

        if self.state.planets[origin_id].owner() != Some(player_id - 1) {
            return Err(CommandError::OriginNotOwned);
        }

        if self.state.planets[origin_id].ship_count() < cmd.ship_count {
            return Err(CommandError::NotEnoughShips);
        }

        if cmd.ship_count == 0 {
            return Err(CommandError::ZeroShipMove);
        }

        Ok(Dispatch {
            origin: origin_id,
            target: target_id,
            ship_count: cmd.ship_count,
        })
    }

    /// Execute a dispatch.
    /// This assumes the dispatch is valid. You should check this yourself
    /// or use `parse_command` to obtain a valid dispatch.
    pub fn execute_dispatch(&mut self, dispatch: &Dispatch) {
        self.state.dispatch(dispatch);
    }
}