RCRPG/Nim

From Rosetta Code
RCRPG/Nim is part of RCRPG. You may find other members of RCRPG at Category:RCRPG.

This Nim version proposes the following commands:

– alias <alias name> <command>: define an alias.

– aliases: list aliases.

– attack <direction>: use sledge to dig a tunnel in the given direction.

– drop all | <item>: drop all items or a specified item.

– east: move to East.

– equip <item>: equip an item present in inventory.

– help: display the list of commands and directions.

– inventory: list items in the inventory.

– look: display location, items and exits.

– name <name>: give a name to the current room.

– north: move to North.

– south: move to South.

– quit: quit game.

– take all | <item>: take all items or a specified item.

– unequip: unequip the equipped item.

– west: move to West.


Directions are: north, east, south, west, up, down.

Code

import random
import strformat
import strutils
import tables

type

  Direction = enum nowhere, north, east, south, west, up, down
  Item = enum none, sledge, ladder, gold, all

  Position = tuple[x, y, z: int]
  Delta = Position

  Room = ref object
    game: Game
    pos: Position
    items: seq[Item]
    passages: array[Direction, bool]

  Game = ref object
    pos: Position
    rooms: Table[Position, Room]
    roomNames: Table[Position, string]
    aliases: Table[string, string]
    inventory: seq[Item]
    equipped: Item


const
  IncPos: array[Direction, Delta] = [(0, 0, 0), (0, -1, 0), (1, 0, 0),
                                     (0, 1, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)]

  OppositeDir: array[Direction, Direction] = [nowhere, south, west, north, east, down, up]

  # Command names and number of tokens required for this command.
  Commands = {"alias": 3, "aliases": 1, "quit": 1, "name": 2, "look": 1,
              "help": 1, "north": 1, "east": 1, "south": 1, "west": 1,
              "up": 1, "down": 1, "attack": 2, "inventory": 1,
              "take": 2, "drop": 2, "equip": 2, "unequip": 1}.toTable()

  StartRoomPos: Position = (0, 0, 0)
  PrizeRoomPos: Position = (1, 1, 5)


####################################################################################################
# Miscellaneous.

func nextPos(oldPos: Position; direction: Direction): Position =
  ## Compute new coordinates from current position and direction.
  (oldPos.x + IncPos[direction].x,
   oldPos.y + IncPos[direction].y,
   oldPos.z + IncPos[direction].z)

#___________________________________________________________________________________________________

proc randomItems(): seq[Item] =
  ## Select randomly the items for a room.
  ## At most one sledge, one ladder and one gold.
  for item in [sledge, ladder, gold]:
    if rand(1) != 0:
      result.add(item)

#___________________________________________________________________________________________________

template toItem(token: string): Item =
  ## Return item from token.
  parseEnum[Item](token.toLower, none)

#___________________________________________________________________________________________________

template toDirection(token: string): Direction =
  ## Return direction from token.
  parseEnum[Direction](token.toLower, nowhere)

#___________________________________________________________________________________________________

proc displayHelp() =
  ## Display help about commands.
  echo "Commands:"
  echo "– alias <alias name> <command>: define an alias."
  echo "– aliases: list aliases."
  echo "– attack <direction>: use sledge to dig a tunnel in the given direction."
  echo "– drop all | <item>: drop all items or a specified item."
  echo "– east: move to East."
  echo "– equip <item>: equip an item present in inventory."
  echo "– help: display this help."
  echo "– inventory: list items in the inventory."
  echo "– look: display location, items and exits."
  echo "– name <name>: give a name to the current room."
  echo "– north: move to North."
  echo "– south: move to South."
  echo "– quit: quit game."
  echo "– take all | <item>: take all items or a specified item."
  echo "– unequip: unequip the equipped item."
  echo "– west: move to West."
  echo "\nDirections:"
  echo "– north, east, south, west, up, down"

####################################################################################################
# Room.

func description(room: Room): string =
  ## Return the description of a room.

  result = "You are at "
  if room.pos in room.game.roomNames:
    result.add(room.game.roomNames[room.pos])
  else:
    result.add(fmt"{room.pos.x}, {room.pos.y}, {room.pos.z}")
  if room.items.len > 0:
    result.add("\nOn the ground you can see: " & room.items.join(", "))
  result.add("\nExits are: ")
  var exits: seq[string]
  for dir, passage in room.passages:
    if passage:
      exits.add(($dir).capitalizeAscii())
  result.add(if exits.len > 0: exits.join(", ") else: "None")

#___________________________________________________________________________________________________

proc take(room: Room; item: Item): seq[Item] =
  ## Take an item in a room.
  if item == all:
    result = room.items
    room.items.setLen(0)
    echo "You now have everything in the room."
  else:
    let idx = room.items.find(item)
    if idx >= 0:
      room.items.delete(idx)
      result = @[item]
      echo "Taken ", item, "."
    else:
      echo "Item not found."


####################################################################################################
# Game.

proc newGame(): Game =
  ## Initialize a game.
  new(result)
  # Create rooms.
  result.rooms[StartRoomPos] = Room(game: result, pos: StartRoomPos, items: @[sledge])
  result.rooms[PrizeRoomPos] = Room(game: result, pos: PrizeRoomPos,
                                    items: @[gold, gold, gold, gold, gold])
  # Create room names.
  result.roomNames = {StartRoomPos: "the starting room",
                      PrizeRoomPos: "the prize room"}.toTable()
  # Create aliases.
  result.aliases = {"n": "north", "e": "east", "s": "south", "w": "west", "u": "up",
                    "d": "down", "a": "attack", "i": "inventory", "l": "look"}.toTable()

