luxc 0.8.3

A small teaching language that runs anywhere and transpiles to Rust, Swift, and Go
Documentation
// THE LITTLE KEEP — a tiny text adventure.
//
// The whole world is this file. You play it by running it; you change it by
// editing it. There is no hidden engine: every room, every exit, every door is
// written below in plain lux. Play it once, then open it and read down — you
// already know more of what you're looking at than you'd guess.
//
//     lux run world.lux
//
// When you're ready, try giving the cellar a new exit, or hiding a third thing
// to find.

// The rooms of the keep. An enum is "one of a fixed set" — these five are every
// place you can stand.
enum Room {
    entrance
    hall
    cellar
    vault
    chamber
}

// The whole state of the game in one value: where you are, what you're carrying,
// whether the door is open, and whether you're still playing. We never change a
// World in place. Each turn builds the *next* World from the one before — that's
// why every helper below takes a World and hands one back.
struct World {
    room: Room
    items: [string]
    doorOpen: bool
    playing: bool
}

// Is something in your pack? Your pack is an array — a list that grows — and
// there's no built-in "does it contain this", so we look, one item at a time.
// (lux learn arrays)
func has(items: [string], thing: string) -> bool {
    for it in items {
        if it == thing {
            return true
        }
    }
    return false
}

// What each room looks like. Pure prose, one arm per room.
func describe(room: Room) -> string {
    return match room {
        entrance => "You stand at the mouth of an old stone keep. Cold air drifts from a passage north."
        hall     => "A wide hall, its banners long rotted. Ways lead south and east, and a heavy door faces north."
        cellar   => "A damp cellar smelling of earth and old wine. The way west leads back toward the hall. Worn steps go down into the dark."
        vault    => "A low vault under the keep, the steps you came down rising back into the cellar."
        chamber  => "A round chamber, open at last to the sky. Ivy climbs the walls and a window looks out over green hills."
    }
}

// Print the room you're in, plus whatever is true here right now — the things on
// the floor, the state of the door, the dark of the vault, what you carry.
// Looking never changes anything, so it hands the world straight back.
func describeHere(w: World) -> World {
    print(describe(w.room))
    if w.room == Room.hall {
        if has(w.items, "key") {
            print("You can see where the brass key lay in the dust.")
        } else {
            print("A brass key lies on the dusty floor.")
        }
        if w.doorOpen {
            print("The iron door to the north stands open.")
        } else {
            print("A locked iron door blocks the way north.")
        }
    }
    if w.room == Room.cellar {
        if has(w.items, "torch") {
            print("An empty bracket on the wall is where a torch once burned.")
        } else {
            print("A torch rests in a bracket on the wall.")
        }
    }
    if w.room == Room.vault {
        if has(w.items, "torch") {
            print("Your torch throws back the dark: gold coins spill from a split chest, more than you could carry.")
        } else {
            print("It is pitch black. You can feel the steps behind you, but see nothing ahead. You should fetch a light.")
        }
    }
    if length(w.items) > 0 {
        print("You are carrying:", w.items)
    }
    // The chamber behind the locked door is where the keep tells you what it
    // really is — the reward for getting through, not a thing you read up front.
    if w.room == Room.chamber {
        print("")
        print("You made it through the locked door — and in the light here, the")
        print("keep gives up its secret.")
        print("")
        print("This keep is the very file you are running, and that file is a")
        print("program you can read. Open it in your editor: every room, the")
        print("locked door, the torch in the cellar, all of it written there in")
        print("plain lux, with nothing hidden from you.")
        print("")
        print("Which means you can change it. Rewrite a room's description and run")
        print("it again — the keep changes because you changed it. Then make the")
        print("cellar lead somewhere new, or hide a third thing to find. You are")
        print("not playing the keep anymore. You are building it.")
        print("")
        // A program can read and write files, too. We read first to see if the
        // secret is already saved — readFile fails when the file isn't there,
        // which is exactly when to write it — so we only write it once, and never
        // clobber a copy you've started changing. Each call hands back a Result
        // you have to read. (lux learn io)
        let note = "You found the secret of the keep.\n\nThis keep is the very file you ran to play it — a program you can open and read. Change a room's description, give the cellar a new exit, or hide another thing to find, then run it again.\n\nWhen you're ready:\n  lux learn crawl   how a world is put together\n  lux learn         everything else lux can do\n\nIt's your keep.\n"
        match readFile("the-secret.txt") {
            ok(let _)  => print("(Your copy is already saved in the-secret.txt.)")
            err(let _) => match writeFile("the-secret.txt", note) {
                ok(let _)  => print("(There's a copy in the-secret.txt now, so it's not lost when you leave.)")
                err(let _) => print("(I couldn't leave a copy on disk, but it's all here on the screen.)")
            }
        }
        print("")
        print("When you're ready, type quit to step out of the keep, then:")
        print("")
        print("  lux learn crawl     how a world is put together")
        print("  lux magic input     make your keep ask questions and listen")
        print("  lux learn           everything else lux can do")
        print("")
        print("Have fun. Break things. It's your keep.")
    }
    return w
}

