RCRPG/Julia: Difference between revisions

m
Fixed syntax highlighting.
m (Fixed syntax highlighting.)
 
(9 intermediate revisions by one other user not shown)
Line 1:
This [[Julia]] version of [[RCRPG]] implementscontains two versions of the game code: one version with a text interface. Whenand runa withinsecond Julia'sone commandwith linea REPL,GUI interface.
it will display many of the game items in color.
 
===Language Idioms===
 
This program illustrates some of the interesting aspects of Julia:
Line 10 ⟶ 9:
* functional programming with map and other functions using anonymous functions on arrays
 
<br />
== Objective ==
In addition, the GUI version code illustrates the following additional aspects of Julia:
* The use of Channels to create a communication between game code and display code in a way that could allow the game to run as a client-server version with little change
* The use of @async to allow co-routine based multitasking with Channels to communicate between co-routines
 
===Objective===
The objective of the game is to find your way to the treasure room, which is located in the upper left of level 5
(against the dungeon level 5 corner at (1, 1, 5)), and then to exit the game by ascending above level 1.
 
===Commands===
 
Direction commands:<pre>north, south, east, west, up, down</pre>Type the first letter of these commands
Line 36 ⟶ 40:
removing inventory drops items on the dungeon floor.
 
Rooms for each level remain only while you are on that level. Levels generate new rooms each time you renterre-enter them.
 
View inventory: Letter i.
 
Current rroom in(of)om informationormation: Letter of.
 
Aliasing: Letter l. Restrictions are that the keys are case insensitive and cannot be extended keys.
 
Help: Letter h.
 
===Code===
<langsyntaxhighlight lang="julia">using Crayons
 
struct Point
Line 566 ⟶ 572:
 
rungame()
</syntaxhighlight>
</lang>
 
==GUI Code==
<syntaxhighlight lang="julia">using Gtk.ShortNames, Colors, Cairo, Graphics
 
#============== GUI CODE ===================#
 
const fontpointsize = 10
const mapwidth = 1000
const mapheight = 500
const logheight = 100
const leveldim = div(mapwidth, fontpointsize)
const windowmaxx = div(mapheight, fontpointsize)
const windowmaxy = leveldim
const basebuffer = fill('=', windowmaxx, windowmaxy)
 
win = Window("Dungeon Game", mapwidth, mapheight + logheight) |> (Frame() |> (vbox = Box(:v)))
set_gtk_property!(vbox, :expand, true)
can = Canvas(div(mapwidth, 3), mapheight)
push!(vbox, can)
textdisplay = ScrolledWindow()
logtxt = TextBuffer()
logtxt.text[String] = "=== ** DUNGEON GAME ** ===\n"
tview = TextView(logtxt)
push!(textdisplay, tview)
push!(vbox, textdisplay)
 
mainchoices = ["N", "S", "E", "W", "Up", "Down", "Inven", "inFo", "Take", "Remove", "eQuip", "Attack", "aLiases", "Help"]
aliases = Dict{String, String}()
directionchoices = ["N", "S", "E", "W", "U", "D"]
yesnochoices = ["Yes", "No"]
yesnoquitchoices = ["Yes", "No", "Quit"]
aliases = Dict{String, String}()
 
struct DisplayUpdate
x::Int # location in basebuffer of data
y::Int # location in basebuffer of data
playerx::Int
playery::Int
data::Matrix{Char}
end
 
inputchan = Channel{String}(1000)
kbinput(w, event) = (push!(inputchan, string(Char(event.keyval))); 1)
inputhid = signal_connect(kbinput, win, "key-press-event")
 
dchan = Channel{DisplayUpdate}(100)
lchan = Channel{String}(20)
mchan = Channel{String}(20)
const playerchar = ['p']
const itemcolors = Dict{Char, Colorant}('p' => colorant"white", ' ' => colorant"black", 'g' => colorant"gold",
's' => colorant"skyblue", 'l' => colorant"green", '_' => colorant"red",
'^' => colorant"blue", '~' => colorant"navy",
'\u2592' => colorant"ivory", '\u2593' => colorant"silver")
 
