A means of creating simple (or un-simple) text adventure games in Scheme. PROS: -I have not figured out a single kind of text adventure game I couldn't write with this. -It can be comprehended and executes quickly. -It is clean. CONS (lol, cons): -There is no macro provided as an abstraction of area definitions; one must define one's areas using the template or define one's own area-defining macro. -It uses set!. This is easy to fix, but for now, I'm going to leave it as it is and see if a fix is necessary.
#lang scheme ;; An area is either: ;; code in accordance with the following template: #| (lambda () (begin (display dat) ... (set! id dat) ...)) |# ;; where all dat are data ;; and all id are identifiers ;; OR ;; code in accordance with the following template: #| (lambda () (begin (display dat) ... (set! id dat) ... (local [(define main (lambda (input) (cond [ignore-input-condition-unrelated-to-input ... (area) ...] ... [condition-relating-to-input ... (area) ...] [not-ignore-input-condition-unrelated-to-input ... (area) ...] ... [else (main (read (current-input-port)))])))] (main (read (current-input-port)))))) |# ;; where all dat are data, ;; all id are identifiers, ;; all ignore-input-condition-unrelated-to-input are values of any kind, ;; all condition-relating-to-input are values that are produced by algorithms in which the parameter "input" of main is used, ;; all not-ignore-input-condition-unrelated-to-input are values of any kind, ;; and all area are areas. ;; Allow me to explain the commented templates above. ;; For both templates: ;; The reason that areas are procedures and not constants is because procedures aren't ;; evaluated until they are called, whereas values that are not procedures are evaluated ;; immediately. I'm talking about the lambda that contains the begin. I can't have the display ;; expression, the set! macro, and the read expression be evaluated immediately, because ;; if they are evaluated upon execution, then the areas will be defined as the output of the ;; begin, meaning that all the questions in the game will be asked in the order they are ;; defined and then nothing will happen. By defining area as a procedure producing the output ;; of the begin rather than the output of the begin itself, I am defining area as the algorithm ;; that begins with the begin. Display is used instead of print because display is more aesthetically ;; useful; it doesn't print the quotation marks around the string, and it prints newlines. It also ;; prints \" as ". In case you haven't realized, it's for showing the player what's going on. The ;; reason set! is being used is because I am lazeh and don't want a struct parameter being passed ;; around the areas. It's for information that you want to always be there throughout the game, ;; like the player's name, gender, job, et cetera. The reason begin is being used instead of ;; the "and" operator... Well, it's just more correct, purpose-wise. ;; For the first template: ;; Just one thing to say about the first template: notice how it contains no mention of the ;; word "area." This is because the first template is not recursive; it's the tail of the cons, ;; so to speak. The base case of the recursion. ;; For the second template: ;; Now is when things get interesting. The first part is pretty straightforward, but then you ;; get to the local. This is here to define main. Main is there to take data and ask questions ;; about it, as you can see with the cond. Ignore-input-condition-unrelated-to-input is a ;; value that should not be dependent on "input" (the parameter from main) to calculate and ;; is put above those that are dependent on input to calculate in the cond so that they will be ;; evaluated first, such that the rest of the clauses will be ignored, hence the "ignore-input" ;; part. Generally, they should be dependent on mutable constants (set with the set! expressions). ;; Condition-relating-to-input is a value that should be dependent on "input" to calculate. ;; Not-ignore-input-condition-unrelated-to-input is a value that should not be dependent on ;; "input" to calculate and is put below those that are dependent on input such that their ;; clauses will be evaluated only if they are not false and all of the condition-relating-to-input ;; clauses are false. These are highly uncommon, but they do have a couple of uses. For instance, ;; you are a spy. You are invading an enemy base. The guard asks you for a password. You provide ;; the incorrect password (condition-relating-to-input clause is false). The guard takes a good ;; look at you (moving on to not-ignore-input-condition-unrelated-to-input clause) and sees that ;; you are a spy (let's say there's a constant "class" defined and it has been set! to 'spy by a ;; previous area, and the condition is (eq? class 'spy)). He then gets pissed off and throws you ;; out (calling another area in the body of the clause). The else clause is in case none of these ;; are true, and it usually simply requests input again (it assumes that the input was invalid because ;; none of the cond clauses were true). Technically, there may be reasons that you would want to change ;; this. In these cases, go ahead. After main is defined locally, it is called with user input (the expression ;; (read (current-input-port)) generally prompts the user for input and then uses it). Main must be ;; defined at local scope because if it were visible to other expressions, definitions would interfere. ;; Here is an extremely short, silly, stupid, and somewhat frustrating sample game ;; that is not fun in any way, shape or form but does indeed show an example of how to use this template. (define name "") (define area2 (lambda () (begin (display "Cool beans.") (set! name "Monkey")))) (define area1 (lambda () (begin (display "What is your name?\n") (local [(define main (lambda (input) (cond [(not (string? input)) (begin (display "It's supposed to be a string, idiot.\n") (main (read (current-input-port))))] [(string=? input "Monkey") (area2)] [else (begin (display "I don't like that name. Enter a new one.\n") (main (read (current-input-port))))])))] (main (read (current-input-port))))))) (area1)