aboutsummaryrefslogtreecommitdiff
path: root/examples/text-adventure
diff options
context:
space:
mode:
Diffstat (limited to 'examples/text-adventure')
-rw-r--r--examples/text-adventure/data.rs25
-rw-r--r--examples/text-adventure/dsl.rs2
-rw-r--r--examples/text-adventure/logic.rs22
-rw-r--r--examples/text-adventure/main.rs16
-rw-r--r--examples/text-adventure/side_effects.rs10
5 files changed, 31 insertions, 44 deletions
diff --git a/examples/text-adventure/data.rs b/examples/text-adventure/data.rs
index 2c12bb0..3e9c49e 100644
--- a/examples/text-adventure/data.rs
+++ b/examples/text-adventure/data.rs
@@ -83,19 +83,13 @@ pub enum Item{
impl Item {
fn price(self) -> usize {
match self {
- Item::SausageRoll => 300,
- Item::Pickles => 250,
- Item::Milk => 125,
- Item::Yoghurt => 125,
+ Item::SausageRoll | Item::FishSandwich | Item::Shots => 300,
+ Item::Pickles | Item::Pulp => 250,
+ Item::Milk | Item::Yoghurt | Item::Beer => 125,
Item::Cheese => 750,
Item::CatFood => 2500,
- Item::Beer => 125,
Item::ToiletPaper => 500,
- Item::FishSandwich => 300,
Item::ChewingGum => 100,
- Item::Shots => 300,
- Item::Pulp => 250,
-
}
}
pub fn description(self) -> &'static str {
@@ -112,7 +106,6 @@ impl Item {
Item::ChewingGum => "A pack of chewing gum, costing €1.00",
Item::Shots => "A shot of a sad excuse for whisky, costing €3.00",
Item::Pulp => "A pulp novel called \"Aliens ate my trashbin\", which should not cost the €2.50 it does",
-
}
}
}
@@ -131,17 +124,11 @@ impl Location{
//In a real project I would probably aim to make this Copy as well, especially if it's as small as this.
//I left it as Clone intentionally, to illustrate how one can work around the limitation of it not being Copy.
-#[derive(Clone)]
+#[derive(Clone, Default)]
pub struct Inventory {
pub items : Vec<Item>,
}
-impl Default for Inventory{
- fn default() -> Self {
- Self { items: Default::default() }
- }
-}
-
impl Inventory{
pub fn has_item_from_room(&self, room : Location) -> bool {
let items_from_room = room.items();
@@ -151,7 +138,7 @@ impl Inventory{
if self.items.len() < 3 {
let mut items = self.items;
items.push(item); //am I the only one that hates that push doesn't return the updated vec?
- Ok(Inventory{items, ..self})
+ Ok(Inventory{items})
} else {
Err(self)
}
@@ -170,7 +157,7 @@ impl Inventory{
"€10.00" //It doesn't change. Can as well be a constant.
}
pub fn total_price(&self) -> usize {
- self.items.iter().cloned().map(Item::price).sum::<usize>()
+ self.items.iter().copied().map(Item::price).sum::<usize>()
}
pub fn can_afford(&self) -> bool {
self.total_price() <= 1000_usize
diff --git a/examples/text-adventure/dsl.rs b/examples/text-adventure/dsl.rs
index 44bc978..c448f04 100644
--- a/examples/text-adventure/dsl.rs
+++ b/examples/text-adventure/dsl.rs
@@ -5,7 +5,7 @@ use std::{rc::Rc, borrow::Cow};
use higher_free_macro::free;
use std::convert::identity;
use higher::Functor;
-use super::data::*;
+use super::data::{Location, Mood, Speaker};
#[derive(Clone)]
pub enum SausageRoll<'a, 's,A>{
diff --git a/examples/text-adventure/logic.rs b/examples/text-adventure/logic.rs
index dc3ff45..d884598 100644
--- a/examples/text-adventure/logic.rs
+++ b/examples/text-adventure/logic.rs
@@ -4,8 +4,8 @@
use std::borrow::Cow;
use higher::{run, Functor, Pure, Bind};
-use super::data::*;
-use super::dsl::*;
+use super::data::{Inventory, Item, Location, Mood, Speaker};
+use super::dsl::{FreeSausageRoll, exposition, give_player_options, present_location, say_dialogue_line};
//Haskell has a when function, and it's nice. Sooo, copy that.
macro_rules! when {
@@ -24,7 +24,7 @@ pub fn game<'a,'s : 'a>() -> FreeSausageRoll<'a, 's, ()>{
c <= intro();
when!{c => {
//handle_rooms is the main game loop: Go from room to room.
- c <= handle_rooms(Location::Refrigerators, Default::default()); //can't destructure in assignment in higher-0.2. Maybe later.
+ c <= handle_rooms(Location::Refrigerators, Inventory::default()); //can't destructure in assignment in higher-0.2. Maybe later.
//if we ended up here, we left the supermarket.
ending(c.1)
}}
@@ -66,7 +66,7 @@ fn ending<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ()>{
} else {
run!{
say_dialogue_line(Speaker::Partner, Cow::from("What did you do in there? I asked you to bring me a sausage roll..."), Mood::Annoyed);
- when!{inventory.items.len() > 0 => {
+ when!{!inventory.items.is_empty() => {
say_dialogue_line(Speaker::Partner, Cow::from("Also, why did you buy all that other stuff?"), Mood::Annoyed)
}};
say_dialogue_line(Speaker::Partner, Cow::from("Well, let's move on, but don't complain if I get hangry on the way."), Mood::Annoyed)
@@ -77,13 +77,11 @@ fn ending<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ()>{
fn handle_rooms<'a,'s:'a>(room: Location, inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{
run!{
c <= handle_room(room, inventory);
- if c.0 != Location::Entrance {
+ if c.0 == Location::Entrance {
+ FreeSausageRoll::pure(c)
+ } else {
//If this were an actual game, we could put a save-point here. At this location the next room to handle is just determined by room and inventory.
handle_rooms(c.0, c.1)
- } else {
- run!{
- yield c
- }
}
}
}
@@ -185,7 +183,7 @@ fn handle_deli<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Loca
3 => {
run!{
i <= talk_to_deli_lady(inventory.clone());
- handle_deli(i.clone())
+ handle_deli(i)
}
},
_ => unreachable!()
@@ -215,7 +213,7 @@ fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (
FreeSausageRoll::pure((Location::Entrance, inventory.clone()))
}
},
- Err(inventory) => handle_checkout(inventory.clone()),
+ Err(inventory) => handle_checkout(inventory),
}
}
},
@@ -275,7 +273,7 @@ fn return_item<'a,'s:'a>(inventory : Inventory, room : Location) -> FreeSausageR
//similar to try_take_item here the inventory can't easily be captured. For illustration purposes, here we
//don't pass it along as clones, but rather return from do-notation to capture it.
let items_in_room = room.items();
- let carried_items_from_here = inventory.items.iter().filter(|i| items_in_room.contains(i)).cloned().collect::<Vec<_>>();
+ let carried_items_from_here = inventory.items.iter().filter(|i| items_in_room.contains(i)).copied().collect::<Vec<_>>();
//ownership shmownership
let carried_items_from_here2 = carried_items_from_here.clone();
let chosen = run! {
diff --git a/examples/text-adventure/main.rs b/examples/text-adventure/main.rs
index c98441b..1d538cc 100644
--- a/examples/text-adventure/main.rs
+++ b/examples/text-adventure/main.rs
@@ -1,3 +1,5 @@
+#![deny(clippy::pedantic)]
+#![deny(clippy::all)]
//! A small example text adventure, the logic of which is implemented as a Free Monad based eDSL.
//!
//! The goal of this game is to buy a sausage roll. With pickle.
@@ -11,17 +13,17 @@
//!
//! In a real project, I'd just make all game state (here: inventory) `Copy`, and use owned values wherever possible to make the code
//! more concise. If `Copy` is not an option, I'd probably make a custom version of `run!{}` that allows to clone the
-//! game state in a convenient way (see https://github.com/bodil/higher/issues/6).
+//! game state in a convenient way (see [higher issue 6](https://github.com/bodil/higher/issues/6)).
//!
//! But on to the explanation what is going on:
//! This project has 4 modules:
-//! - "data" contains the data. Stuff like item types, item descriptions, rooms, etc.
-//! - "dsl" contains the embedded domain specific language. In other words, a Functor and the corresponding Free Monad type (and some helpers)
-//! - "logic" describes the game's main logic using the language defined in "dsl"
-//! - "side_effects" actually runs the logic.
+//! - `data` contains the data. Stuff like item types, item descriptions, rooms, etc.
+//! - `dsl` contains the embedded domain specific language. In other words, a Functor and the corresponding Free Monad type (and some helpers)
+//! - `logic` describes the game's main logic using the language defined in "dsl"
+//! - `side_effects` actually runs the logic.
//!
-//! The important part here is that all the stuff that isn't in "side_effects" is independent of the concrete implementation of "side_effects".
-//! The current "side_effects" runs a text-adventure, but it could just as well render as a visual-novel, without the need to touch any of the other modules.
+//! The important part here is that all the stuff that isn't in `side_effects` is independent of the concrete implementation of `side_effects`.
+//! The current `side_effects` runs a text-adventure, but it could just as well render as a visual-novel, without the need to touch any of the other modules.
mod data;
mod dsl;
diff --git a/examples/text-adventure/side_effects.rs b/examples/text-adventure/side_effects.rs
index ed0f9e1..dd911dd 100644
--- a/examples/text-adventure/side_effects.rs
+++ b/examples/text-adventure/side_effects.rs
@@ -11,23 +11,23 @@ pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result
while let FreeSausageRoll::Free(command) = game {
game = match *command {
crate::dsl::SausageRoll::SayDialogueLine { speaker, text, mood, next } => {
- println!("{} says: \"{}\" with {} on their face.", speaker.text_description(), text, mood.text_description());
+ println!("{} says: \"{text}\" with {} on their face.", speaker.text_description(), mood.text_description());
next
},
crate::dsl::SausageRoll::GivePlayerOptions { options, next } =>
{
println!("Your options are:");
for (id, option) in options.iter().enumerate().map(|(i,o)| (i+1,o)){
- println!("{}: {}", id, option);
+ println!("{id}: {option}");
}
let mut input = String::new();
let mut chosen;
- while let None = {
+ while {
input.clear();
std::io::stdin().read_line(&mut input)?;
chosen = input.trim().parse().ok().filter(|o : &usize| *o > 0 && *o <= options.len());
- chosen
+ chosen.is_none()
} {
println!("Invalid choice. Please select one of the options given above.");
}
@@ -39,7 +39,7 @@ pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result
next
},
crate::dsl::SausageRoll::Exposition { text, next } => {
- println!("{}", text);
+ println!("{text}");
next
},
};