@guarded draw(can) do widget
ctx = getgc(can)
select_font_face(ctx, "Courier", Cairo.FONT_SLANT_NORMAL, Cairo.FONT_WEIGHT_BOLD)
set_font_size(ctx, fontpointsize)
workcolor = colorant"black"
set_source_rgb(ctx, 0.2, 0.2, 0.2)
rectangle(ctx, 0, 0, mapwidth, mapheight)
fill(ctx)
color = colorant"white"
set_source(ctx, color)
linelen = size(basebuffer)[2]
workbuf = Char[]
for i in 1:size(basebuffer)[1]
move_to(ctx, 0, i * fontpointsize)
lastcharprinted = '\x01'
for j in 1:linelen
ch = basebuffer[i, j]
if j == 1
lastcharprinted = ch
elseif ch != lastcharprinted
show_text(ctx, String(workbuf))
empty!(workbuf)
end
if haskey(itemcolors, ch) && itemcolors[ch] != color
color = itemcolors[ch]
set_source(ctx, color)
end
push!(workbuf, ch)
if j == linelen
show_text(ctx, String(workbuf))
empty!(workbuf)
end
end
end
end
 
function makemenu(strings, chan)
menu = Box(:h)
for s in strings
c = String("" * s[findfirst(isuppercase, s)])
but = Button(s)
onclick(b) = push!(chan, c)
signal_connect(onclick, but, "clicked")
push!(menu, but)
end
menu
end
 
swapforlastinbox(menu, box) = begin oldmenu = box[3]; delete!(box, box[3]); push!(box, menu); Gtk.showall(win); oldmenu end
 
function logwindow(s1="", s2="", s3="", s4="")
set_gtk_property!(logtxt, :text, String(string(s1) * string(s2) * string(s3) * string(s4) *
get_gtk_property(logtxt, :text, String)))
Gtk.showall(win)
end
 
logln(s1="", s2="", s3="", s4="") = logwindow(s1, s2, string(s3) * string(s4), "\n")
 
mainmenu = makemenu(mainchoices, inputchan)
directionmenu = makemenu(directionchoices, inputchan)
yesnomenu = makemenu(yesnochoices, inputchan)
yesnoquitmenu = makemenu(yesnoquitchoices, inputchan)
push!(vbox, mainmenu)
Gtk.showall(win)
 
#============== INPUT INTERFACE BETWEEN GUI AND GAME PLAY VIA CHANNELS =======================#
 
function aliasing(player)
aliaschan = Channel{String}(4)
aliaskeyhit(w, event) = push!(aliaschan, string(Char(event.keyval)))
signal_handler_block(win, inputhid)
hid = signal_connect(aliaskeyhit, win, "key-press-event")
logln("Hit the CURRENTLY DEFAULT key for function:")
oldch = take!(aliaschan)
logln("Hit the TO BE AN ALIAS key for the last key:")
newch = take!(aliaschan)
aliases[newch] = oldch
signal_handler_disconnect(win, hid)
signal_handler_unblock(win, inputhid)
end
 
function queryprompt(menu, options, txt="")
if txt != ""
push!(lchan, txt)
end
oldmenu = swapforlastinbox(menu, vbox)
while true
choice = uppercase(take!(inputchan))
if haskey(aliases, choice)
choice = uppercase(aliases[choice])
end
if choice in options
swapforlastinbox(oldmenu, vbox)
return choice
end
end
end
 
function updatedisplay()
while isopen(dchan)
upd = take!(dchan)
dx, dy = size(upd.data)
for x in 1:dx
for y in 1:dy
basebuffer[upd.x + x - 1, upd.y + y - 1] =
(y == upd.playery && x == upd.playerx) ? playerchar[1] : upd.data[x, y]
end
end
draw(can)
end
end
 
function updatemenu()
while isopen(mchan)
m = take!(mchan)
if haskey(menus, m)
swapforlastinbox(menu, vbox)
end
end
end
 
function updatelog()
while isopen(lchan)
line = take!(lchan)
logln(line)
end
end
 
function startchannelconsumers()
@async(updatedisplay())
@async(updatemenu())
@async(updatelog())
end
 
#===================== GAME PLAY CODE =====================================#
 
struct Point
x::Int
y::Int
z::Int
end
 
const obstacles = (wall = '\u2592', permwall = '\u2593', water = '~')
 
const defaults = (baseluck = 0.5, levelrooms = div(leveldim, 20), maxroomdim = div(leveldim, 5),
wallheight = 3, 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
 
displayroom(player, lit=true) = if lit push!(dchan, DisplayUpdate(1, 1, player.location.p.x,
player.location.p.y, player.location.level.grid)) end
 
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 permwalls(level)
level.grid[1:end, 1] .= obstacles.permwall
level.grid[1:end, end] .= obstacles.permwall
level.grid[1, 1:end] .= obstacles.permwall
level.grid[end, 1:end] .= obstacles.permwall
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, height=defaults.wallheight, map=defaults.map)
grid = fill(obstacles.wall, windowmaxx, windowmaxy)
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
permwalls(level)
level
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
 
