codegame 0.6.0

CodeGame framework
Documentation
use super::*;

mod background;

pub use background::*;

enum State<G: Game> {
    Game {
        game: G,
        rng: Box<dyn RngCore + Send>,
    },
    Repeater {
        game: G,
        reader: Box<dyn std::io::BufRead + Send>,
        finished: bool,
    },
}

impl<G: Game> State<G> {
    fn process_turn(&mut self, actions: HashMap<usize, G::Action>) -> Vec<G::Event> {
        match self {
            Self::Game { game, rng } => game.process_turn(rng, actions),
            Self::Repeater {
                game,
                reader,
                finished,
            } => {
                assert!(!*finished);
                let events =
                    Vec::<G::Event>::read_from(&mut *reader).expect("Failed to read game log");
                let delta = G::Delta::read_from(&mut *reader).expect("Failed to read game log");
                game.update(&delta);
                self.update_finished();
                events
            }
        }
    }
    fn game(&self) -> &G {
        match self {
            Self::Game { game, .. } | Self::Repeater { game, .. } => game,
        }
    }
    fn update_finished(&mut self) {
        match self {
            Self::Game { .. } => {}
            Self::Repeater {
                reader, finished, ..
            } => {
                if reader
                    .fill_buf()
                    .expect("Failed to read game log")
                    .is_empty()
                {
                    *finished = true;
                }
            }
        }
    }
    fn finished(&self) -> bool {
        match self {
            Self::Game { game, .. } => game.finished(),
            Self::Repeater { finished, .. } => *finished,
        }
    }
}

pub struct GameProcessor<G: Game> {
    seed: Option<u64>,
    state: State<G>,
    players: Vec<Option<Box<dyn Player<G>>>>,
    player_comments: Vec<Option<String>>,
    ticks_processed: usize,
    tick_handler: Option<Box<dyn FnMut(Option<&Vec<G::Event>>, &G) + Send>>,
    results_handler: Option<Box<dyn FnOnce(FullResults<G>) + Send>>,
}

impl<G: Game + 'static> GameProcessor<G> {
    pub fn new_full(full_options: FullOptions<G>, player_extra_data: &G::PlayerExtraData) -> Self {
        Self::new(
            full_options.seed,
            full_options.options_preset.into(),
            futures::executor::block_on(futures::future::join_all(
                full_options
                    .players
                    .iter()
                    .map(|options| options.get(player_extra_data)),
            ))
            .into_iter()
            .map(|result| match result {
                Ok(player) => player,
                Err(e) => Box::new(ErroredPlayer(e.to_string())),
            })
            .collect(),
        )
    }
    pub fn new(seed: Option<u64>, options: G::Options, players: Vec<Box<dyn Player<G>>>) -> Self {
        let seed = seed.unwrap_or_else(|| global_rng().gen());
        let mut rng = Box::new(<rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(
            seed,
        ));
        let game = G::init(&mut rng, players.len(), options);
        let player_comments = vec![None; players.len()];
        Self {
            seed: Some(seed),
            state: State::Game { game, rng },
            players: players.into_iter().map(|player| Some(player)).collect(),
            player_comments,
            ticks_processed: 0,
            tick_handler: None,
            results_handler: None,
        }
    }
    pub fn repeat_full(
        full_options: FullOptions<G>,
        player_extra_data: &G::PlayerExtraData,
        reader: impl std::io::Read + Send + 'static,
    ) -> Self {
        Self::repeat(
            reader,
            futures::executor::block_on(futures::future::join_all(
                full_options
                    .players
                    .iter()
                    .map(|options| options.get(player_extra_data)),
            ))
            .into_iter()
            .map(|result| match result {
                Ok(player) => player,
                Err(e) => Box::new(ErroredPlayer(e.to_string())),
            })
            .collect(),
        )
    }
    pub fn repeat(
        reader: impl std::io::Read + Send + 'static,
        players: Vec<Box<dyn Player<G>>>,
    ) -> Self {
        let mut reader = std::io::BufReader::new(reader);
        let game = G::read_from(&mut reader).expect("Failed to read game log");
        let mut state = State::Repeater {
            game,
            reader: Box::new(reader),
            finished: false,
        };
        state.update_finished();
        let player_comments = vec![None; players.len()];
        Self {
            seed: None,
            state,
            players: players.into_iter().map(|player| Some(player)).collect(),
            player_comments,
            ticks_processed: 0,
            tick_handler: None,
            results_handler: None,
        }
    }

    pub fn set_tick_handler(
        &mut self,
        mut handler: Box<dyn FnMut(Option<&Vec<G::Event>>, &G) + Send>,
    ) {
        handler(None, self.state.game());
        self.tick_handler = Some(handler);
    }
    pub fn set_results_handler(&mut self, handler: Box<dyn FnOnce(FullResults<G>) + Send>) {
        self.results_handler = Some(handler);
    }

    pub(crate) fn process_tick(
        &mut self,
        custom_data_handler: Option<&dyn Fn(usize, G::CustomData)>,
    ) -> Vec<G::Event> {
        assert!(!self.finished());
        let views: Vec<_> = (0..self.players.len())
            .map(|index| self.state.game().player_view(index))
            .collect();
        let action_results = self
            .players
            .iter_mut()
            .zip(views.into_iter())
            .enumerate()
            .filter_map(|(index, (player, view))| {
                player.as_mut().map(|player| {
                    (
                        index,
                        player.get_action(
                            &view,
                            custom_data_handler
                                .map(move |f| (move |custom_data| f(index, custom_data)))
                                .as_ref()
                                .map(|f| f as _),
                        ),
                    )
                })
            });
        let player_comments = &mut self.player_comments;
        let actions: HashMap<usize, G::Action> = action_results
            .filter_map(|(index, result)| {
                if let Err(e) = &result {
                    warn!("Player error: {}", e);
                    player_comments[index] = Some(format!("Player crashed: {}", e));
                }
                result.ok().map(|action| (index, action))
            })
            .collect();
        for (index, player) in self.players.iter_mut().enumerate() {
            if !actions.contains_key(&index) {
                *player = None;
            }
        }
        let events = self.state.process_turn(actions);
        if let Some(handler) = &mut self.tick_handler {
            handler(Some(&events), self.state.game());
        }
        if self.finished() {
            let results = self.state.game().results();
            if let Some(handler) = self.results_handler.take() {
                handler(FullResults {
                    players: self
                        .players
                        .iter()
                        .zip(self.player_comments.iter())
                        .map(|(player, comment)| PlayerResult {
                            crashed: player.is_none(),
                            comment: comment.clone(),
                        })
                        .collect(),
                    results,
                    seed: self.seed,
                });
            }
        }
        self.ticks_processed += 1;
        trace!("Processed {} ticks", self.ticks_processed);
        events
    }

    pub fn run(mut self) {
        while !self.finished() {
            self.process_tick(None);
        }
    }

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

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