// The map. Given a room and a direction, where does that lead? `none` means
// there's nothing that way. Locks aren't handled here — this is just geography.
func exit(room: Room, dir: string) -> Option<Room> {
    return match room {
        entrance => match dir {
            "north" => some(Room.hall)
            _       => none
        }
        hall => match dir {
            "south" => some(Room.entrance)
            "east"  => some(Room.cellar)
            "north" => some(Room.chamber)
            _       => none
        }
        cellar => match dir {
            "west" => some(Room.hall)
            "down" => some(Room.vault)
            _      => none
        }
        vault => match dir {
            "up" => some(Room.cellar)
            _    => none
        }
        chamber => match dir {
            "south" => some(Room.hall)
            _       => none
        }
    }
}

// Step into a room: build the next world there, then describe it.
func enter(w: World, r: Room) -> World {
    let moved = World(room: r, items: w.items, doorOpen: w.doorOpen, playing: w.playing)
    return describeHere(moved)
}

// Try to enter a room. The chamber is the one place behind the locked door.
func tryEnter(w: World, r: Room) -> World {
    if r == Room.chamber {
        if w.doorOpen {
            return enter(w, r)
        }
        print("A heavy iron door blocks the way north. It's locked.")
        return w
    }
    return enter(w, r)
}

// Walk in a direction: look up where it leads — that lookup is an Option, since
// there may be nothing that way — then try to go there. (lux learn option)
func walk(w: World, dir: string) -> World {
    return match exit(w.room, dir) {
        some(let r) => tryEnter(w, r)
        none        => cantGo(w)
    }
}

func cantGo(w: World) -> World {
    print("You can't go that way.")
    return w
}

// Pick something up — but only where it lies. Adding to your pack builds a new
// list with the thing on the end; the old world is left untouched.
func takeThing(w: World, thing: string, place: Room) -> World {
    if w.room == place {
        if has(w.items, thing) {
            print("You already have the " + thing + ".")
            return w
        }
        var pack = w.items
        pack += thing
        print("You take the " + thing + ".")
        return World(room: w.room, items: pack, doorOpen: w.doorOpen, playing: w.playing)
    }
    print("There's no " + thing + " here to take.")
    return w
}

// Open the iron door — only in the hall, and only with the key in your pack.
func openDoor(w: World) -> World {
    if w.room == Room.hall {
        if w.doorOpen {
            print("The iron door already stands open.")
            return w
        }
        if has(w.items, "key") {
            print("The brass key turns with a heavy clunk. The iron door swings open to the north.")
            return World(room: w.room, items: w.items, doorOpen: true, playing: w.playing)
        }
        print("The iron door won't budge. It needs a key.")
        return w
    }
    print("There's no door to open here.")
    return w
}

// Taking the gold is never listed in help — a small reward for trying the
// obvious thing, and a nudge that you can add commands of your own.
func takeGold(w: World) -> World {
    if w.room == Room.vault {
        if has(w.items, "torch") {
            if has(w.items, "gold") {
                print("Your pockets are already heavy with coins.")
                return w
            }
            print("You fill your pockets with coins — it barely dents the hoard.")
            print("(That one was never in the help. It's just a line in world.lux — add a command of your own the same way.)")
            var pack = w.items
            pack += "gold"
            return World(room: w.room, items: pack, doorOpen: w.doorOpen, playing: w.playing)
        }
        print("It's too dark to see any gold. You would need a light.")
        return w
    }
    print("There's no gold here to take.")
    return w
}

func showHelp(w: World) -> World {
    print("Type a command — the left column is what you type:")
    print("")
    print("  command                         what the command does")
    print("  look                            see the room again")
    print("  north  south  east  west        move that way")
    print("  up  down                        go up or down")
    print("  take key   take torch           pick something up")
    print("  open door                       try a door")
    print("  help                            see this list")
    print("  quit                            stop playing")
    return w
}

func leave(w: World) -> World {
    print("You step back into the daylight. Farewell.")
    return World(room: w.room, items: w.items, doorOpen: w.doorOpen, playing: false)
}

func huh(w: World, cmd: string) -> World {
    print("You're not sure how to \"" + cmd + "\" here. Type help for what you can do.")
    return w
}

// One turn: take the world and what you typed, hand back the next world.
// This is the heart of the game, and it's just a list of what each command does.
func step(w: World, cmd: string) -> World {
    return match cmd {
        "look"       => describeHere(w)
        "help"       => showHelp(w)
        "quit"       => leave(w)
        "north"      => walk(w, "north")
        "south"      => walk(w, "south")
        "east"       => walk(w, "east")
        "west"       => walk(w, "west")
        "up"         => walk(w, "up")
        "down"       => walk(w, "down")
        "take key"   => takeThing(w, "key", Room.hall)
        "take torch" => takeThing(w, "torch", Room.cellar)
        "take gold"  => takeGold(w)
        "open door"  => openDoor(w)
        _            => huh(w, cmd)
    }
}

// Start at the entrance with an empty pack, describe it, then read commands one
// line at a time until you quit or the input runs out. Each line becomes the
// next world.
print("THE LITTLE KEEP")
print("")
var world = World(room: Room.entrance, items: [], doorOpen: false, playing: true)
world = describeHere(world)
print("")
print("(type help for commands)")

while world.playing {
    print("")
    world = match readLine() {
        some(let line) => step(world, line)
        none           => leave(world)
    }
}