help(player=nothing) = push!(lchan,
"n north, s south, w west, e east, d down, u up, i inventory, f roominfo, t take, r drop, q equip, a attack/dig, l aliases")
 
playerdisplayinventory(player) = push!(lchan, "Inventory: $(player.inventory)\nWielding: $(player.wielding)\n")
 
function playerroominfo(player)
txt = ""
if player.location.room == nothing
txt *= "You are between rooms on level $(player.location.level)."
else
if player.location.level.floorlevel == defaults.prizeroomlevel
txt *= "The prize room is on this level!\n"
end
txt *= "You are on level $(player.location.level.floorlevel). You look around the current room.\n"
hasgold, hasladder, haspit, hassledge, hasdiggable = false, false, false, false, false
saytype(t::Gold) = if !hasgold txt *= "There is gold here. "; hasgold = true end
saytype(t::Ladder) = begin txt *= "There is a ladder here. " end
saytype(t::Pit) = begin txt *= "There is a pit here. " end
saytype(t::Sledge) = begin txt *= "There is a sledgehammer here. " end
for (p, v) in player.location.level.content
if inroom(p, player.location.room)
for i in v
saytype(i)
end
end
end
txt *= "\n"
end
push!(lchan, txt)
end
 
function playerequip(player)
for (i, item) in enumerate(player.inventory)
if isequip(item)
yn = queryprompt(yesnoquitmenu, ["Y", "N", "Q"], "Wield $item? (Q to quit menu)")
if yn == "Y"
if iswielding(typeof(item), player)
push!(lchan, "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)
mainmenu = swapforlastinbox(yesnoquitmenu, vbox)
yn = queryprompt(yesnoquitmenu, ["Y", "N", "Q"], "Remove $item? (Q to quit menu)")
if yn == "Y"
push!(player.inventory, item)
deleteat!(player.wielding, i)
elseif yn == "Q"
break
end
end
for (i, item) in enumerate(player.inventory)
yn = queryprompt(yesnoquitmenu, ["Y", "N", "Q"], "Remove $item? (Q to quit menu)")
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
 
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) = (push!(lchan, "Cannot pick that up."); false)
takeit(g::Gold) = begin addgold(g); push!(lchan, "You add $(g.value) in gold.") end
takeit(s::Sledge) = begin push!(player.inventory, s); push!(lchan, "You have a sledge.") end
takeit(s::Ladder) = begin push!(player.inventory, s); push!(lchan, "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)
s = "You exit the game with inventory $(player.inventory), wielding $(player.wielding)."
push!(lchan, s)
println(s)
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
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))
push!(lchan, "You have to leave your ladder behind.")
newlevel(player, -1)
else
push!(lchan, "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
push!(lchan, "You can only move downwards at a Pit in the floor.")
end
end
 
function playerdig(player)
movedirs = Dict("N" => [-1, 0], "W" => [0, -1], "S" => [1, 0], "E" => [0, 1])
if !iswielding(Sledge, player)
push!(lchan, "You need to be wielding a Sledge.")
return
end
direc = queryprompt(directionmenu, ["N", "S", "E", "W", "U", "D"], "Direction?")
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] = '^'
push!(lchan, "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] = '_'
push!(lchan, "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(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
push!(lchan, "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
push!(lchan, "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
push!(lchan, "Something is in the way.")
else
player.location = Location(goalp, player.location.room, player.location.level)
end
end
playerN(player) = playermove(player, -1, 0)
playerW(player) = playermove(player, 0, -1)
playerS(player) = playermove(player, 1, 0)
playerE(player) = playermove(player, 0, 1)
 
function makeplayer(newlevel=1)
level = makelevel(newlevel)
room = rand(level.rooms)
point = randemptypoint(room, level)
player = Agent(Location(point, room, level))
playerroominfo(player)
player
end
 
const usercommands = Dict("N" => playerN, "S" => playerS, "E" => playerE, "W" => playerW,
"U" => playerup, "D" => playerdown, "I" => playerdisplayinventory,
"F" => playerroominfo, "T" => playertake, "R" => playerremove,
"Q" => playerequip, "A" => playerdig, "L" => aliasing, "H" => help)
 
function rungame()
startchannelconsumers()
player = makeplayer()
while true
updateroom(player)
displayroom(player)
choice = queryprompt(mainmenu, keys(usercommands))
usercommands[choice](player)
end
end
 
rungame()</syntaxhighlight>
9,476

edits