RCRPG/Julia: Difference between revisions
m (→Commands) |
mNo edit summary |
||
Line 9:
* built-in data structures including named tuples, vectors, matrices, and Dicts (hashes)
* functional programming with map and other functions using anonymous functions on arrays
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 ==
Line 562 ⟶ 566:
while true
runuser(player)
end
end
rungame()
</lang>
==GUI Code==
<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", "Help"]
directionchoices = ["N", "S", "E", "W", "U", "D"]
yesnochoices = ["Yes", "No"]
yesnoquitchoices = ["Yes", "No", "Quit"]
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)
signal_connect(win, "key-press-event") do widget, event
println("hit key $(String(event.keyval))")
push!(inputchan, String(event.keyval))
end
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 queryprompt(menu, options, txt="")
if txt != ""
push!(lchan, txt)
end
oldmenu = swapforlastinbox(menu, vbox)
while true
choice = uppercase(take!(inputchan))
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")
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, "H" => help)
function rungame()
startchannelconsumers()
player = makeplayer()
while true
updateroom(player)
displayroom(player)
choice = queryprompt(mainmenu, keys(usercommands))
usercommands[choice](player)
end
end
|
Revision as of 20:20, 16 February 2019
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:
- multiple function dispatch used to select function behavior by data argument type
- both dynamic and optionally strongly typed data types
- built-in data structures including named tuples, vectors, matrices, and Dicts (hashes)
- functional programming with map and other functions using anonymous functions on arrays
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:
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 items on the dungeon floor.
Rooms for each 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>
GUI Code
<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", "Help"] directionchoices = ["N", "S", "E", "W", "U", "D"] yesnochoices = ["Yes", "No"] yesnoquitchoices = ["Yes", "No", "Quit"]
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)
signal_connect(win, "key-press-event") do widget, event
println("hit key $(String(event.keyval))") push!(inputchan, String(event.keyval))
end 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 queryprompt(menu, options, txt="")
if txt != "" push!(lchan, txt) end oldmenu = swapforlastinbox(menu, vbox) while true choice = uppercase(take!(inputchan)) 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")
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, "H" => help)
function rungame()
startchannelconsumers() player = makeplayer() while true updateroom(player) displayroom(player) choice = queryprompt(mainmenu, keys(usercommands)) usercommands[choice](player) end
end
rungame() </lang>