#___________________________________________________________________________________________________

proc createAlias(game: Game; newAlias, command: string) =
  ## Create an alias for a command.
  if command in game.aliases:
    echo "You cannot alias an alias."
    return
  game.aliases[newAlias] = command
  echo "Alias created."

#___________________________________________________________________________________________________

proc listAliases(game: Game) =
  ## List the defined aliases.
  for alias, command in game.aliases.pairs:
    echo fmt"{alias}: {command}"

#___________________________________________________________________________________________________

proc look(game: Game) =
  ## Look at current room and display description.
  echo game.rooms[game.pos].description()

#___________________________________________________________________________________________________

proc move(game: Game; direction: Direction) =
  ## Move in a direction.
  if direction == up and ladder notin game.rooms[game.pos].items:
    echo "You’ll need a ladder in this room to go up."
    return
  if game.rooms[game.pos].passages[direction]:
    game.pos = nextPos(game.pos, direction)
  else:
    echo "Can’t go that way."

#___________________________________________________________________________________________________

proc displayInventory(game: Game) =
  ## Display the inventory.
  if game.inventory.len == 0:
    echo "You aren’t carrying anything."
  else:
    echo "Carrying: ", join(game.inventory, ", "), "."
  if game.equipped != none:
    echo "Holding: ", game.equipped, "."

#___________________________________________________________________________________________________

proc take(game: Game; item: Item) =
  ## Take an item.
  game.inventory &= game.rooms[game.pos].take(item)

#___________________________________________________________________________________________________

proc drop(game: Game; item: Item) =
  ## Drop an item.
  if item == all:
    game.rooms[game.pos].items.add(game.inventory)
    game.inventory.setLen(0)
    echo "Everything dropped."
  else:
    let idx = game.inventory.find(item)
    if idx >= 0:
      game.inventory.delete(idx)
      game.rooms[game.pos].items.add(item)
      echo "Dropped ", item, "."
    else:
      echo "Could not find item in inventory."

#___________________________________________________________________________________________________

proc unequip(game: Game) =
  ## Unequip an item.
  if game.equipped == none:
    echo "You aren’t equipped with anything."
  else:
    game.inventory.add(game.equipped)
    echo "Unequipped ", game.equipped, "."
    game.equipped = none

#___________________________________________________________________________________________________

proc equip(game: Game; item: Item) =
  ## Equip an item.
  let idx = game.inventory.find(item)
  if idx >= 0:
    if game.equipped != none:
      game.unequip()
    game.inventory.delete(idx)
    game.equipped = item
    echo "Equipped ", item, "."
  else:
    echo "You aren’t carrying that."

#___________________________________________________________________________________________________

proc name(game: Game; newRoomNameTokens: varargs[string]) =
  ## Name a room.
  game.roomNames[game.pos] = newRoomNameTokens.join(" ")

#___________________________________________________________________________________________________

proc dig(game: Game; direction: Direction) =
  ## Dig in a direction.
  if game.equipped != sledge:
    echo "You don’t have a digging tool equipped."
    return
  if not game.rooms[game.pos].passages[direction]:
    game.rooms[game.pos].passages[direction] = true
    let newRoomPos = nextPos(game.pos, direction)
    if newRoomPos notin game.rooms:
      game.rooms[newRoomPos] = Room(game: game, pos: newRoomPos, items: randomItems())
    game.rooms[newRoomPos].passages[OppositeDir[direction]] = true
    echo "You have dug a tunnel."
  else:
    echo "There’s already a tunnel that way."


####################################################################################################
# Main.

randomize()           # Initialize the random generator.
let game = newGame()

echo "Welcome to the dungeon!"
echo "Grab the sledge and make your way to room 1,1,5 for treasure!"

while true:
  game.look()
  if game.pos == PrizeRoomPos:
    echo "You have found the prize room and the treasure!"
    break
  stdout.write("> ")
  var line: string
  if not stdin.readLine(line):
    break   # End of file.
  var tokens = line.strip().toLower.split()

  # Replace alias by the command.
  if tokens[0] in game.aliases:
    tokens[0] = game.aliases[tokens[0]]

  # Check command.
  if tokens[0] notin Commands:
    echo "Invalid command."
    continue
  if tokens.len != Commands[tokens[0]]:
    echo "Wrong number of parameters."
    continue

  # Process command.
  case tokens[0]
  of "alias":
    game.createAlias(tokens[1], tokens[2])
  of "aliases":
    game.listAliases()
  of "name":
    game.name(tokens[1])
  of "look":
    game.look()
  of "north", "east", "south", "west", "up", "down":
    game.move(tokens[0].toDirection)
  of "inventory":
    game.displayInventory()
  of "take":
    game.take(tokens[1].toItem)
  of "drop":
    game.drop(tokens[1].toItem)
  of "equip":
    game.equip(tokens[1].toItem)
  of "unequip":
    game.unequip()
  of "attack":
    let dir = tokens[1].toDirection
    if dir == nowhere:
      echo "Wrong direction."
    else:
      game.dig(dir)
  of "help":
    displayHelp()
  of "quit":
    break

echo "Thanks for playing"