about summary refs log tree commit diff
path: root/src/game.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/game.rs')
-rw-r--r--src/game.rs617
1 files changed, 0 insertions, 617 deletions
diff --git a/src/game.rs b/src/game.rs
deleted file mode 100644
index c478e0d2f55b..000000000000
--- a/src/game.rs
+++ /dev/null
@@ -1,617 +0,0 @@
-use crate::description::list_to_sentence;
-use crate::display::{self, Viewport};
-use crate::entities::entity::Describe;
-use crate::entities::entity::Entity;
-use crate::entities::{
-    AnEntity, Character, Creature, EntityID, Identified, Item,
-};
-use crate::messages::message;
-use crate::settings::Settings;
-use crate::types::command::Command;
-use crate::types::entity_map::EntityMap;
-use crate::types::{
-    pos, BoundingBox, Collision, Dimensions, Position, Positioned, Ticks,
-};
-use crate::util::promise::Cancelled;
-use crate::util::promise::{promise, Complete, Promise, Promises};
-use crate::util::template::TemplateParams;
-use rand::rngs::SmallRng;
-use rand::SeedableRng;
-use std::io::{self, StdinLock, StdoutLock, Write};
-use termion::input::Keys;
-use termion::input::TermRead;
-use termion::raw::RawTerminal;
-
-type Stdout<'a> = RawTerminal<StdoutLock<'a>>;
-
-type Rng = SmallRng;
-
-enum PromptResolution {
-    Uncancellable(Complete<String>),
-    Cancellable(Complete<Result<String, Cancelled>>),
-}
-
-/// The mode to use when describing entities on a tile to the user
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum EntityDescriptionMode {
-    /// Describe the entities that the user is walking over.
-    ///
-    /// This means:
-    /// - Skip the character themselves
-    /// - Describe nothing if there are no items other than the character
-    Walk,
-
-    /// Describe entities that the user is actively asking about.
-    ///
-    /// This means:
-    /// - Describe the character themselves if they've asked to look at the tile
-    ///   they're standing on
-    /// - Explicitly say there's nothing there if there's nothing there.
-    Look,
-}
-
-impl PromptResolution {
-    fn is_cancellable(&self) -> bool {
-        use PromptResolution::*;
-        match self {
-            Uncancellable(_) => false,
-            Cancellable(_) => true,
-        }
-    }
-
-    fn fulfill(&mut self, val: String) {
-        use PromptResolution::*;
-        match self {
-            Cancellable(complete) => complete.ok(val),
-            Uncancellable(complete) => complete.fulfill(val),
-        }
-    }
-
-    fn cancel(&mut self) {
-        use PromptResolution::*;
-        match self {
-            Cancellable(complete) => complete.cancel(),
-            Uncancellable(_complete) => {}
-        }
-    }
-}
-
-/// The kind of input the game is waiting to receive
-enum InputState {
-    /// The initial input state of the game - we're currently waiting for direct
-    /// commands.
-    Initial,
-
-    /// A free text prompt has been shown to the user, and every character
-    /// besides "escape" is interpreted as a response to that prompt
-    Prompt {
-        complete: PromptResolution,
-        buffer: String,
-    },
-}
-
-impl InputState {
-    fn uncancellable_prompt(complete: Complete<String>) -> Self {
-        InputState::Prompt {
-            complete: PromptResolution::Uncancellable(complete),
-            buffer: String::new(),
-        }
-    }
-
-    fn cancellable_prompt(
-        complete: Complete<Result<String, Cancelled>>,
-    ) -> Self {
-        InputState::Prompt {
-            complete: PromptResolution::Cancellable(complete),
-            buffer: String::new(),
-        }
-    }
-}
-
-impl Default for InputState {
-    fn default() -> Self {
-        InputState::Initial
-    }
-}
-
-/// The full state of a running Game
-pub struct Game<'a> {
-    settings: Settings,
-
-    viewport: Viewport<Stdout<'a>>,
-
-    /// An iterator on keypresses from the user
-    keys: Keys<StdinLock<'a>>,
-
-    /// The kind of input the game is waiting to receive
-    input_state: InputState,
-
-    /// The map of all the entities in the game
-    entities: EntityMap<AnEntity>,
-
-    /// The entity ID of the player character
-    character_entity_id: EntityID,
-
-    /// The messages that have been said to the user, in forward time order
-    messages: Vec<String>,
-
-    /// The index of the currently-displayed message. Used to track the index of
-    /// the currently displayed message when handling PreviousMessage commands
-    message_idx: usize,
-
-    /// A global random number generator for the game
-    rng: Rng,
-
-    /// A list of promises that are waiting on the game and a result
-    promises: Promises<'a, Self>,
-}
-
-impl<'a> Game<'a> {
-    pub fn new(
-        settings: Settings,
-        stdout: RawTerminal<StdoutLock<'a>>,
-        stdin: StdinLock<'a>,
-        w: u16,
-        h: u16,
-    ) -> Game<'a> {
-        let rng = match settings.seed {
-            Some(seed) => SmallRng::seed_from_u64(seed),
-            None => SmallRng::from_entropy(),
-        };
-        let mut entities: EntityMap<AnEntity> = EntityMap::new();
-
-        // TODO make this dynamic
-        {
-            entities.insert(Box::new(Creature::new_from_raw(
-                "gormlak",
-                pos(10, 0),
-            )));
-
-            entities
-                .insert(Box::new(Item::new_from_raw("noodles", pos(0, 10))));
-        }
-
-        Game {
-            settings,
-            rng,
-            message_idx: 0,
-            viewport: Viewport::new(
-                BoundingBox::at_origin(Dimensions { w, h }),
-                BoundingBox::at_origin(Dimensions { w: w - 2, h: h - 2 }),
-                stdout,
-            ),
-            keys: stdin.keys(),
-            input_state: Default::default(),
-            character_entity_id: entities.insert(Box::new(Character::new())),
-            messages: Vec::new(),
-            entities,
-            promises: Promises::new(),
-        }
-    }
-
-    fn downcast_entities_at<A: Entity>(&self, pos: Position) -> Vec<&A> {
-        self.entities
-            .at(pos)
-            .iter()
-            .filter_map(|e| e.downcast_ref())
-            .collect()
-    }
-
-    /// Returns a list of all creature entities at the given position
-    fn creatures_at(&self, pos: Position) -> Vec<&Creature> {
-        self.downcast_entities_at(pos)
-    }
-
-    /// Returns a list of all item entities at the given position
-    fn items_at(&self, pos: Position) -> Vec<&Item> {
-        self.downcast_entities_at(pos)
-    }
-
-    /// Returns a collision, if any, at the given Position in the game
-    fn collision_at(&self, pos: Position) -> Option<Collision> {
-        if !pos.within(self.viewport.inner) {
-            Some(Collision::Stop)
-        } else if self.creatures_at(pos).is_empty() {
-            None
-        } else {
-            Some(Collision::Combat)
-        }
-    }
-
-    fn character(&self) -> &Character {
-        (*self.entities.get(self.character_entity_id).unwrap())
-            .downcast_ref()
-            .unwrap()
-    }
-
-    fn mut_character(&mut self) -> &mut Character {
-        (*self.entities.get_mut(self.character_entity_id).unwrap())
-            .downcast_mut()
-            .unwrap()
-    }
-
-    /// Draw all the game entities to the screen
-    fn draw_entities(&mut self) -> io::Result<()> {
-        for entity in self.entities.entities() {
-            self.viewport.draw(
-                entity,
-                &self.entities.neighbor_entities(entity.position()),
-            )?;
-        }
-        Ok(())
-    }
-
-    /// Draw all the game entities to the screen
-    fn draw_entities_at(&mut self, pos: Position) -> io::Result<()> {
-        for entity in self.entities.at(pos) {
-            self.viewport.draw(
-                entity,
-                &self.entities.neighbor_entities(entity.position()),
-            )?;
-        }
-        Ok(())
-    }
-
-    /// Draw the game entity with the given ID, if any, to the screen
-    fn draw_entity(&mut self, entity_id: EntityID) -> io::Result<bool> {
-        if let Some(entity) = self.entities.get(entity_id) {
-            self.viewport.draw(
-                entity,
-                &self.entities.neighbor_entities(entity.position()),
-            )?;
-            Ok(true)
-        } else {
-            Ok(false)
-        }
-    }
-
-    /// Describe all the entities at a given position to the user.
-    ///
-    /// If `force` is not set to `true`, will not do anything if there are no
-    /// entities
-    fn describe_entities_at(
-        &mut self,
-        pos: Position,
-        mode: EntityDescriptionMode,
-    ) -> io::Result<()> {
-        use EntityDescriptionMode::*;
-        let mut entities = self.entities.at(pos);
-        if mode == Walk {
-            entities.retain(|e| e.id() != self.character_entity_id);
-        }
-
-        if entities.is_empty() {
-            match mode {
-                Walk => return Ok(()),
-                Look => {
-                    return self.say(
-                        "global.describe_no_entities",
-                        &template_params!(),
-                    )
-                }
-            }
-        }
-
-        let descriptions = list_to_sentence(
-            &entities
-                .iter()
-                .map(|e| e.description())
-                .collect::<Vec<String>>(),
-        );
-
-        self.say(
-            "global.describe_entities",
-            &template_params!({ "descriptions" => &descriptions, }),
-        )
-    }
-
-    /// Remove the given entity from the game, drawing over it if it's visible
-    fn remove_entity(&mut self, entity_id: EntityID) -> io::Result<()> {
-        if let Some(entity) = self.entities.remove(entity_id) {
-            self.viewport.clear(entity.position())?;
-        }
-        Ok(())
-    }
-
-    /// Step the game forward the given number of ticks
-    fn tick(&mut self, _ticks: Ticks) {}
-
-    /// Get a message from the global map based on the rng in this game
-    fn message<'params>(
-        &mut self,
-        name: &'static str,
-        params: &TemplateParams<'params>,
-    ) -> String {
-        message(name, &mut self.rng, params)
-    }
-
-    /// Say a message to the user
-    fn say<'params>(
-        &mut self,
-        message_name: &'static str,
-        params: &TemplateParams<'params>,
-    ) -> io::Result<()> {
-        let message = self.message(message_name, params);
-        self.messages.push(message.to_string());
-        self.message_idx = self.messages.len() - 1;
-        self.viewport.write_message(&message)?;
-        Ok(())
-    }
-
-    /// Prompt the user for input, returning a Future for the result of the
-    /// prompt
-    fn prompt(
-        &mut self,
-        name: &'static str,
-        params: &TemplateParams<'_>,
-    ) -> io::Result<Promise<Self, String>> {
-        let (complete, promise) = promise();
-        self.input_state = InputState::uncancellable_prompt(complete);
-        let message = self.message(name, params);
-        self.viewport.write_prompt(&message)?;
-        self.promises.push(Box::new(promise.clone()));
-        Ok(promise)
-    }
-
-    fn prompt_cancellable(
-        &mut self,
-        name: &'static str,
-        params: &TemplateParams<'_>,
-    ) -> io::Result<Promise<Self, Result<String, Cancelled>>> {
-        let (complete, promise) = promise();
-        self.input_state = InputState::cancellable_prompt(complete);
-        let message = self.message(name, params);
-        self.viewport.write_prompt(&message)?;
-        self.promises.push(Box::new(promise.clone()));
-        Ok(promise)
-    }
-
-    fn previous_message(&mut self) -> io::Result<()> {
-        if self.message_idx == 0 {
-            return Ok(());
-        }
-        self.message_idx -= 1;
-        let message = &self.messages[self.message_idx];
-        self.viewport.write_message(message)?;
-        Ok(())
-    }
-
-    fn clear_message(&mut self) -> io::Result<()> {
-        debug!("{:?} {:?}", self.message_idx, self.messages);
-        if self.message_idx == self.messages.len() {
-            return Ok(());
-        }
-        self.viewport.clear_message()?;
-        self.message_idx += 1;
-        Ok(())
-    }
-
-    fn creature(&self, creature_id: EntityID) -> Option<&Creature> {
-        self.entities
-            .get(creature_id)
-            .and_then(|e| e.downcast_ref::<Creature>())
-    }
-
-    fn expect_creature(&self, creature_id: EntityID) -> &Creature {
-        self.creature(creature_id).unwrap_or_else(|| {
-            panic!("Creature ID went away: {:?}", creature_id)
-        })
-    }
-
-    fn mut_creature(&mut self, creature_id: EntityID) -> Option<&mut Creature> {
-        self.entities
-            .get_mut(creature_id)
-            .and_then(|e| e.downcast_mut::<Creature>())
-    }
-
-    fn expect_mut_creature(&mut self, creature_id: EntityID) -> &mut Creature {
-        self.mut_creature(creature_id).unwrap_or_else(|| {
-            panic!("Creature ID went away: {:?}", creature_id)
-        })
-    }
-
-    fn attack(&mut self, creature_id: EntityID) -> io::Result<()> {
-        info!("Attacking creature {:?}", creature_id);
-        let damage = self.character().damage();
-        let creature_name = self.expect_creature(creature_id).typ.name;
-        let tps = template_params!({
-            "creature" => {
-                "name" => creature_name,
-            },
-        });
-        self.say("combat.attack", &tps)?;
-
-        let creature = self.expect_mut_creature(creature_id);
-        creature.damage(damage);
-        if creature.dead() {
-            self.say("combat.killed", &tps)?;
-            info!("Killed creature {:?}", creature_id);
-            self.remove_entity(creature_id)?;
-        }
-        Ok(())
-    }
-
-    fn attack_at(&mut self, pos: Position) -> io::Result<()> {
-        let creatures = self.creatures_at(pos);
-        match creatures.len() {
-            0 => Ok(()),
-            1 => {
-                let creature = creatures.get(0).unwrap();
-                let creature_id = creature.id();
-                self.attack(creature_id)
-            }
-            _ => {
-                // TODO prompt with a menu of creatures to combat
-                unimplemented!()
-            }
-        }
-    }
-
-    fn pick_up(&mut self) -> io::Result<()> {
-        let pos = self.character().position;
-        let items = self.items_at(pos);
-        match items.len() {
-            0 => Ok(()),
-            1 => {
-                let item_id = items.get(0).unwrap().id();
-                let item: Box<Item> =
-                    self.entities.remove(item_id).unwrap().downcast().unwrap();
-                let desc = item.description();
-                self.mut_character().inventory.push(item);
-                self.say(
-                    "global.pick_up",
-                    &template_params!({
-                        "item" => { "name" => &desc, },
-                    }),
-                )
-            }
-            _ => {
-                // TODO prompt with a menu of items to pick up
-                unimplemented!()
-            }
-        }
-    }
-
-    fn flush_promises(&mut self) {
-        unsafe {
-            let game = self as *mut Self;
-            (*game).promises.give_all(&mut *game);
-        }
-    }
-
-    /// Run the game
-    pub fn run(mut self) -> io::Result<()> {
-        info!("Running game");
-        self.viewport.init()?;
-        self.draw_entities()?;
-        self.flush().unwrap();
-
-        self.prompt("character.name_prompt", &template_params!())?
-            .on_fulfill(|game, char_name| {
-                game.say(
-                    "global.welcome",
-                    &template_params!({
-                        "character" => {
-                            "name" => char_name,
-                        },
-                    }),
-                )
-                .unwrap();
-                game.flush().unwrap();
-                game.mut_character().set_name(char_name.to_string());
-            });
-
-        loop {
-            let mut old_position = None;
-            let next_key = self.keys.next().unwrap().unwrap();
-            match &mut self.input_state {
-                InputState::Initial => {
-                    use Command::*;
-                    match Command::from_key(next_key) {
-                        Some(Quit) => {
-                            info!("Quitting game due to user request");
-                            break;
-                        }
-
-                        Some(Move(direction)) => {
-                            use Collision::*;
-                            let new_pos = self.character().position + direction;
-                            match self.collision_at(new_pos) {
-                                None => {
-                                    old_position =
-                                        Some(self.character().position);
-                                    self.entities.update_position(
-                                        self.character_entity_id,
-                                        new_pos,
-                                    );
-                                }
-                                Some(Combat) => {
-                                    self.attack_at(new_pos)?;
-                                }
-                                Some(Stop) => (),
-                            }
-                        }
-
-                        Some(PreviousMessage) => self.previous_message()?,
-
-                        Some(PickUp) => self.pick_up()?,
-
-                        None => (),
-                    }
-
-                    if let Some(old_pos) = old_position {
-                        let character = self.character();
-                        let char_pos = character.position;
-                        self.viewport.game_cursor_position = char_pos;
-                        self.viewport.clear(old_pos)?;
-                        self.draw_entities_at(old_pos)?;
-                        self.draw_entity(self.character_entity_id)?;
-                        self.clear_message()?;
-                        self.describe_entities_at(
-                            char_pos,
-                            EntityDescriptionMode::Walk,
-                        )?;
-                        self.tick(
-                            self.character().speed().tiles_to_ticks(
-                                (old_pos - char_pos).as_tiles(),
-                            ),
-                        );
-                    }
-                }
-
-                InputState::Prompt { complete, buffer } => {
-                    use termion::event::Key::*;
-                    match next_key {
-                        Char('\n') => {
-                            info!("Prompt complete: \"{}\"", buffer);
-                            self.viewport.clear_prompt()?;
-                            complete.fulfill(buffer.clone());
-                            self.input_state = InputState::Initial;
-                        }
-                        Char(chr) => {
-                            buffer.push(chr);
-                            self.viewport.push_prompt_chr(chr)?;
-                        }
-                        Esc => complete.cancel(),
-                        Backspace => {
-                            buffer.pop();
-                            self.viewport.pop_prompt_chr()?;
-                        }
-                        _ => {}
-                    }
-                }
-            }
-
-            self.flush()?;
-            self.flush_promises();
-            debug!("{:?}", self.character());
-        }
-        Ok(())
-    }
-}
-
-impl<'a> Drop for Game<'a> {
-    fn drop(&mut self) {
-        display::clear(self).unwrap_or(());
-    }
-}
-
-impl<'a> Write for Game<'a> {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        self.viewport.write(buf)
-    }
-
-    fn flush(&mut self) -> io::Result<()> {
-        self.viewport.flush()
-    }
-
-    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
-        self.viewport.write_all(buf)
-    }
-}
-
-impl<'a> Positioned for Game<'a> {
-    fn position(&self) -> Position {
-        Position { x: 0, y: 0 }
-    }
-}