RCRPG/Julia

From Rosetta Code
Revision as of 11:11, 12 February 2019 by Wherrera (talk | contribs)

This Julia version of RCRPG implements a text interface. When run within Julia's command line REPL, it will display many of the game items in color.

Language Idioms

This program illustrates some of the interesting aspects of Julia:

  • dynamic and optional data types
  • multiple function dispatch used to select function behavior by data type
  • built-in data structures including named tuples, vectors, matrices, and Dicts (hashes)
  • functional programming with map and other functions using anonymous functions on arrays

Objective

The objective of the game is to find your way to the treasure room, which is located in the upper left corner on level 5 (centered at (1, 1, 5)), and then to exit the game by ascending above level 1.

Commands

Direction commands:

north, south, east, west, up, down

Type the first letter of these commands

in order to go in a certain direction (n, s, e, w, u, d).

To exit a room, you must use the sledge to create a hole through the room wall, ceiling, or floor. Make a hole by e(q)uipping a sledge (equip with q), and (a)ttacking the obstacle (attack with a).

To go down, there must be a pit in the floor where you are standing. Create a pit by (a)ttacking (d)own.

In order to go up, there must be a skylight in the ceiling where you are standing and you must have a ladder equipped. Create a skylight with the sledge (attack up).

A sledge can be used indefinitely without losing it, but you lose your equipped ladder when you go up using that ladder.

Attack (dig with the sledgehammer) with the letter a. You must have a sledge equipped (equip with letter q) to do this.

Take gold and other items from room floor by standing over the item and then (t)aking with letter t.

Remove items with letter r for (r)emove. Removing equipped items will move to inventory, removing inventory drops them on the dungeon floor.

Rooms for each a level remain only while you are on that level. Levels generate new rooms each time you renter them.

View inventory: Letter i.

Current r(o)om information: Letter o.

Help: Letter h.

Code

<lang julia>using Crayons

struct Point

   x::Int
   y::Int
   z::Int

end

const obstacles = (wall = '\u2592', permwall = '\u2593', water = '~')

const defaults = (baseluck = 0.5, levelrooms = 8, itemrarity = 0.2, maxroomdim = 20,

   wallheight = 3, leveldim = 100, prizeroomlevel = 5, fastdig = true, map = 1,
   showlevel = true)

abstract type Item end

struct Room

   origin::Point
   xsize::Int
   ysize::Int

end

struct Level

   map::Int
   grid::Matrix{Char}
   zbase::Int
   floorlevel::Int
   height::Int
   rooms::Vector{Room}
   content::Dict{Point, Vector{Item}}

end

struct Location

   p::Point
   room::Union{Room, Nothing}
   level::Level

end

mutable struct Agent <: Item

   location::Location
   inventory::Vector{Item}
   wearing::Vector{Item}
   wielding::Vector{Item}
   Agent(loc) = new(loc, Item[Gold(0)], Item[], Item[])

end

mutable struct Gold <: Item

   value::Int
   disp::Char
   Gold(v::Int) = new(v, 'g')

end

struct Sledge <: Item

   weight::Int
   disp::Char
   Sledge() = new(10, 's')

end

struct Ladder <: Item

   weight::Int
   disp::Char
   Ladder() = new(7, 'l')

end

struct Pit <: Item

   disp::Char
   Pit() = new('_')

end

struct Skylight <: Item

   disp::Char
   Skylight() = new('^')

end

const charcrayons = Dict{Char,Crayon}('p' => crayon"white", ' ' => crayon"black", 'g' => crayon"yellow",

                                     's' => crayon"blue", 'l' => crayon"green", '_' => crayon"red", 
                                     '^' => crayon"light_cyan", '~' => crayon"blue",
                                     '\u2592' => crayon"light_gray", '\u2593' => crayon"dark_gray")

