codegame 0.6.0

CodeGame framework
Documentation
use super::*;

mod player;

pub use player::*;

struct RendererWrapper<R>(Rc<RefCell<R>>);

impl<R> Clone for RendererWrapper<R> {
    fn clone(&self) -> Self {
        Self(self.0.clone())
    }
}

impl<G: Game, R: Renderer<G>> Renderer<G> for RendererWrapper<R> {
    type ExtraData = R::ExtraData;
    type Preferences = R::Preferences;
    fn default_tps(&self) -> f64 {
        self.0.borrow().default_tps()
    }
    fn update(&mut self, delta_time: f64) {
        self.0.borrow_mut().update(delta_time);
    }
    fn draw(
        &mut self,
        game: &G,
        last_events: &[G::Event],
        extra_data: &Self::ExtraData,
        custom_data: &HashMap<usize, Vec<G::CustomData>>,
        framebuffer: &mut ugli::Framebuffer,
    ) {
        self.0
            .borrow_mut()
            .draw(game, last_events, extra_data, custom_data, framebuffer);
    }
    fn process_event(&mut self, event: &G::Event) {
        self.0.borrow_mut().process_event(event);
    }
    fn handle_event(&mut self, event: &geng::Event) {
        self.0.borrow_mut().handle_event(event);
    }
}

pub trait DeepConfig<T>: ui::Config<T> {
    fn transition(&mut self) -> Option<Box<dyn geng::State>>;
}

struct Data<G: Game, R: Renderer<G>> {
    geng: Rc<Geng>,
    preferences: Rc<RefCell<AutoSave<AppPreferences<R::Preferences>>>>,
    theme: Rc<ui::Theme>,
    game_options_config: Box<dyn DeepConfig<G::OptionsPreset>>,
    save_button: ui::TextButton,
    replay_button: ui::TextButton,
    start_button: ui::TextButton,
    repeat_button: ui::TextButton,
    player_configs: Vec<PlayerConfigWidget<G>>,
    renderer: RendererWrapper<R>,
    replay_requested: bool,
    repeat_requested: bool,
    ready: bool,
}

impl<G: Game, R: Renderer<G>> Data<G, R> {
    pub fn ui<'a>(&'a mut self) -> impl ui::Widget + 'a {
        use ui::*;
        let options = self.full_options();
        let mut ready = true;
        for config in &mut self.player_configs {
            // Doing for to assert calling .ready()
            if !config.ready() {
                ready = false;
            }
        }
        let play_section = if ready {
            let play = self.start_button.ui(Box::new({
                let ready = &mut self.ready;
                move || *ready = true
            }));
            if cfg!(target_arch = "wasm32") {
                Box::new(play) as Box<dyn Widget>
            } else {
                Box::new(ui::row![
                    play.align(vec2(0.5, 0.5)),
                    text(translate("or"), &self.theme.font, 32.0, Color::GRAY)
                        .align(vec2(0.5, 0.5))
                        .padding_left(32.0)
                        .padding_right(32.0),
                    self.repeat_button
                        .ui(Box::new({
                            let repeat_requested = &mut self.repeat_requested;
                            move || *repeat_requested = true
                        }))
                        .align(vec2(0.5, 0.5)),
                ]) as Box<dyn Widget>
            }
        } else {
            Box::new(text(
                translate("Waiting for players"),
                &self.theme.font,
                32.0,
                Color::GRAY,
            )) as Box<dyn Widget>
        };
        let column = ui::column![
            row(self
                .player_configs
                .iter_mut()
                .map(|config| Box::new(config.ui()) as _)
                .collect())
            .align(vec2(0.5, 0.5)),
            text(
                translate("Game options"),
                &self.theme.font,
                32.0,
                Color::GRAY
            )
            .padding_top(32.0)
            .align(vec2(0.5, 0.5)),
            self.game_options_config.ui().align(vec2(0.5, 0.5)),
            play_section.padding_top(32.0).align(vec2(0.5, 0.5)),
        ];
        #[cfg(not(target_arch = "wasm32"))]
        let column = ui::column![
            row![
                text(
                    translate("Create a game or"),
                    &self.theme.font,
                    32.0,
                    Color::GRAY
                )
                .padding_right(32.0),
                self.replay_button.ui(Box::new({
                    let replay_requested = &mut self.replay_requested;
                    move || *replay_requested = true
                })),
            ]
            .align(vec2(0.5, 0.5))
            .padding_bottom(32.0),
            column,
            self.save_button
                .ui(Box::new(move || {
                    save_file(translate("save config"), "config.json", move |writer| {
                        options.save(writer)
                    })
                    .expect("Failed to save config");
                }))
                .padding_top(32.0)
                .align(vec2(0.5, 0.5)),
        ];
        column.align(vec2(0.5, 0.5))
    }
    fn transition(&mut self) -> Option<geng::Transition> {
        if self.ready {
            self.ready = false;
            let players: Vec<Box<dyn Player<G>>> = self
                .player_configs
                .iter_mut()
                .map(|config| config.create())
                .collect();
            return Some(geng::Transition::Push(Box::new(GameScreen::new(
                &self.geng,
                GameProcessor::new(None, self.game_options_config.get().into(), players),
                self.renderer.clone(),
                self.preferences.clone(),
            ))));
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            if self.replay_requested {
                self.replay_requested = false;
                if let Some(path) = select_file(translate("Select file to replay")) {
                    return Some(geng::Transition::Push(Box::new(GameScreen::replay(
                        &self.geng,
                        futures::executor::block_on(History::load(path.to_str().unwrap())),
                        self.renderer.clone(),
                        self.preferences.clone(),
                    ))));
                }
            }
            if self.repeat_requested {
                self.repeat_requested = false;
                if let Some(path) = select_file(translate("Select file to repeat")) {
                    let players: Vec<Box<dyn Player<G>>> = self
                        .player_configs
                        .iter_mut()
                        .map(|config| config.create())
                        .collect();
                    return Some(geng::Transition::Push(Box::new(GameScreen::new(
                        &self.geng,
                        GameProcessor::<G>::repeat(
                            std::fs::File::open(path).expect("Failed to open game log file"),
                            players,
                        ),
                        self.renderer.clone(),
                        self.preferences.clone(),
                    ))));
                }
            }
        }
        None
    }
    fn full_options(&self) -> FullOptions<G> {
        FullOptions {
            seed: None,
            options_preset: self.game_options_config.get(),
            players: self
                .player_configs
                .iter()
                .map(|config| config.to_options())
                .collect(),
        }
    }
}

