Giter Site home page Giter Site logo

facundoolano / advenjure Goto Github PK

View Code? Open in Web Editor NEW
313.0 9.0 18.0 615 KB

Text adventure engine written in Clojure and ClojureScript

Home Page: https://facundoolano.github.io/advenjure/

License: Eclipse Public License 1.0

Clojure 100.00%
game-development adventure-game-engine adventure-game clojure text-adventure interactive-fiction

advenjure's Introduction

advenjure Build Status

Example game

Advenjure is a text adventure (or interactive fiction) game engine. I wrote it as an excuse to learn Clojure. Some of its distinctive features are:

  • Target the terminal and the browser with the same codebase.
  • Unix-like prompt with smart tab completion and command history
  • Customizable verbs and plugins.
  • LucasArts inspired dialog trees.

Example game

You can see the code for a working example in the advenjure-example repository and play it online here.

For a fully fledged game see House Taken Over.

Installation

Add the following to your project map as a dependency:

[advenjure "0.9.0"]

Basic Usage

Creating items

Text adventures consist mainly of moving around rooms and interacting with items through verb commands such as GO, LOOK, TAKE, etc.

Items are represented by maps in advenjure. the advenjure.items/make function takes a name, a description and a set of key-value pairs to customize behavior. For example:

(require '[advenjure.items :as items])

(def magazine (items/make "magazine"
                          "The cover read 'Sports Almanac 1950-2000'"
                          :take true
                          :read "Oh là là? Oh là là!?"))

That will define a magazine item that can be taken (put in the player's inventory) and can be read (which differs from looking at it).

You can provide a vector of names so the player can refer to it by one of its synonyms; the first name in the vector will be considered its canonical name:

(def magazine (items/make ["magazine" "sports magazine" "newspaper"]
                          "The cover read 'Sports Almanac 1950-2000'"
                          :take true
                          :read "Oh là là? Oh là là!?"))

Like :take and :read there are keywords for the other actions (:look-at, :open, :close, :unlock, etc.).

A special kind of items are those that can contain other items:

(def magazine (items/make "bag" :items #{magazine} :closed true))

The bag contains the magazine, but since it's :closed the player needs to open it before being able to look inside it and take its contents. Note that marking an object as :closed also implies that OPEN and CLOSE verbs can be applied to it (i.e. it means :open true, :close true).

Creating rooms

Once you've created a bunch of items, you'll need to put them in a room (if not directly into the player's inventory). Rooms are also maps and also have an advenjure.rooms/make function to build them:

(require '[advenjure.rooms :as rooms])

(def bedroom (rooms/make "Bedroom"
                         "A smelling bedroom."
                         :initial-description "I woke up in a smelling little bedroom, without windows."))

Note that rooms can have only one name. :initial-description is an optional attribute to define how the player will describe a room the first time he visits it, usually with a more verbose description. If :initial-description is not defined, and whenever the LOOK AROUND command is entered, the regular description will be used.

To add items to a room use advenjure.rooms/add-item:

(def bedroom (-> (rooms/make "Bedroom"
                             "A smelling bedroom."
                             :initial-description "I woke up in a smelling little bedroom, without windows.")
                 (rooms/add-item (items/make "bed" "It was the bed I slept in."))
                 (rooms/add-item magazine "On the floor was a sports magazine.")))

The second parameter is an optional room-specific description of the item. It will be used to mention the item while describing the room (as opposed of the default a <item> is here.).

Building a room map

Once you have some rooms, you need to connect them to build a room map, which is nothing but a plain clojure hash map. First map the room to some id keyword, then connect the rooms using the advenjure.rooms/connect function:

(def room-map (-> {:bedroom bedroom
                   :living living
                   :outside outside}
                  (rooms/connect :bedroom :north :living)
                  (rooms/connect :living :east :outside)))

An alternative function, advenjure.rooms/one-way-connect, allows connecting the rooms just in one direction.

Building and running a game

The next building block is the game map itself, which contains the room map, the player's inventory and a pointer to the current room. advenjure.game/make helps to build it:

(require '[advenjure.game :as game])

(game/make room-map :bedroom)

The room keyword defines what room the player will be in when the game starts. If you want to start off the game with some items in the player's inventory, just pass them in a set as the third argument.

Lastly, the advenjure.game/run takes a game state map, a boolean function to tell if the game has finished and an optional string to print before it starts. Putting it all together in a -main function:

(defn -main
  "Build and run the game."
  [& args]
  (let [game-state (game/make room-map :bedroom)
        finished? #(= (:current-room %) :outside)]
    (game/run game-state finished? :start-message "Welcome to the advenjure!")))

The game flows by taking the initial game state map, prompting the user for a command, applying the command to produce a new game state and repeat the process until the finished? condition is met, which, in the example above means entering the :outside room.

Advanced Usage

There is a number of advanced features available in the engine:

  • Overriding messages: use custom messages for a given action on a room or item.
  • Pre conditions: function hook to define whether an action can be performed.
  • Post conditions: function hook to customize how the game state is modified after an action is performed.
  • Dialogs: interactive dialogs with 'character' items, in the style of the LucasArts graphic adventures.
  • Text customization and internationalization.
  • Custom verbs/commands.
  • Plugin hooks to customize behavior without modifying the library.

I'm waiting for the APIs to stabilize (and get a lot of free time) before fully documenting all those features, but I'd be happy to write something up if you need help with something specific, just file an issue!

Run on the browser

The codebase is prepared to run both in the terminal with Clojure and the browser with ClojureScript. An example configuration, using lein-cljsbuild would be:

:cljsbuild
    {:builds
     {:main {:source-paths ["src"]
             :compiler {:output-to "main.js"
                        :main example.core
                        :optimizations :simple
                        :pretty-print false
                        :optimize-constants true
                        :static-fns true}}}

Then the command lein cljsbuild once will output a main.js file that can be included in any web page to run the game. The HTML should have a #terminal div and include the jQuery Terminal CSS to properly render the terminal.

The current limitations of the ClojureScript version of the library are:

  • No internationalization support (since clojure-gettext does not support ClojureScript).
  • Can use up to :simple optimizations (not :advanced), since ClojureScript self-hosting is required for some of the advanced features.

See the advenjure-example for a game that targets both the terminal and the browser.

Roadmap

Check the milestones.

advenjure's People

Contributors

charleshd avatar facundoolano avatar gman98ish avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

advenjure's Issues

play nicely with the repl

this issue is bigger in clj, remember in cljs the game runs in the browser so the terminal taken problem does not exist.

Still targeting both, if we fix this the clj version may make more sense for development.

  • look into proper fighweel workflow. try to preserve game state instead of restarting when code updates
  • run game or parts of it from REPL (exit dont kill process)
  • add debugging capabilities to a running game, for example print game map (all or specific parts) at any moment
  • develop/test from the repl

Potential feature request: game state events (that are not tied to dialogs)

I'm really enjoying creating a geocaching adventure app using this engine!

But I think I'm looking for a feature which doesn't currently exist. I want to write logic which depends on the state of the game. As a concrete example, the player should not be able to head to a new location ("room") until they have read a note with the coordinates written on it. I think that I could make this work using the events in the advenjure.dialogs namespace (such as set-event, event?, not-event?, etc), but I think that I would be abusing this feature, as it's specifically tied to dialogs.

Or am I just confused about the correct approach to doing this?

Custom commands and interactive objects

Hi! I really like this project (even though I almost don't know Clojure) and I had some fun using it already. However, there are two things I missed. Maybe the are already present, but I couldn't take that from example game:

  1. Custom commands, i.e. push button or pull lever (maybe even move desk, but that's probably another story). They would for example open doors or simply print some messages.
  2. Interactive object where player would need to provide some text from keyboard, for example PIN to ATM or password to the computer, based on some clues around. This could also be a dialog option (say password to a doorman etc.)

So, is it possible to achieve those with current state of engine? If not, do you perhaps have some roadmap on which they are 😉 ?

Anyway, thanks for your effort and publishing this project!

verb synonyms

A verb spec flag that instructs to use another verb instead, i.e. unlock is the same as open for a safe. Could be as easy as having a kw value: {:unlock "No way" :open :unlock}

Debug commands

_get and _set in map. See how to exclude from suggestions

update gettext dictionaries

A lot of code has been rewritten since the en-present and es-past dictionaries were first defined, so some keys are wrong and a lot missing. Scan the files and generate new dictionaries.

consider dropping Clojure (target only ClojureScript)

The engine was written for Clojure because that was what I was learning at the time, but it feels like 99% of the time a user of this library will want to target primarily or exclusively the browser, because who wants to install clojure and play from the terminal?

Even while the conditional code was reduced a lot, removing reader conditionals in the requires alone would improve the readability of the code a lot.

Also, the project will become simpler and development process easier; I imagine new opportunities for improvement will become apparent once we start targeting a single host.

Obviously, unit tests and a better development workflow (i.e. better hot reloading without having to restart the game, fix whatever makes the figwheel repl unresponsive from time to time, etc.).

If you are reading this and think dropping terminal support is a bad idea, please chime in!

cljs tests

there should be a way of porting the clj ones and run both in travis

consider clojure.spec

Could be useful validations of the basic types (items, rooms, etc.)

Probably a good idea to wait for the api to settle before doing this (e.g. #45)

are records needed?

currently Item and Room are records, check if there's a good reason for them not to be just maps.

If they are not needed, remove them (and remove the register-tag-parser from ui.input.cljs

simple hint system

react to hint/think verbs, some sort of what to do next kind of thing

tutorial/wiki

ideally after #16 so the code can be followed from the repl

  1. introduction and concepts
  2. the simplest possible game
  3. the simplest possible game with a puzzle
  4. more items and verbs examples (appendix, item/room kws)
  5. run in the browser
  6. post and pre condition customizations
  7. custom verbs
  8. dialogs
  9. plugins
  10. prompt verbs
  11. internationalization

refactor basic API to favor a declarative/data-oriented style

This:

(def living (-> (room/make "Living Room"
                           "A living room with a nailed shut window"
                           :initial-description "The living room was as smelly as the bedroom..."
                           :synonyms ["living" "livingroom"])
                (room/add-item drawer "There was a chest drawer by the door.")
                (room/add-item door "")
                (room/add-item glass-door "")
                (room/add-item portrait "A portrait ocuppied a very prominent place on one of the walls.")
                (room/add-item (item/make ["window"] "It was nailed shut." :closed true :open "It was nailed shut.") "")))

Should turn into this

(def living (room/make {:name                "Living Room"
                        :description         "A living room with a nailed shut window"
                        :initial-description "The living room was as smelly as the bedroom..."
                        :synonyms            ["living" "livingroom"]
                        :items               [drawer door glass-door portrait
                                              (item/make {:names       ["window"]
                                                          :description "It was nailed shut."
                                                          :closed      true
                                                          :open        "It was nailed shut."})]}
                       :item-descriptions {"drawer"   "There was a chest drawer by the door."
                                           "portrait" "A portrait ocuppied a very prominent place on one of the walls."}))

try not to use eval

In most cases it's used to eval symbols, which can be replaced by (deref (resolve s))

If we successfully remove eval then advanced optimizations for cljs could work again.

Scrollbar bug on GitHub pages

Found an issue on GitHub pages:

If you use height: 100% and padding you need to use box-sizing as well. Otherwise, the element will be bigger than 100%. So in result, you have scrollbars always visible.

Allow function instance instead of symbol

The reason why a symbol is required is that it would break the save/load serialization of the game state if function values are used instead.

Look into making a serializer that knows how to do it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.