onpoint(p, level) = haskey(level.content, p) ? level.content[p] : Item[] hastype(t::Type, arr) = begin for i in arr if typeof(i) == t return true end end; false end hastype(t, p, level) = hastype(t, onpoint(p, level)) x1y1x2y2(room) = [room.origin.x, room.origin.y, room.origin.x + room.xsize, room.origin.y + room.ysize] inroom(x, y, rm) = begin x1, y1, x2, y2 = x1y1x2y2(rm); x1 <= x <= x2 && y1 <= y <= y2 end inroom(p::Point, rm) = inroom(p.x, p.y, rm) isinventory(t::Type, player) = hastype(t, player.inventory) iswielding(t::Type, player) = hastype(t, player.wielding) iswearing(t::Type, player) = hastype(t, player.wearing) isequip(i::Ladder) = true isequip(i::Sledge) = true isequip(i::Item) = false

function overlaps(testroom::Room, baseroom::Room, level::Level)

   x1, y1, x2, y2 = x1y1x2y2(testroom)
   xx1, yy1, xx2, yy2 = x1y1x2y2(baseroom)
   x2 < xx1 || xx2 < x1 || y2 < yy1 || yy2 < y1 ? false : true

end

overlaps(notyet::Room, level::Level) = (for r in level.rooms if overlaps(notyet, r, level) return true end end; false) randpoint(level) = begin xmax, ymax = size(level.grid); Point(rand(1:xmax), rand(1:ymax), level.zbase) end addatpoint(i, p, lev) = (if !haskey(lev.content, p) lev.content[p] = Vector{Item}() end; push!(lev.content[p], i))

function randorigin(level, xd=defaults.maxroomdim, yd=defaults.maxroomdim)

   xmax, ymax = size(level.grid)
   Point(rand(1:xmax-xd), rand(1:ymax-yd), level.zbase)

end

function notyetaroom(level::Level, origin::Point, minx=6, maxx=50, miny=6, maxy=25)

   levsize = size(level.grid)
   if origin.x < 3 || origin.y < 3
       origin = Point(3, 3, origin.z)
   end
   xmax, ymax = min(maxx, levsize[1] - origin.x), min(maxy, levsize[2] - origin.y)
   xsize = rand(minx:xmax)
   ysize = rand(miny:ymax)
   Room(origin, xsize, ysize)

end

function emptypoints(room, level)

   vpt = Vector{Point}()
   x1, y1, x2, y2 = x1y1x2y2(room)
   for x in x1+1:x2-1, y in y1+1:y2-1
       if level.grid[x, y] == ' '
           push!(vpt, Point(x, y, level.zbase))
       end
   end
   vpt

end randemptypoint(room, level) = (pts = emptypoints(room, level); isempty(pts) ? room.origin .+ 2 : rand(pts))

function addtoroom(item, room, level)

   p = randemptypoint(room, level)
   addatpoint(item, p, level)
   level.grid[p.x, p.y] = item.disp

end

function fillroom(room, level, uppossible=true, downpossible=true)

   grid = level.grid
   x1, y1, x2, y2 = x1y1x2y2(room)
   grid[x1:x2, y1:y2] .= ' '
   grid[x1:x2, y1] .= obstacles.wall
   grid[x1:x2, y2] .= obstacles.wall
   grid[x1, y1:y2] .= obstacles.wall
   grid[x2, y1:y2] .= obstacles.wall
   for _ in 1:Int(floor(defaults.baseluck * 8))
       if rand() < defaults.baseluck
           val = Int(floor(100.0 * rand() * defaults.baseluck))
           addtoroom(Gold(val), room, level)
       end
   end
   if rand() * defaults.baseluck * 10 > 1.0 && uppossible
       addtoroom(Ladder(), room, level)
   end
   if rand() * defaults.baseluck * 15 > 1.0 && downpossible
       addtoroom(Pit(), room, level)
   end
   if rand() * defaults.baseluck * 5 > 1.0
       addtoroom(Sledge(), room, level)
   end
   room

end

