From 67e74c565748a5d77a708e871e2e51e3d2ad6ff7 Mon Sep 17 00:00:00 2001 From: Lennart Kiil <1455044+kiil@users.noreply.github.com> Date: Wed, 23 Apr 2025 01:10:59 +0200 Subject: [PATCH] add games folder and simple game demonstrating nu code concepts (#1100) I'd like to contribute this game. The code exemplifies several nu concepts. --- games/humlespring/README.md | 32 +++++++ games/humlespring/script.nu | 182 ++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 games/humlespring/README.md create mode 100755 games/humlespring/script.nu diff --git a/games/humlespring/README.md b/games/humlespring/README.md new file mode 100644 index 0000000..4422c80 --- /dev/null +++ b/games/humlespring/README.md @@ -0,0 +1,32 @@ +# Humlespring Text Adventure + +A simple text adventure game written in Nushell. + +The game utilizes Nushell's functional programming capabilities in managing the game map and player state. + +* **Immutable Data Structures**: The game map (`$map`) is defined as a record, which is an immutable data structure. This means the map's contents cannot be changed directly during gameplay, promoting data integrity and predictable game behavior. Locations, exits, items, and NPCs are all defined within this structure. + +* **Data Transformation**: The `describe_location` function takes the player state and map as input and transforms this data to produce the location description. It uses the `get` command to access location data and `str join` to format the output, demonstrating functional data manipulation. + +* **State Management**: The player state (`$player_state`) is a mutable record, but updates to it are handled in a controlled manner within the main loop. Actions like "go", "take", and "search" update the state by assigning new values to the `$player_state` record. + +## How to Play + +1. Make sure you have Nushell installed (`https://www.nushell.sh/`). +2. Save the script as `script.nu`. +3. Run the game by executing `nu script.nu` in your terminal. + +## Commands + +* `look` or `l`: Describe the current location. +* `go` or `move` [direction]: Move in the specified direction (e.g., `go north`). +* `take` or `get` [item]: Pick up an item (e.g., `take leaflet`). +* `inventory` or `i`: View your inventory. +* `talk` [npc]: Talk to a non-player character (e.g., `talk alexander`). +* `search` [object]: Search an object in the current location (e.g., `search tree`). +* `give` [item]: Give an item to an NPC (e.g., `give cat`). +* `quit` or `exit`: Quit the game. + +## Goal + +Olivia has lost her cat, Mittens. Find Mittens and return her to Olivia in the village square. diff --git a/games/humlespring/script.nu b/games/humlespring/script.nu new file mode 100755 index 0000000..33be458 --- /dev/null +++ b/games/humlespring/script.nu @@ -0,0 +1,182 @@ +#!/usr/bin/env nu + +# --- Game Data --- + +# Define the locations in the village +let map = { + village_square: { + desc: "You are in the charming village square of Humlespring. Cobblestones pave the ground. Paths lead north, east, and west. Olivia is here, looking worried." + exits: { north: "tavern", east: "market", west: "alexander_house" } + items: ["leaflet"] + npcs: { + olivia: "Olivia paces back and forth. 'Oh, Alexander... have you seen Mittens? My cat is missing! I last saw her near the old oak tree by the market.'" + } + }, + tavern: { + desc: "The 'Leaky Mug' tavern. It smells faintly of ale and sawdust. The only exit is south." + exits: { south: "village_square" } + items: ["mug"] + npcs: {} + }, + market: { + desc: "The village market area. Stalls are mostly empty now, but an old oak tree stands tall nearby. A faint 'meow' can be heard from the tree. The path leads back west." + exits: { west: "village_square" } + items: [] # Mittens isn't an 'item' to take, but is found here + npcs: {} + special: "You look up the old oak tree and see Mittens stuck on a branch! You carefully coax her down." # Add a trigger for this + }, + alexander_house: { + desc: "You are outside a small, tidy cottage. This must be Alexander's house. The only exit is east. Alexander is tending his small garden." + exits: { east: "village_square" } + items: ["watering_can"] + npcs: { + alexander: "'Ah, hello!' Alexander says, wiping his brow. 'Lovely day, isn't it? Though Olivia seems quite upset about her cat, Mittens. Maybe check near the big oak by the market?'" + } + } +} + +# --- Game State --- + +# Player's current status (mutable) +mut player_state = { + location: "village_square" # Starting location ID + inventory: [] + found_cat: false +} + +# --- Helper Functions --- + +# Describe the current location +def describe_location [state: record, map: record] { + let current_loc_id = $state.location + let loc_data = ($map | get $current_loc_id) + + # Description + print $loc_data.desc + + # Items + if not ($loc_data.items | is-empty) { + print $"You see here: ($loc_data.items | str join ', ')" + } + + # NPCs + let npcs = ($loc_data.npcs | columns) + if not ($npcs | is-empty) { + print $"People here: ($npcs | str join ', ')" + } + + # Exits + let exits = ($loc_data.exits | columns | str join ', ') + print $"Exits are: ($exits)" + + # Check for winning condition trigger + if $current_loc_id == "market" and not $state.found_cat { + print "\nHint: Maybe you should 'search tree'?" + } +} + +# Handle player actions are now inlined in the main loop below + +# --- Main Game Loop --- + +print "Welcome to Humlespring!" +print "-------------------------" +describe_location $player_state $map + +loop { + # Get player input + let user_input = (input "> " | str trim) + + # Skip empty input + if $user_input == "" { continue } + + # Parse input (simple verb-noun) + let parts = ($user_input | split row " ") + let verb = ($parts | get 0 | str downcase) + # Use `get` and `default` for robust noun handling (handles commands with no noun) + let noun = if ($parts | length) > 1 { $parts | get 1 | default "" | str downcase } else {""} + + # grab the current location once + let loc_data = ($map | get $player_state.location) + + # Handle the action directly in the loop + match $verb { + "quit" | "exit" => { + print "Goodbye!" + exit + } + "look" | "l" => { + describe_location $player_state $map + } + "go" | "move" => { + if ( $noun not-in ( $loc_data.exits | columns )) { + print "You can't go that way." + } else { + let new_loc = ($loc_data.exits | get $noun) + $player_state.location = $new_loc + print $"You go ($noun)." + print "" # line break + describe_location $player_state $map + } + } + "inventory" | "i" => { + if ($player_state.inventory | is-empty) { + print "Your inventory is empty." + } else { + print $"You are carrying: ($player_state.inventory | str join ', ')" + } + } + "take" | "get" => { + if ($loc_data.items | find -r $noun).0? != null { + # Just add to inventory + $player_state.inventory = ($player_state.inventory | append $noun) + print $"You take the ($noun)." + } else { + print $"There is no '($noun)' here to take." + } + } + "talk" => { + if ( $noun not-in ( $loc_data.npcs | columns )) { + print $"You cannot talk to ($noun) " } else { + if ($loc_data.npcs | get $noun) == null { + print $"There is no one called '($noun)' here to talk to." + } else { + print ($loc_data.npcs | get $noun) + } + } + } + "search" => { + if $noun == "tree" and $player_state.location == "market" { + if $player_state.found_cat { + print "You already found Mittens!" + } else { + print ($loc_data | get special) + $player_state.found_cat = true + print "\nMittens purrs happily in your arms. You should return her to Olivia!" + } + } else { + print "You search around, but find nothing special." + } + } + "give" => { + if $noun == "cat" and $player_state.location == "village_square" and $player_state.found_cat { + if ('olivia' in ($loc_data.npcs | columns)) { + print "\nYou give Mittens back to Olivia. She is overjoyed!" + print "'Oh, thank you, thank you!' she cries, hugging her cat. 'You saved her!'" + print "\n*** Congratulations! You completed the adventure! ***" + exit + } else { + print "Olivia isn't here right now." + } + } else if $player_state.found_cat and $noun == "cat" { + print "You need to be in the village square to give the cat back to Olivia." + } else { + print "You don't have that to give, or you can't give it here/now." + } + } + _ => { + print "I don't understand that command. Try: go, look, take, inventory, talk, search, give, quit." + } + } + print "" # Add a newline for better readability +}