aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/text-adventure/data.rs48
-rw-r--r--examples/text-adventure/dsl.rs94
-rw-r--r--examples/text-adventure/logic.rs120
-rw-r--r--examples/text-adventure/main.rs14
-rw-r--r--examples/text-adventure/side_effects.rs42
5 files changed, 197 insertions, 121 deletions
diff --git a/examples/text-adventure/data.rs b/examples/text-adventure/data.rs
index 3e9c49e..bf78977 100644
--- a/examples/text-adventure/data.rs
+++ b/examples/text-adventure/data.rs
@@ -1,13 +1,13 @@
#[derive(Clone, Copy)]
-pub enum Speaker{
+pub enum Speaker {
Partner,
DeliLady,
Cashier,
}
-impl Speaker{
- pub fn text_description(self)->&'static str{
- match self{
+impl Speaker {
+ pub fn text_description(self) -> &'static str {
+ match self {
Speaker::Partner => "Your partner",
Speaker::DeliLady => "The lady behind the deli counter",
Speaker::Cashier => "The cashier",
@@ -16,7 +16,7 @@ impl Speaker{
}
#[derive(Clone, Copy)]
-pub enum Mood{
+pub enum Mood {
Friendly,
Confused,
Happy,
@@ -25,9 +25,9 @@ pub enum Mood{
Apologetic,
}
-impl Mood{
- pub fn text_description(self)->&'static str{
- match self{
+impl Mood {
+ pub fn text_description(self) -> &'static str {
+ match self {
Mood::Friendly => "a friendly expression",
Mood::Confused => "a confused expression",
Mood::Happy => "a happy expression",
@@ -39,7 +39,7 @@ impl Mood{
}
#[derive(Clone, Copy, PartialEq, Eq)]
-pub enum Location{
+pub enum Location {
Entrance,
Deli,
Checkout,
@@ -47,9 +47,9 @@ pub enum Location{
Shelves,
}
-impl Location{
+impl Location {
//Used if we want to present the location in text. If we were writing a visual novel, we would offer another function that returns the respective assets.
- pub fn get_text_description(self)->&'static str{
+ pub fn get_text_description(self) -> &'static str {
match self {
Location::Entrance => "You are at the entrance area of the super market. Behind you is the parking lot, in front the inviting automated doors of the entrance. Your partner is here with you.",
Location::Deli => "This is the area with the deli counter. There is a lady wearing a hair protector and plastic gloves standing behind the presentation tray.",
@@ -60,8 +60,8 @@ impl Location{
}
}
-#[derive(Copy,Clone,PartialEq,Eq)]
-pub enum Item{
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum Item {
//refrigerators
Milk,
Yoghurt,
@@ -81,7 +81,7 @@ pub enum Item{
}
impl Item {
- fn price(self) -> usize {
+ fn price(self) -> usize {
match self {
Item::SausageRoll | Item::FishSandwich | Item::Shots => 300,
Item::Pickles | Item::Pulp => 250,
@@ -110,7 +110,7 @@ impl Item {
}
}
-impl Location{
+impl Location {
pub fn items(self) -> Vec<Item> {
match self {
Location::Entrance => Vec::default(),
@@ -126,34 +126,34 @@ impl Location{
//I left it as Clone intentionally, to illustrate how one can work around the limitation of it not being Copy.
#[derive(Clone, Default)]
pub struct Inventory {
- pub items : Vec<Item>,
+ pub items: Vec<Item>,
}
-impl Inventory{
- pub fn has_item_from_room(&self, room : Location) -> bool {
+impl Inventory {
+ pub fn has_item_from_room(&self, room: Location) -> bool {
let items_from_room = room.items();
self.items.iter().any(|i| items_from_room.contains(i))
}
- pub fn try_add(self, item : Item) -> Result<Self, Self>{
+ pub fn try_add(self, item: Item) -> Result<Self, Self> {
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})
+ Ok(Inventory { items })
} else {
Err(self)
}
}
- pub fn try_remove(mut self, item : Item) -> Result<Self, Self>{
+ pub fn try_remove(mut self, item: Item) -> Result<Self, Self> {
let idx = self.items.iter().position(|i| *i == item);
match idx {
Some(idx) => {
self.items.swap_remove(idx);
Ok(self)
- },
+ }
None => Err(self),
}
}
- pub fn get_money() -> &'static str{
+ pub fn get_money() -> &'static str {
"€10.00" //It doesn't change. Can as well be a constant.
}
pub fn total_price(&self) -> usize {
@@ -162,4 +162,4 @@ impl Inventory{
pub fn can_afford(&self) -> bool {
self.total_price() <= 1000_usize
}
-} \ No newline at end of file
+}
diff --git a/examples/text-adventure/dsl.rs b/examples/text-adventure/dsl.rs
index c448f04..73b6ad4 100644
--- a/examples/text-adventure/dsl.rs
+++ b/examples/text-adventure/dsl.rs
@@ -1,63 +1,95 @@
//This module defines the domain specific language.
-use std::{rc::Rc, borrow::Cow};
+use std::{borrow::Cow, rc::Rc};
+use super::data::{Location, Mood, Speaker};
+use higher::Functor;
use higher_free_macro::free;
use std::convert::identity;
-use higher::Functor;
-use super::data::{Location, Mood, Speaker};
#[derive(Clone)]
-pub enum SausageRoll<'a, 's,A>{
- SayDialogueLine{
+pub enum SausageRoll<'a, 's, A> {
+ SayDialogueLine {
speaker: Speaker,
- text : Cow<'s, str>, //In a real project I would just make this a String. Here it's a reference to show off lifetime support.
- mood : Mood,
- next : A
+ text: Cow<'s, str>, //In a real project I would just make this a String. Here it's a reference to show off lifetime support.
+ mood: Mood,
+ next: A,
},
- GivePlayerOptions{
- options : Vec<&'s str>,
- next : Rc<dyn Fn(usize)-> A + 'a> //let's just assume that the interpreter validates input.
+ GivePlayerOptions {
+ options: Vec<&'s str>,
+ next: Rc<dyn Fn(usize) -> A + 'a>, //let's just assume that the interpreter validates input.
},
- PresentLocation{
- location : Location,
- next : A,
+ PresentLocation {
+ location: Location,
+ next: A,
+ },
+ Exposition {
+ text: &'s str,
+ next: A,
},
- Exposition{
- text : &'s str,
- next : A,
- }
}
-impl<'a,'s, A : 'a> Functor<'a,A> for SausageRoll<'a,'s,A>{
- type Target<T> = SausageRoll<'a,'s,T>;
+impl<'a, 's, A: 'a> Functor<'a, A> for SausageRoll<'a, 's, A> {
+ type Target<T> = SausageRoll<'a, 's, T>;
fn fmap<B, F>(self, f: F) -> Self::Target<B>
where
- F: Fn(A) -> B + 'a {
+ F: Fn(A) -> B + 'a,
+ {
match self {
- SausageRoll::SayDialogueLine { speaker, text, mood, next } => SausageRoll::SayDialogueLine { speaker, text, mood, next: f(next) },
- SausageRoll::GivePlayerOptions { options, next } => SausageRoll::GivePlayerOptions { options, next: Rc::new(move |x| f(next(x))) },
- SausageRoll::PresentLocation { location, next } => SausageRoll::PresentLocation { location, next: f(next) },
- SausageRoll::Exposition { text, next } => SausageRoll::Exposition { text, next: f(next) },
+ SausageRoll::SayDialogueLine {
+ speaker,
+ text,
+ mood,
+ next,
+ } => SausageRoll::SayDialogueLine {
+ speaker,
+ text,
+ mood,
+ next: f(next),
+ },
+ SausageRoll::GivePlayerOptions { options, next } => SausageRoll::GivePlayerOptions {
+ options,
+ next: Rc::new(move |x| f(next(x))),
+ },
+ SausageRoll::PresentLocation { location, next } => SausageRoll::PresentLocation {
+ location,
+ next: f(next),
+ },
+ SausageRoll::Exposition { text, next } => SausageRoll::Exposition {
+ text,
+ next: f(next),
+ },
}
}
}
free!(<'a>, pub FreeSausageRoll<'a,'s,A>, SausageRoll<'a, 's, FreeSausageRoll<'a,'s,A>>);
-pub fn say_dialogue_line<'a,'s:'a>(speaker : Speaker, text : Cow<'s,str>, mood : Mood) -> FreeSausageRoll<'a, 's, ()>{
- FreeSausageRoll::lift_f(SausageRoll::SayDialogueLine { speaker, text, mood, next: () })
+pub fn say_dialogue_line<'a, 's: 'a>(
+ speaker: Speaker,
+ text: Cow<'s, str>,
+ mood: Mood,
+) -> FreeSausageRoll<'a, 's, ()> {
+ FreeSausageRoll::lift_f(SausageRoll::SayDialogueLine {
+ speaker,
+ text,
+ mood,
+ next: (),
+ })
}
-pub fn give_player_options<'a,'s:'a>(options : Vec<&'s str>) -> FreeSausageRoll<'a,'s, usize>{
- FreeSausageRoll::lift_f(SausageRoll::GivePlayerOptions { options, next: Rc::new(identity) })
+pub fn give_player_options<'a, 's: 'a>(options: Vec<&'s str>) -> FreeSausageRoll<'a, 's, usize> {
+ FreeSausageRoll::lift_f(SausageRoll::GivePlayerOptions {
+ options,
+ next: Rc::new(identity),
+ })
}
-pub fn present_location<'a, 's:'a>(location : Location) -> FreeSausageRoll<'a,'s, ()>{
+pub fn present_location<'a, 's: 'a>(location: Location) -> FreeSausageRoll<'a, 's, ()> {
FreeSausageRoll::lift_f(SausageRoll::PresentLocation { location, next: () })
}
-pub fn exposition<'a,'s:'a>(text : &'s str) -> FreeSausageRoll<'a,'s,()>{
+pub fn exposition<'a, 's: 'a>(text: &'s str) -> FreeSausageRoll<'a, 's, ()> {
FreeSausageRoll::lift_f(SausageRoll::Exposition { text, next: () })
}
diff --git a/examples/text-adventure/logic.rs b/examples/text-adventure/logic.rs
index d884598..3f05555 100644
--- a/examples/text-adventure/logic.rs
+++ b/examples/text-adventure/logic.rs
@@ -1,11 +1,12 @@
//! This module does nothing. It just creates the game's high level flow encoded as Free Monad.
-
use std::borrow::Cow;
-use higher::{run, Functor, Pure, Bind};
use super::data::{Inventory, Item, Location, Mood, Speaker};
-use super::dsl::{FreeSausageRoll, exposition, give_player_options, present_location, say_dialogue_line};
+use super::dsl::{
+ exposition, give_player_options, present_location, say_dialogue_line, FreeSausageRoll,
+};
+use higher::{run, Bind, Functor, Pure};
//Haskell has a when function, and it's nice. Sooo, copy that.
macro_rules! when {
@@ -18,9 +19,8 @@ macro_rules! when {
};
}
-
-pub fn game<'a,'s : 'a>() -> FreeSausageRoll<'a, 's, ()>{
- run!{
+pub fn game<'a, 's: 'a>() -> FreeSausageRoll<'a, 's, ()> {
+ run! {
c <= intro();
when!{c => {
//handle_rooms is the main game loop: Go from room to room.
@@ -31,8 +31,8 @@ pub fn game<'a,'s : 'a>() -> FreeSausageRoll<'a, 's, ()>{
}
}
-fn intro<'a,'s:'a>() -> FreeSausageRoll<'a, 's, bool>{
- run!{
+fn intro<'a, 's: 'a>() -> FreeSausageRoll<'a, 's, bool> {
+ run! {
present_location(Location::Entrance);
say_dialogue_line(Speaker::Partner, Cow::from("Would you be so kind as to quickly grab me a sausage roll from the supermarket? With pickle if possible?"), Mood::Friendly);
say_dialogue_line(Speaker::Partner, Cow::from("I'd meanwhile go over to the pharmacy, and buy some pills against headache."), Mood::Friendly);
@@ -47,24 +47,24 @@ fn intro<'a,'s:'a>() -> FreeSausageRoll<'a, 's, bool>{
}
}
-fn ending<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ()>{
+fn ending<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, ()> {
if inventory.items.contains(&Item::SausageRoll) {
- if inventory.items.contains(&Item::Pickles){
- run!{
+ if inventory.items.contains(&Item::Pickles) {
+ run! {
say_dialogue_line(Speaker::Partner, Cow::from("Wait, seriously? You bought a glass of pickles and a sausage roll without pickle?"), Mood::Confused);
exposition("You explain that the deli counter had run out of pickles.");
say_dialogue_line(Speaker::Partner, Cow::from("Well, that's a creative solution."), Mood::Amused);
say_dialogue_line(Speaker::Partner, Cow::from("Thanks a lot, let's move on."), Mood::Happy)
}
} else {
- run!{
+ run! {
say_dialogue_line(Speaker::Partner, Cow::from("Thanks for the sausage roll, but there are no pickles in it?"), Mood::Annoyed);
exposition("You explain that the deli counter had run out of pickles.");
say_dialogue_line(Speaker::Partner, Cow::from("Well, that can't be helped then. Thanks a lot, let's move on."), Mood::Happy)
}
}
} else {
- run!{
+ 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.is_empty() => {
say_dialogue_line(Speaker::Partner, Cow::from("Also, why did you buy all that other stuff?"), Mood::Annoyed)
@@ -74,8 +74,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!{
+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 {
FreeSausageRoll::pure(c)
@@ -86,8 +89,11 @@ fn handle_rooms<'a,'s:'a>(room: Location, inventory : Inventory) -> FreeSausageR
}
}
-fn handle_room<'a,'s:'a>(room : Location, inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)> {
- run!{
+fn handle_room<'a, 's: 'a>(
+ room: Location,
+ inventory: Inventory,
+) -> FreeSausageRoll<'a, 's, (Location, Inventory)> {
+ run! {
present_location(room);
match room {
Location::Refrigerators => handle_refrigerators(inventory.clone()),
@@ -97,10 +103,12 @@ fn handle_room<'a,'s:'a>(room : Location, inventory : Inventory) -> FreeSausageR
Location::Entrance => unreachable!(), //if we are at the entrance, we won.
}
}
-}
+}
-fn handle_refrigerators<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{
- run!{
+fn handle_refrigerators<'a, 's: 'a>(
+ inventory: Inventory,
+) -> FreeSausageRoll<'a, 's, (Location, Inventory)> {
+ run! {
c <= {
let options = if inventory.has_item_from_room(Location::Refrigerators) {
vec!["Move on to the Shelves.", "Move to the deli counter.", "Check Inventory", "Take an item", "Return an item"]
@@ -112,7 +120,7 @@ fn handle_refrigerators<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a
match c {
0 => { FreeSausageRoll::pure((Location::Shelves, inventory.clone())) },
1 => { FreeSausageRoll::pure((Location::Deli, inventory.clone()))},
- 2 => {
+ 2 => {
let inventory = inventory.clone();
run!{
check_inventory(inventory.clone());
@@ -132,9 +140,11 @@ fn handle_refrigerators<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a
}
}
-fn handle_shelves<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{
+fn handle_shelves<'a, 's: 'a>(
+ inventory: Inventory,
+) -> FreeSausageRoll<'a, 's, (Location, Inventory)> {
//this is rather similar to refrigerators. Just different items.
- run!{
+ run! {
c <= {
let options = if inventory.has_item_from_room(Location::Shelves) {
vec!["Move on to the Refrigerators.", "Move to the deli counter.", "Move to the checkout.", "Check Inventory","Take an item", "Return an item"]
@@ -167,8 +177,8 @@ fn handle_shelves<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's,
}
}
-fn handle_deli<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{
- run!{
+fn handle_deli<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)> {
+ run! {
c <= give_player_options(vec!["Move on to refrigerators.", "Move on to shelves.", "Check Inventory", "Talk to the lady behind the counter"]);
match c{
0 => FreeSausageRoll::pure((Location::Refrigerators, inventory.clone())),
@@ -191,8 +201,10 @@ fn handle_deli<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Loca
}
}
-fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{
- run!{
+fn handle_checkout<'a, 's: 'a>(
+ inventory: Inventory,
+) -> FreeSausageRoll<'a, 's, (Location, Inventory)> {
+ run! {
c <= {
let options = if inventory.has_item_from_room(Location::Checkout) {
vec!["Move back to the shelves.", "Pay for your stuff and leave.", "Check Inventory", "Take an item", "Return an item"]
@@ -203,7 +215,7 @@ fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (
};
match c {
0 => { FreeSausageRoll::pure((Location::Shelves, inventory.clone())) },
- 1 => {
+ 1 => {
run!{
r <= try_pay(inventory.clone());
match r {
@@ -237,11 +249,14 @@ fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (
}
}
-fn try_take_item<'a, 's: 'a>(inventory : Inventory, options : Vec<Item>) -> FreeSausageRoll<'a, 's,Inventory>{
+fn try_take_item<'a, 's: 'a>(
+ inventory: Inventory,
+ options: Vec<Item>,
+) -> FreeSausageRoll<'a, 's, Inventory> {
//here we run into an "interesting" issue with Rust's ownership and do-notation.
//We would like to capture inventory and use it in bind-notation, but that doesn't work (except in the first 2 lines), because Inventory isn't Copy.
//This leaves us with a couple of options: We can either pass it through by repeated cloning (done here), or leave do-notation before capturing it.
- run!{
+ run! {
i <= exposition("You look around and these items nearby catch your attention.").fmap(move |_| (inventory.clone(), options.clone()));
o <= give_player_options(i.1.iter().map(|o| o.description()).chain(std::iter::once("Cancel")).collect()).fmap(move |c| (c, i.0.clone(), i.1.clone()));
{
@@ -269,11 +284,19 @@ fn try_take_item<'a, 's: 'a>(inventory : Inventory, options : Vec<Item>) -> Free
}
}
-fn return_item<'a,'s:'a>(inventory : Inventory, room : Location) -> FreeSausageRoll<'a,'s, Inventory>{
+fn return_item<'a, 's: 'a>(
+ inventory: Inventory,
+ room: Location,
+) -> FreeSausageRoll<'a, 's, Inventory> {
//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)).copied().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! {
@@ -281,26 +304,30 @@ fn return_item<'a,'s:'a>(inventory : Inventory, room : Location) -> FreeSausageR
give_player_options(carried_items_from_here.iter().map(|o| o.description()).chain(std::iter::once("Cancel")).collect())
};
chosen.bind(move |c| {
- match carried_items_from_here2.get(c).ok_or_else(|| inventory.clone()).and_then(|i| inventory.clone().try_remove(*i)) {
+ match carried_items_from_here2
+ .get(c)
+ .ok_or_else(|| inventory.clone())
+ .and_then(|i| inventory.clone().try_remove(*i))
+ {
Ok(i) => {
- run!{
+ run! {
exposition("You put back the item.");
yield i.clone()
}
- },
+ }
Err(i) => {
run! {
exposition("You decided to not return an item."); //good enough. We filtered for valid items beforehand.
yield i.clone()
}
- },
+ }
}
})
}
-fn check_inventory<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s, ()>{
+fn check_inventory<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, ()> {
let c = inventory.items.len();
- run!{
+ run! {
exposition("You look at the items you carry. You are holding:");
list_inventory_items(inventory.clone(),0);
if c < 2 {
@@ -314,9 +341,12 @@ fn check_inventory<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s, (
}
}
-fn list_inventory_items<'a,'s:'a>(inventory : Inventory, index : usize) -> FreeSausageRoll<'a,'s, ()>{
+fn list_inventory_items<'a, 's: 'a>(
+ inventory: Inventory,
+ index: usize,
+) -> FreeSausageRoll<'a, 's, ()> {
if index < inventory.items.len() {
- run!{
+ run! {
exposition(inventory.items[index].description());
list_inventory_items(inventory.clone(), index+1)
}
@@ -325,7 +355,7 @@ fn list_inventory_items<'a,'s:'a>(inventory : Inventory, index : usize) -> FreeS
}
}
-fn talk_to_deli_lady<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Inventory>{
+fn talk_to_deli_lady<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, Inventory> {
run!{
exposition("You greet the lady at the deli counter.");
say_dialogue_line(Speaker::DeliLady, Cow::from("Hi! How can I help you, dear?"), Mood::Friendly);
@@ -333,7 +363,7 @@ fn talk_to_deli_lady<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,I
}.bind(move |_| deli_lady_loop(inventory.clone()))
}
-fn deli_lady_loop<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Inventory>{
+fn deli_lady_loop<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, Inventory> {
let has_deli_item = inventory.has_item_from_room(Location::Deli);
let c = run! {
if has_deli_item {
@@ -395,7 +425,9 @@ fn deli_lady_loop<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,In
})
}
-fn try_pay<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Result<Inventory,Inventory>>{
+fn try_pay<'a, 's: 'a>(
+ inventory: Inventory,
+) -> FreeSausageRoll<'a, 's, Result<Inventory, Inventory>> {
let total_price = inventory.total_price();
let can_afford = inventory.can_afford();
run!{
@@ -419,4 +451,4 @@ fn try_pay<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Result<Inv
}
}
}.fmap(move |e| e.map(|_| inventory.clone()).map_err(|_| inventory.clone()))
-} \ No newline at end of file
+}
diff --git a/examples/text-adventure/main.rs b/examples/text-adventure/main.rs
index 1d538cc..ff9b8dd 100644
--- a/examples/text-adventure/main.rs
+++ b/examples/text-adventure/main.rs
@@ -1,27 +1,27 @@
#![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.
-//!
+//!
//! The code of this example contains a few peculiarities, to highlight features of and issues with the current
//! Free Monad code.
//! For instance, it intentionally does not have `Copy` implemented on the player's inventory, to illustrate how
//! one can work around a limitation in the current run!{} macro version.
//! Another thing that is not really that useful in practice is that all strings that are hardcoded are references
//! instead of owned copies. This is just to illustrate that lifetimes are supported too.
-//!
+//!
//! 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 [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.
-//!
+//!
//! 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.
@@ -33,7 +33,7 @@ mod side_effects;
fn main() -> std::io::Result<()> {
//Let's build the game logic. As a data structure.
let game = logic::game();
-
+
//And now let's do something with it.
side_effects::run(game)
-} \ No newline at end of file
+}
diff --git a/examples/text-adventure/side_effects.rs b/examples/text-adventure/side_effects.rs
index dd911dd..379ed5d 100644
--- a/examples/text-adventure/side_effects.rs
+++ b/examples/text-adventure/side_effects.rs
@@ -2,22 +2,30 @@
use crate::dsl::FreeSausageRoll;
-pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result<()>{
+pub fn run<'a, 's: 'a>(mut game: FreeSausageRoll<'a, 's, ()>) -> std::io::Result<()> {
//this function doesn't know who it is, or why it is here. It only knows it must deal.
//Deal with the few commands in the eDSL and nothing more.
//This would be easier to write recursively. However, in an actual project this might run for quite some time.
//Since we operate on the stack, let's rather be safe than sorry, and use a loop instead of recursion therefore.
- while let FreeSausageRoll::Free(command) = game {
+ while let FreeSausageRoll::Free(command) = game {
game = match *command {
- crate::dsl::SausageRoll::SayDialogueLine { speaker, text, mood, next } => {
- println!("{} says: \"{text}\" with {} on their face.", speaker.text_description(), mood.text_description());
+ crate::dsl::SausageRoll::SayDialogueLine {
+ speaker,
+ text,
+ mood,
+ next,
+ } => {
+ println!(
+ "{} says: \"{text}\" with {} on their face.",
+ speaker.text_description(),
+ mood.text_description()
+ );
next
- },
- crate::dsl::SausageRoll::GivePlayerOptions { options, next } =>
- {
+ }
+ crate::dsl::SausageRoll::GivePlayerOptions { options, next } => {
println!("Your options are:");
- for (id, option) in options.iter().enumerate().map(|(i,o)| (i+1,o)){
+ for (id, option) in options.iter().enumerate().map(|(i, o)| (i + 1, o)) {
println!("{id}: {option}");
}
@@ -26,23 +34,27 @@ pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result
while {
input.clear();
std::io::stdin().read_line(&mut input)?;
- chosen = input.trim().parse().ok().filter(|o : &usize| *o > 0 && *o <= options.len());
+ chosen = input
+ .trim()
+ .parse()
+ .ok()
+ .filter(|o: &usize| *o > 0 && *o <= options.len());
chosen.is_none()
} {
println!("Invalid choice. Please select one of the options given above.");
}
println!();
- next(chosen.unwrap()-1)
- },
+ next(chosen.unwrap() - 1)
+ }
crate::dsl::SausageRoll::PresentLocation { location, next } => {
println!("{}", location.get_text_description());
next
- },
+ }
crate::dsl::SausageRoll::Exposition { text, next } => {
println!("{text}");
next
- },
+ }
};
- };
+ }
std::io::Result::Ok(())
-} \ No newline at end of file
+}