function makeprizeroom(level)

   prizeroom = Room(Point(2, 2, level.zbase), defaults.maxroomdim, defaults.maxroomdim)
   x1, y1, x2, y2 = x1y1x2y2(prizeroom)
   level.grid[x1:x2, y1:y2] .= ' '
   level.grid[x1:x2, y1] .= obstacles.permwall
   level.grid[x1:x2, y2] .= obstacles.wall
   level.grid[x1, y1:y2] .= obstacles.permwall
   level.grid[x2, y1:y2] .= obstacles.wall
   for _ in 1:Int(floor(defaults.baseluck * 40))
       val = Int(floor(5000.0 * rand() * defaults.baseluck))
       addtoroom(Gold(val), prizeroom, level)
   end
   prizeroom

end

function makelevel(floorlevel, gridsize=defaults.leveldim, height=defaults.wallheight, map=defaults.map)

   grid = fill(obstacles.wall, gridsize, gridsize)
   grid[:, 1] .= obstacles.permwall
   grid[:, end] .= obstacles.permwall
   grid[1, :] .= obstacles.permwall
   grid[end, :] .= obstacles.permwall
   rooms = Vector{Room}()
   level = Level(map, grid, floorlevel * height, floorlevel, height, rooms, Dict())
   if floorlevel == defaults.prizeroomlevel
       push!(rooms, makeprizeroom(level))
   end
   trycount = 0
   while trycount < defaults.levelrooms * 5 && length(rooms) < defaults.levelrooms
       rm = notyetaroom(level, randorigin(level))
       if !overlaps(rm, level)
           fillroom(rm, level)
           push!(rooms, rm)
       end
       trycount += 1
   end
   level

end

function displaytunnel(player)

   p = player.location.p
   visgrid = view(player.location.level.grid, p.x-1:p.x+1, p.y-1:p.y+1)
   for y in 1:3
       for x in 1:3
           ch = (x == 2 == y) ? 'p' : visgrid[x, y]
           print(charcrayons[ch], ch)
       end
       println()
   end
   println()

end

function updateroom(player)

   r = nothing
   for room in player.location.level.rooms
       if inroom(player.location.p, room)
           r = room
           break
       end
   end
   if r != player.location.room
       player.location = Location(player.location.p, r, player.location.level)
   end

end

function displayroom(player, lit=true)

   println()
   if !lit
       return
   end
   x1, y1, x2, y2 = x1y1x2y2(player.location.room)
   playerx, playery = player.location.p.x, player.location.p.y
   for y in y1:y2
       for x in x1:x2
           ch = (x == playerx && y == playery) ? 'p' : player.location.level.grid[x, y]
           print(charcrayons[ch], ch)
       end
       println()
   end
   println()

end

function displaylevel(player, showunvisited=true)

   mat = player.location.level.grid
   p = player.location.p
   for y in 1:size(mat)[2]
       println()
       for x in 1:size(mat)[1]
           print((x == p.x && y == p.y) ? 'p' : mat[x, y])
       end
   end
   println

end

function queryprompt(query, choices, choicetxt="")

   carr = map(x -> uppercase(strip(string(x))), collect(choices))
   while true
       print(query, " ", choicetxt == "" ? carr : choicetxt, ": ")
       choice = uppercase(strip(readline(stdin)))
       if choice in carr
           return choice
       end
       println()
   end

end

help(player=nothing) =

   println("n north, s south, w west, e east, d down, u up, i inventory, o roominfo, t take, r drop, q equip, a attack/dig")

function playerdisplayinventory(player)

   println("Inventory: $(player.inventory)\n")
   println("Wielding: $(player.wielding)\n")    

end

function playerroominfo(player)

   if player.location.room == nothing
       println("You are between rooms on level $(player.location.level).")
       return
   end
   if player.location.level.floorlevel == defaults.prizeroomlevel
       println("The prize room is on this level!")
   end
   println("You are on level $(player.location.level.floorlevel). You look around the current room.")
   hasgold, hasladder, haspit, hassledge, hasdiggable = false, false, false, false, false
   saytype(t::Gold) = if !hasgold println("There is gold here."); hasgold = true end
   saytype(t::Ladder) = println("There is a ladder here.")
   saytype(t::Pit) = println("There is a pit here.")
   saytype(t::Sledge) = println("There is a sledgehammer here.")
   for (p, v) in player.location.level.content
       if inroom(p, player.location.room)
           for i in v
               saytype(i)
           end
       end
   end