pub struct ConfigScreen<G: Game, R: Renderer<G>> {
    data: Data<G, R>,
    ui_controller: ui::Controller,
}

impl<G: Game, R: Renderer<G>> ConfigScreen<G, R> {
    pub fn new(
        geng: &Rc<Geng>,
        theme: &Rc<ui::Theme>,
        game_options_config: Box<dyn DeepConfig<G::OptionsPreset>>,
        player_config_options: Vec<Box<dyn Fn() -> Box<dyn PlayerConfig<G>>>>,
        player_config_defaults: Vec<usize>,
        renderer: R,
        preferences: Rc<RefCell<AutoSave<AppPreferences<R::Preferences>>>>,
    ) -> Self {
        add_translations(include_str!("translations.txt"));
        let renderer = RendererWrapper(Rc::new(RefCell::new(renderer)));
        let player_config_options = Rc::new(player_config_options);
        let mut player_configs = Vec::with_capacity(player_config_defaults.len());
        for (index, default) in player_config_defaults.into_iter().enumerate() {
            player_configs.push(PlayerConfigWidget::new(
                geng,
                theme,
                &player_config_options,
                default,
                format!("{} {}", translate("Player"), index + 1),
            ));
        }
        Self {
            data: Data {
                geng: geng.clone(),
                theme: theme.clone(),
                replay_button: ui::TextButton::new(
                    geng,
                    theme,
                    translate("watch a replay").to_owned(),
                    32.0,
                ),
                save_button: ui::TextButton::new(
                    geng,
                    theme,
                    translate("save config").to_owned(),
                    24.0,
                ),
                start_button: ui::TextButton::new(geng, theme, translate("START").to_owned(), 32.0),
                repeat_button: ui::TextButton::new(
                    geng,
                    theme,
                    translate("repeat a game").to_owned(),
                    32.0,
                ),
                game_options_config,
                player_configs,
                renderer,
                replay_requested: false,
                repeat_requested: false,
                ready: false,
                preferences,
            },
            ui_controller: ui::Controller::new(),
        }
    }
}

impl<G: Game, R: Renderer<G>> geng::State for ConfigScreen<G, R> {
    fn update(&mut self, delta_time: f64) {
        self.ui_controller.update(self.data.ui(), delta_time);
    }
    fn handle_event(&mut self, event: geng::Event) {
        self.ui_controller.handle_event(self.data.ui(), event);
    }
    fn draw(&mut self, framebuffer: &mut ugli::Framebuffer) {
        ugli::clear(framebuffer, Some(Color::BLACK), None);
        self.ui_controller.draw(self.data.ui(), framebuffer);
    }
    fn transition(&mut self) -> Option<geng::Transition> {
        if let Some(transition) = self.data.game_options_config.transition() {
            return Some(geng::Transition::Push(transition));
        }
        self.data.transition()
    }
}