aboutsummaryrefslogtreecommitdiff
path: root/planetwars-matchrunner/src/lib.rs
blob: fcd4799571f97f8c2840fa48159a1880ee6221b5 (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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
pub mod bot_runner;
pub mod docker_runner;
pub mod match_context;
pub mod match_log;
pub mod pw_match;

use std::{
    path::PathBuf,
    sync::{Arc, Mutex},
};

use async_trait::async_trait;
use futures::{stream::FuturesOrdered, StreamExt};
use match_context::MatchCtx;
use match_log::{create_log_sink, MatchLogger};
use planetwars_rules::PwConfig;
use serde::{Deserialize, Serialize};

use self::match_context::{EventBus, PlayerHandle};

pub struct MatchConfig {
    pub map_name: String,
    pub map_path: PathBuf,
    pub log_path: PathBuf,
    pub players: Vec<MatchPlayer>,
}

#[derive(Serialize, Deserialize)]
pub struct MatchMeta {
    pub map_name: String,
    pub timestamp: chrono::DateTime<chrono::Local>,
    pub players: Vec<PlayerInfo>,
}

#[derive(Serialize, Deserialize)]
pub struct PlayerInfo {
    pub name: String,
}

pub struct MatchPlayer {
    pub bot_spec: Box<dyn BotSpec>,
}

#[async_trait]
pub trait BotSpec: Send + Sync {
    async fn run_bot(
        &self,
        player_id: u32,
        event_bus: Arc<Mutex<EventBus>>,
        match_logger: MatchLogger,
    ) -> Box<dyn PlayerHandle>;
}

pub struct MatchOutcome {
    pub winner: Option<usize>,
}

pub async fn run_match(config: MatchConfig) -> MatchOutcome {
    let pw_config = PwConfig {
        map_file: config.map_path,
        max_turns: 500,
    };

    let event_bus = Arc::new(Mutex::new(EventBus::new()));
    let match_logger = create_log_sink(&config.log_path).await;

    // start bots
    // TODO: what happens when a bot fails?
    let players = config
        .players
        .iter()
        .enumerate()
        .map(|(player_id, player)| {
            let player_id = (player_id + 1) as u32;
            start_bot(
                player_id,
                event_bus.clone(),
                player.bot_spec.as_ref(),
                match_logger.clone(),
            )
        })
        .collect::<FuturesOrdered<_>>()
        // await all results
        .collect()
        .await;

    let match_ctx = MatchCtx::new(event_bus, players, match_logger);

    // TODO: is this still needed?
    // assemble the math meta struct
    // let match_meta = MatchMeta {
    //     map_name: config.map_name.clone(),
    //     timestamp: chrono::Local::now(),
    //     players: config
    //         .players
    //         .iter()
    //         .map(|bot| PlayerInfo {
    //             name: bot.name.clone(),
    //         })
    //         .collect(),
    // };
    // write!(
    //     log_file,
    //     "{}\n",
    //     serde_json::to_string(&match_meta).unwrap()
    // )
    // .unwrap();

    let mut match_state = pw_match::PwMatch::create(match_ctx, pw_config);
    match_state.run().await;

    let final_state = match_state.match_state.state();
    let survivors = final_state.living_players();
    let winner = if survivors.len() == 1 {
        Some(survivors[0])
    } else {
        None
    };

    MatchOutcome { winner }
}

// writing this as a closure causes lifetime inference errors
async fn start_bot(
    player_id: u32,
    event_bus: Arc<Mutex<EventBus>>,
    bot_spec: &dyn BotSpec,
    match_logger: MatchLogger,
) -> (u32, Box<dyn PlayerHandle>) {
    let player_handle = bot_spec.run_bot(player_id, event_bus, match_logger).await;
    (player_id, player_handle)
}