end

function playerequip(player)

   for (i, item) in enumerate(player.inventory)
       if isequip(item)
           yn = queryprompt("Equip $item? (y)es, (n)o, (q)uit choice", ["Y", "N", "Q"])
           if yn == "Y"
               if iswielding(typeof(item), player)
                   println("You are already wielding a $(typeof(item)).")
               else
                   push!(player.wielding, item)
                   deleteat!(player.inventory, i)
               end
           elseif yn == "Q"
               break
           end
       end
   end

end

function playerremove(player)

   for (i, item) in enumerate(player.wielding)
       if iswielding(typeof(item), player)
           yn = queryprompt("Unequip $item? (y)es, (n)o, (q)uit choice", ["Y", "N", "Q"])
           if yn == "Y"
               push!(player.inventory, item)
               deleteat!(player.wielding, i)
           elseif yn == "Q"
               break
           end
       end
   end
   for (i, item) in enumerate(player.inventory)
       if isinventory(item)
           yn = queryprompt("Drop $item? (y)es, (n)o, (q)uit choice", ["Y", "N", "Q"])
           if yn == "Y"
               if !haskey(player.location.level.content, player.location.p)
                   player.location.level.content[player.location.p] = Vector{Item}()
               end
               push!(player.location.level.content[player.location.p], item)
               deleteat!(player.inventory, i)
           elseif yn == "Q"
               break
           end
       end
   end

end

function playertake(player)

   function addgold(gitem)
       if findfirst(x -> typeof(x) == Gold, player.inventory) == nothing
           push!(player.inventory, Gold(0))
       end
       player.inventory[findall(x -> typeof(x) == Gold,
           player.inventory)[1]].value += gitem.value
   end
   canpickup(i::Gold) = true
   canpickup(i::Sledge) = true
   canpickup(i::Ladder) = true
   canpickup(i::Item) = (println("Cannot pick that up."); false)
   takeit(g::Gold) = begin addgold(g); println("You add $(g.value) in gold.") end
   takeit(s::Sledge) = begin push!(player.inventory, s); println("You have a sledge.") end
   takeit(s::Ladder) = begin push!(player.inventory, s); println("You have a ladder.") end
   d = player.location.level.content
   if player.location.room != nothing && haskey(d, player.location.p)
       for _ in 1:length(d[player.location.p])
           item = pop!(d[player.location.p])
           if canpickup(item)
               takeit(item)
           else
               pushfirst!(d[player.location.p], item)
           end
       end
       if isempty(d[player.location.p])
           delete!(d, player.location.p)
           player.location.level.grid[player.location.p.x, player.location.p.y] = ' '
       else
           player.location.level.grid[player.location.p.x, player.location.p.y] =
               d[player.location.p][1].disp
       end
   end

end

function exitmap(player)

   println("You exit the game with inventory $(player.inventory), wielding $(player.wielding).")
   exit()

end

function newlevel(player, levelsdown, startup=false)

   oldlevel = player.location.level.floorlevel
   newlevel = oldlevel + levelsdown
   if newlevel < 1
       exitmap(player)
   else
       level = makelevel(newlevel)
       room = rand(level.rooms)
       point = randemptypoint(room, level)
       player.location = Location(point, room, level)
       playerroominfo(player)
   end
   if defaults.showlevel
       displaylevel(player)
   end
   player

end

function playerup(player)

   if haskey(player.location.level.content, player.location.p) &&
       hastype(Skylight, player.location.p, player.location.level) &&
       iswielding(Ladder, player)
       deleteat!(player.wielding, findfirst(i -> typeof(i) == Ladder, player.wielding))
       println("You have to leave your ladder behind.")
       newlevel(player, -1)
   else
       println("You need to wield a ladder while at a skylight to go upwards.")
   end

