This commit is contained in:
deadvey 2025-04-09 22:34:45 +01:00
commit c674a69e48
9 changed files with 609 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target

384
Cargo.lock generated Normal file
View File

@ -0,0 +1,384 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clearscreen"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4"
dependencies = [
"nix",
"terminfo",
"thiserror",
"which",
"windows-sys",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]]
name = "errno"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "interactive-storys"
version = "0.1.0"
dependencies = [
"clearscreen",
"regex",
]
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustix"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "terminfo"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
dependencies = [
"fnv",
"nom",
"phf",
"phf_codegen",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "which"
version = "7.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
dependencies = [
"either",
"env_home",
"rustix",
"winsafe",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "interactive-storys"
version = "0.1.0"
edition = "2024"
[dependencies]
clearscreen = "4.0.1"
regex = "1.11.1"

Binary file not shown.

175
src/main.rs Normal file
View File

@ -0,0 +1,175 @@
/*
hashmap narrative = [
("1", ("the sun shone brightly", vector("A", "B"))
("2", ("you get punched in the eye!", vector("C", "D"))
("3", ("you stroke the cat!", vector("1"))
]
hashmap decisions = [
("A", ("Kill him", "1")
("B", ("Eat it", "2")
("C", ("Yes", "1")
("D", ("Stroke the cat", "3")
]
*/
// Please see stories/example.story for syntax examples
use std::fs;
use std::io;
use std::io::{stdin,stdout,Write};
use std::env;
use std::collections::HashMap;
use regex::Regex;
struct ParseError {
line: i64,
error_type: String,
}
fn clear_screen() {
println!("Clearing Screen");
clearscreen::clear().expect("Failed to clear screen");
}
fn parse_story(file_contents: &String) ->
Result<
HashMap<String, (i64, String, Vec<String>)>,
ParseError
>
{
// Hashmaps for storing the contents of the story
let mut narrative: HashMap<String, (i64, String, Vec<String>)> = HashMap::new();
// Define the regex patterns of a peice of narrative and a decision look like
let narrative_regex = Regex::new(r"[\s|\t|\r]?(\d+|[A-Za-z]+) (.*) \{(.*)\}").unwrap(); // eg: "17 You to Greggs {A, B}"
// Parse the file contents in to the two hashmaps
for (line_number, line_string) in file_contents.lines().enumerate()
{
// Parse a narrative line
if narrative_regex.is_match(line_string)
{
for cap in narrative_regex.captures_iter(line_string)
{
let list_string = cap[3].replace(&" ", "");
// Split the list into a vector
let list: Vec<String> = list_string.split(",").map(str::to_string).collect();
narrative.insert(cap[1].to_string(), (line_number as i64 + 1, cap[2].to_string(), list));
}
}
}
// Check that the file is valid
if ! narrative.contains_key("0")
{
return Err(ParseError
{
line: 0,
error_type: "No Narrative with index of 0, Therefore the narrative cannot begin".to_string(),
})
}
return Ok(narrative)
}
fn input() -> String{
let mut s=String::new();
let _=stdout().flush();
stdin().read_line(&mut s).expect("Did not enter a correct string");
if let Some('\n')=s.chars().next_back() {
s.pop();
}
if let Some('\r')=s.chars().next_back() {
s.pop();
}
return s;
}
fn main() -> io::Result<()>
{
// Parse the arguments
let args: Vec<String> = env::args().collect();
if args.len() <= 1
{
println!("ERROR: Please provide the file path of the interactive story\n$ interactive-story -- story.txt");
}
else if &args[1] == "help"
{
println!("Help text here");
}
else
{
// Read the contents of the story to string
let story_file_contents: String = fs::read_to_string(&args[1])?;
// Parse the story
let result = parse_story(&story_file_contents);
match result
{
Err(error) => println!("Erorr on line {}:\n{}", error.line, error.error_type),
Ok(narrative) => {
clear_screen();
let mut indexs: Vec<String> = vec!{"0".to_string()};
let mut line: i64 = 0;
'gameloop: loop
{
// We should check if any of the index is "exit", if so
// that means we should quit, but only if there's exactly one index
if indexs[0].to_lowercase() == "exit" && indexs.len() == 1
{
break 'gameloop;
}
// We must check that all the indexs actually exist
let mut all_the_indexs_exist = true;
let mut none_existant_indexs = Vec::new();
for index in &indexs
{
// Check if narrative contains the key that is the current index
if ! narrative.contains_key(index)
{
all_the_indexs_exist = false;
none_existant_indexs.push(index);
}
} // for index in &indexs {
if all_the_indexs_exist
{
// If there's only one index then it's just a peice of narration, so we ouput
// it and store that one's indexs
if indexs.len() == 1
{
let current_narrative = narrative.get(&indexs[0]).unwrap();
indexs = current_narrative.2.clone();
line = current_narrative.0;
println!("{}", current_narrative.1);
}
// However, if there are more than one index, then that means it is a decision
// step and so we prompt the player to make a decision
else if indexs.len() > 1
{
// relative_index is just the relative number, always starts at 0, it's used as
// a reference for the user to input.
// narrative_index is the actual index of the option that can be chosen
for (relative_index, narrative_index) in indexs.iter().enumerate()
{
let decision_text = &narrative.get(narrative_index).unwrap().1;
println!("{}: {}", relative_index, decision_text);
}
let choice: String = input();
let choice_index: usize = choice.trim().parse().unwrap(); // TODO potential crash?
line = narrative.get(&indexs[choice_index]).unwrap().0;
indexs = narrative.get(&indexs[choice_index]).unwrap().2.clone();
}
} // if all_the_indexs_exist {
else
{
println!("Error on line {}, narrative(s) of index(s) {:?} does not exist", line, none_existant_indexs);
break 'gameloop;
} // else {
println!(""); // Just print a newline to make it look nicer
} // 'gameloop: loop {
} // Ok(narrative, decision) => {
} // match result {
} // else {
println!("Exiting...");
Ok(())
}

BIN
stories/.floss.story.swp Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
1 This story has no narrative with index 0, so it will error!

25
stories/example.story Normal file
View File

@ -0,0 +1,25 @@
# A comment
0 This is a decision based game {1}
1 You walk down the street {A,B}
2 The park is lush and green {C, D}
3 The shop's shelves are brimming with items {E, F, G}
4 You walk around the park then leave the park {1}
5 You play football then leave the park {1}
6 You buy and then drink the refreshing coke then leave {1}
7 "Do you have ID?" {H,I}
8 You buy and eat the Haribos then leave {1}
9 You buy and drink the larger, then fucking die! {J,K}
10 "Piss off then, punk", you piss off {1}
A Go to the park {2}
B Go to the shop {3}
C Walk around the park {4}
D Play football {5}
E Buy a Coke {6}
F Buy a larger {7}
G Buy some Haribos {8}
H Yes {9}
I No {10}
J Restart {1}
K Exit {exit}

14
stories/floss.story Normal file
View File

@ -0,0 +1,14 @@
### Narrations
0 "Hey Floss" says your cousin, Tannerson, "I know it was your Birthday a few days ago, so I thought I'd give you this", Tannerson hands you a small wooden box, about 20cm wide with the letters "AD" embroidered on the lid in gold and a coat of arms with a Strule and some kind of eel on the lock. "What, what it is, and do you have the key?" you ask, curious as to the heavy contents, "I'm not sure, I thought you might be able to open it with one of your lock picking tools and have what ever's inside it, maybe it's gold" suggested your cousin. "Hmm, thanks Tannerson, I'll see what I can do" you reply as Tannerson ducks out of your key cutting and watch mending shop with a kind nod. What do you do? {A,B,C,e}
1 After some hard effort with a crowbar, you manage to open the box as the metal hinge is teared off the box. Inside the box is a thick book titled "THE RISE AND THE FALL OF THE SALVIAN REPUBLIC a historical account from Panoleon I to the sacking of Leith, By Amitale Aberforthy, Translated by Rareks Smithsone" as well as a tape cartridge labeled "The murder of Syme Fils". Over the next few nights, you settle down to read the book as it tells the story of the Salvian Republic and how they conned the people of Terrus (Where you live) into thinking that the Salvians are magic aliens, when in fact, they were originally from Terrus! When you learn of this, you gasp, "If I show this to anyone, I'll be executed for treason for sure!". The book turns your whole knowledge of your world upside down, is it a sort of prank or does it actually make sense? Where should you put the box? {D,E}
2 You look at the lock, but quickly realise that it's complexity is far beyond your capabilities, "Who made this" you wonder {A,C}
3 "Pfft, it's nothing!" you say as you toss it into your bin. Then End {exit}
### Decisions
e Exit {exit}
A Force open the lid with a crowbar {1}
B Attempt to pick the lock {2}
C It's nothing, throw it away! {3}
D Put it on display, the box and it's lock looks nice after all {4}
E Bin it! This stuff could get me in trouble {5}