end

function playerdown(player)

   if haskey(player.location.level.content, player.location.p) &&
       hastype(Pit, player.location.p, player.location.level)
       newlevel(player, 1)
   else
       println("You can only move downwards at a Pit in the floor.")
   end

end

function playerdig(player)

   movedirs = Dict("N" => [0, -1], "W" => [-1, 0], "S" => [0, 1], "E" => [1, 0])
   if !iswielding(Sledge, player)
       println("You need to be wielding a Sledge.")
       return
   end
   direc = queryprompt("Direction?", ["N", "S", "E", "W", "U", "D"])
   if direc == "U"
       if rand() < defaults.baseluck
           addatpoint(Skylight(), player.location.p, player.location.level)
           player.location.level.grid[player.location.p.x, player.location.p.y] = '^'
           println("There is now a hole in the ceiling above you.")
       end
   elseif direc == "D"
       if rand() < defaults.baseluck
           addatpoint(Pit(), player.location.p, player.location.level)
           player.location.level.grid[player.location.p.x, player.location.p.y] = '_'
           println("There is now a pit in the floor at your feet.")
       end
   else
       goalp = Point(player.location.p.x + movedirs[direc][1], player.location.p.y +
           movedirs[direc][2], player.location.p.z)
       if player.location.level.grid[goalp.x, goalp.y] == obstacles.wall
           if rand() < defaults.baseluck
               if defaults.fastdig
                   depth = 0
                   for i in 1:div(defaults.leveldim, 10)
                       p = Point(player.location.p.x + movedirs[direc][1] * i,
                           player.location.p.y + movedirs[direc][2] * i, player.location.p.z)
                       if player.location.level.grid[p.x, p.y] == obstacles.wall
                          player.location.level.grid[p.x, p.y] = ' '
                          depth += 1
                       end
                   end
                   if depth > 3
                       println("Boom! Your sledgehammer's attack on this wall is amazing.")
                   end
               else
                   player.location.level.grid[goalp.x, goalp.y] = ' '
               end
           end
       elseif player.location.level.grid[goalp.x, goalp.y] == obstacles.permwall
           println("This kind of wall cannot be removed.")
       end
   end

end

function playermove(player, dx, dy)

   goalp = Point(player.location.p.x + dx, player.location.p.y + dy, player.location.p.z)
   if player.location.level.grid[goalp.x, goalp.y] in obstacles
       println("Something is in the way.")
   else
       player.location = Location(goalp, player.location.room, player.location.level)
   end

end playerN(player) = playermove(player, 0, -1) playerW(player) = playermove(player, -1, 0) playerS(player) = playermove(player, 0, 1) playerE(player) = playermove(player, 1, 0)

const usercommands = Dict("N" => playerN, "S" => playerS, "E" => playerE, "W" => playerW,

                         "U" => playerup, "D" => playerdown, "I" => playerdisplayinventory,
                         "O" => playerroominfo, "T" => playertake, "R" => playerremove,
                         "Q" => playerequip, "A" => playerdig, "?" => help)

function runuser(player)

   updateroom(player)
   if player.location.room == nothing
       displaytunnel(player)
   else
       displayroom(player)
   end
   choice = queryprompt("Choose move (nsewudiotrqa?)",
       ["N", "S", "E", "W", "U", "D", "I", "O", "T", "R", "Q", "A", "?"])
   usercommands[choice](player)

end

function newplayer(newlevel=1)

   level = makelevel(newlevel)
   room = rand(level.rooms)
   point = randemptypoint(room, level)
   player = Agent(Location(point, room, level))
   playerroominfo(player)
   if defaults.showlevel
       displaylevel(player)
   end
   player

end

function rungame()

   player = newplayer()
   while true
       runuser(player)
   end

end

rungame() </lang>