Hello Everyone,
It's strange how sometimes something simple can catch your imagination and inspire a burst of creativity. About a month ago while doing my daily browsing of these forums I dropped into the 20-Line Challenge thread. Lo and behold,..
http://forum.thegamecreators.com/?m=forum_view&t=169862&b=11
..an 18 line 'Roguelike' game! I was like "No way!".. well after grabbing the code, compiling it and playing it..
WAY!!!
I was simply amazed at how well this little 20-Liner captured the feel of the old Roguelike games. The dungeon generation was "Most Excellent Dude!" Then a spark ignited my imagination, having a long history with Roguelike games and a Hard Drive stuffed with RPG tiles/graphics I have made over the years I decided to see if I could give this little gem a facelift. I contacted IBOL after 'baiting' him in his own thread..
Quote: "I might make you some custom graphics if you are interested..."
IBOL is a smart cookie, he saw the potential of my offer immediately and we made contact. The first thing he did was to adjust his code to use 'Placeholder' graphics. Within hours (in my spare time) I replaced the floor/wall and player tiles. This set the tone and direction of the rest of the graphics. (An initial 'sneak peek' was posted in his original thread.)
When I get inspired my mind races and thinks of ways to 'Keep it simple' and 'make it good'. Well, IBOL took care of that, so I only had to 'insert eye-candy as required'.. LoL
As you no doubt saw in the newsletter, we managed to get it all done and finalized. Attached is a compiled exe along with the required media. Just unzip it to a folder and you should have a GrailOfTheGods.exe and a folder called gotg-ti\ containing all the media.
As an added bonus IBOL has made sure that the source code is 'Super-Commented' so that both beginners and pros alike can learn how the code runs and what part of the code does what. Reading through the commented code is almost like a tutorial in itself. My hats off to IBOL for doing such a fine job of keeping his code clean and well commented.
see for yourself..
` ########################
` # GRAIL OF THE GODS #
` # DELUXE VERSION #
` # Programmed by #
` # Bob Saunders #
` # (IBOL) #
` # Graphics and Sound #
` # by #
` # David Gervais #
` ########################
` basic setup commands:
set display mode 1024,768,32
set text font "copperplate gothic bold"
sync on
sync rate 0
sync
Hide Mouse
` initial hall of fame handling:
Type High_Scores
Date as string
DOW as string
Diff as string
Score
Depth
Endtype
Dim HS(11) as High_Scores
Read_High_Scores()
` FILL ARRAYS. please note that this game was originally written as a 20-liner (and only used 17),
` BUT for that reason, i could not use 'TYPE / ENDTYPE' because they don't shrink to one line.
` so instead, i used long INT and STRING arrays. it would be much better 'programming' to use TYPEs.
restore
dim m$(14,2) : ` m$() handles monster names, and their 'attack phrase' (i.e. stabs, pokes, breathes fire at, etc.)
for t=1 to 14
read a$,b$
m$(t,1)=a$
m$(t,2)=b$
next t
DIM w$(23) : ` w$() holds sword and armor materials and kinds. it's all just 'flavor text'.
for t=1 to 23
read a$
w$(t)=a$
next t
Dim MC(20,20) : ` MC= Monster Corpse. if the value at particular coordinates is 1, there is a monster corpse there.
` MONSTER CORPSEs get their own array so that you can move thru them and not destroy them,
` which is the way the original game code was written: 1 item per square, and you and monsters are 'items' in this case.
dim COL(10) as DWORD
col(1)=rgb(255,255,255) : `white
col(2)=rgb(255,222,128) : `gold
col(3)=rgb(0,255,0) : `green
col(4)=rgb(255,255,0) : `yellow
col(5)=rgb(255,0,0) : `extreme red
col(6)=rgb(11,11,11) : `almost black
col(7)=rgb(127,255,0) : `some other yellow...
`col(8)=rgb() : `
gosub Load_Media
DO
` this is now the start of a loop that encompasses everything.
` after you win or die, it brings you back here, to start a new game.
set text size 24
` initialize character's attributes:
depth=0
rank=1
XP=0
Gold=0
Wep=10
Arm=10
HP=40
Kills=0
potions=0
Super_Explored=0
MUSIC_ON=1
TEST_MODE=0
` TEST_MODE=1 : ` ` makes you invincible and on depth 7
` TEST_MODE=2 : ` ` makes you dead on a random depth
IF TEST_MODE=1
depth=7
rank=11+rnd(5)
Wep=950
Arm=960
HP=1210
potions=5
kills=rnd(99)
gold=rnd(5000)
ENDIF
IF TEST_MODE=2
depth=1+rnd(10)
rank=1+rnd(14)
Wep=1
Arm=1
HP=1
kills=rnd(33)
gold=rnd(3333)
ENDIF
loop music 99
` TITLE SCREEN
paste image 40,0,0
sync
wait key
cls
` this will wait until you have pressed 1,2,3 on the keyboard, and calculate DIFF from it,
` which is used to determine the depth you will find the grail on, and various other calculations.
repeat
DIFF=scancode()-2
until DIFF>-1 and DIFF<3
play sound 10
` our nice screen sliding effect:
Slide_Image(40,0,768,8)
Slide_Image(45,768,0,-8)
` this is the beginning of each level:
come_here:
inc depth
Key=0
Key$=""
XPXP=0 : ` Explore_XP ` when you explore a whole level, you get XP bonus, and this flag is set to 1.
` I use fail (below) very often as a counter for how many times a loop has been run.
` if it reaches some number, we exit the loop.
` this happens whenever some desirable condition can not be met,
` and this way, we don't get stuck in an annoying infinite loop.
fail=0
dim m(22,22) : ` M() is map. it contains numbers relating to what is found at that x,y location
dim n(22,22) : ` N() is Known. whether or not you've explored that area, and know what's there.
for x=1 to 20
for y=1 to 20
n(x,y)=0 : ` this sets the entire maze to "unknown"
MC(x,y)=0 : ` this gets rid of old monster corpses.
` clear the maze and put a wall around the outside:
if x=1 or x=20 or y=1 or y=20
m(x,y)=1
else
m(x,y)=0
endif
next y
next x
` MAZE GEN CODE:
repeat
repeat
` OK, so the big secret here: what is the shortest way I can think of to generate a random, solvable maze?
inc fail
` select a random square within the maze:
x=rnd(19)+2
y=rnd(19)+2
p=0 : ` P is POWER ; power of 2 to be used in 'Q' calculation.
Q=0 : ` Q is the numerical representation of which squares are walls and which squares are floors around our selected square.
` now, we use a loop to look at the area directly surrounding our square .
for t=x-1 to x+1
for u=y-1 to y+1
` you see that each iteration of the loop, if the square we're looking at is a wall, we add a value to Q
if m(t,u)=1
` this value is a power of two, based on how many iterations the for/next loops have run (because P is inc'd each time)
inc Q,(2^p)
endif
inc p : ` P is inc'd regardless of whether there's a wall or not.
next u
next t
` so now, we have generated a Q value from a binary series ( 1,2,4,8,16,32,64,128,256 )
` since any number generated from this series can only be made from a unique combination of the numbers, ( 77 = 64+8+4+1 , always. there is no other way)
` each Q value represents a unique arrangement of walls and empty floors.
` since only certain combinations are desirable as a place to add a wall, we can selectively add walls.
` i had to work out on paper which Q values were valid. here is the Q score in a grid.
` 1 8 64 - assume the middle square is empty, and we are JUST looking across the top ( 1 8 64 )
` 2 16 128 - we want to avoid any diagonal wall placement, so legal = 8, 9,72,73 (8, 1+8, 8+64, 1+8+64 )
` 4 32 256 - Illegal would be 1,64,65 (the two outer edges alone, and them together without the middle square)
` so now, we look at all the other edges: across the bottom, and down the left and right edges.
` since there are fewer Legal cases in total (up to 8 squares might be filled), we just figure all legal cases,
` and check for them in the following very long OR OR OR statement.
if Q=2 or Q=3 or Q=6 or Q=7 or Q=8 or Q=9 or Q=32 or Q=36 or Q=72 or Q=73 or Q=128 or Q=192 or Q=288 or Q=292 or Q=384 or Q=448
m(x,y)=1
fail=0
` we have found a good case, and so the selected square becomes a wall.
` also set fail back to 0, because we had a successful placement, and we want to look for more.
EXIT
endif
until fail>500
until fail>500
` eventually, we won't be able to find an appropriate square, even in 500 tries.
` at that point the loop will exit, and the maze will be considered done.
` we could use a higher fail number, but i like the way the mazes are sometimes a little more empty than others.
` NOW, populate the maze.
` start by putting the PLAYER somewhere along the left wall:
repeat
y=rnd(18)+2
until m(2,y)=0
m(2,y)=2
px=2
py=y
` now place the "goal"; that's either the exit to the next level, or the GRAIL if you're on the deepest level.
repeat
y=rnd(18)+2
until m(19,y)=0
` this either places the EXIT from this level, or the GRAIL, depending on your current DEPTH:
IF depth<(8+(2*DIFF)) : ` this says, EASY=level 8, normal=depth 10, hard=depth 12
m(19,y)=3
ELSE
m(19,y)=10
ENDIF
` store this for later use:
GoalX=19
GoalY=Y
if depth>4 then GOSUB Door_And_Key
` NOW place all the items. monsters,traps,gold,armor,sword,potion,etc.
` ww="walls wanted", "times" is how many of the object, and "item" is its code number, 4=gold, 5 for monster,6=armor, etc.
ww=4
Times=7+(depth/2) : ` the deeper you go, the more monsters there are!
item=5
gosub place
` the subroutine "PLACE" works by finding an empty x,y space.
` then, it counts the number of WALLs surrounding that space.
` if the number of walls=ww, then the object is placed there.
` otherwise, keep looking.
` this way, we can place TREASURE at dead-ends (ww=7), making it so that you have to *search* for it,
` and you can place MONSTERS in open areas (ww=4) , so they get in your way more often.
ww=2
Times=(depth+3) mod 3 : ` these are a different number of traps on each level.
item=8
gosub place
ww=7 : ` ww=7 for all of the following 'treasure' items:
Times=(depth mod 3)+2 : ` GOLD. there some levels have more gold.
item=4
gosub place
` all levels get one sword, one armor, and one potion
Times=1
item=6
gosub place
item=7
gosub place
item=9
gosub place
` this is a special case that comes on the level before the end:
IF depth=(7+(diff*2))
` place the easter egg item
item=13
gosub place
ENDIF
` dungeon generation for this level is now complete.
` this is the beginning of the main game action loop:
do
gosub draw
sync
wait 150
` this will now calculate the percentage of this level that you have EXPLORED
EXPLORED=0
for xx=1 to 20
for yy=1 to 20
if n(xx,yy)>0
inc EXPLORED
endif
next yy
next xx
` here, you are given an XP bonus if you explore the entire level. the map is 20x20. 20*20=400.
` xpxp is a flag that keeps you from geting the Explore XP more than once per level.
if EXPLORED>399
if XPXP=0
play sound 5
gosub draw
text 60,724,"You explored the entire level! +"+str$(depth*3)+" XP"
inc xp,depth*3
sync
wait 1000
XPXP=1
endif
endif
` this turns the music on if the player wants it that way.
if music looping(99)=0
if MUSIC_ON=1
loop music 99
endif
endif
` this waits for a key to be pressed, then we find out below if it was a valid command.
repeat
until scancode()
` this allows you to toggle music on and off with [M] key
if keystate(50)
if MUSIC_ON=1
MUSIC_ON=0
stop music 99
text 60,724,"You turned the music off"
sync
wait 500
else
MUSIC_ON=1
text 60,724,"You turned the music on"
sync
wait 500
endif
endif
` set text color to white:
ink col(1),0
` first, before processing any action, we find out if you are near a monster,
` and if so, you must FIGHT TO THE DEATH!
` px,py is the players position. px-1 to px+1 , py-1 to py+1 look at every square directly around the player.
for xx=px-1 to px+1
for yy=py-1 to py+1
if m(xx,yy)=5 : ` 5 is our map code for a MONSTER!
mt=(xx*yy) mod 3 + depth : ` mt=monster type. this can be 1-3 on depth 1, 2-4 on depth 2, 10-12 on depth 10, etc.
h=mt*(7+diff) : `H is monster health. monsters have more HP on higher difficulties.
repeat
d=rnd(wep)+rank
` d is the amount of damage your attack does. it can never be higher than the monster's total health.
if d>h
d=h
endif
dec wep,(d/7) : ` every 7 points of damage your sword does, it is degraded by 1
` there is a limit to how much your weapon CAN be degraded:
if wep<5+(RANK*1.5)
wep=5+(RANK*1.5)
endif
dec H,d : ` do the damage to the monster
gosub draw
text 60,724,"You hit the "+m$(mt,1)+" for "+str$(d)+" damage"
play sound (rnd(3)+1) : ` we have 4 combat sounds, so play one at random.
paste image 15,xx*32,yy*32,1
sync
wait 900-(depth*50) : ` this decreases the waittimes on the text messages,
` assuming that the player wants to get down to business, the deeper they get.
` many factors go into monster damage.
` what kind of monster is most important (mt), also the depth, and the difficulty (DIFF)
d=rnd(mt*2)+(depth/2)+mt+rnd(DIFF*5)+1
` this puts a monster damage cap on the very first dungeon level:
if depth=1 AND d>((DIFF+1)*3) then d=d/2
` armor absorbs 40% of damage
if arm>0
dr=(d/2.5) : ` this is Damage Reduction, and is subtracted from the actual damage
dec d,dr
dec arm,DR : ` now your armor is degraded by whatever damage it absorbed.
if arm<1
arm=0
endif
endif
dec HP,d
gosub draw
text 60,724,"The "+m$(mt,1)+m$(mt,2)+" you for "+str$(d)+" damage"
play sound (rnd(3)+1)
paste image 15,px*32,py*32,1
sync
wait 900-(depth*50)
` combat goes back and forth until either you or the monster dies.
until h<1 or HP<1
if HP<1
` this means that you died.
MKU$=m$(mt,1)
goto Game_Over
else
` you killed the monster.
m(xx,yy)=0 : ` take out the monster first, then draw the screen with the KILLED text.
MC(xx,yy)=1 : ` this marks a 'monster corpse' at this location.
inc Kills
inc XP,((mt/3)+1) : ` more powerful monsters give more XP for killing them.
gosub draw
text 60,724,"You killed the "+m$(mt,1)
sync
wait 750
endif
endif
next yy
next xx
` this is the END of the COMBAT loop.
` here is the new "potion drinking' functionality:
if keystate(16) : ` you hit the 'Q' key, for Quaff (drink)
if potions>0
if HP<(15+rank*25)
HP=15+rank*25
dec potions
gosub draw
play sound 5
text 60,724,"Your health is magically restored!"
sync
wait 1000
else
text 60,724,"You are already at full health."
play sound 8
sync
wait 750
endif
else
text 60,724,"You don't have any potions!"
play sound 8
sync
wait 750
endif
endif
` THIS checks for MOVEMENT:
` arrowkeys:
dx=(rightkey()-leftkey())
dy=(downkey()-upkey())
` and at the last moment, WASD handling:
if dx=0 and dy=0
dx=(keystate(32)-keystate(30))
dy=(keystate(31)-keystate(17))
endif
` V in this case is the map code of the square that you would theoretically move in to. we will check it.
V=m(px+dx,py+dy)
a$=""
if v=3
` you found the exit.
` now is the time to add how much you've explored this level, to your exploration total,
` which is a factor in game score at the end.
inc Super_Explored,EXPLORED
play sound 10
` this moves the player's image to the exit, right before you go down:
m(px,py)=0
m(px+dx,py+dy)=2
gosub draw
text 60,724,"...Descending..."
sync
wait 750
EXIT : ` this exits from the main game loop, eventually taking you back to "come_here"
endif
if v=4
` this is GOLD. the value of treasure is dependent completely on depth. you also get 1 XP for each treasure you pick up.
play sound 7
inc gold,(depth*5*depth)
v=0 : ` below, if v=0, you will actually move into this space.
inc XP
a$="You found "+str$(depth*5*depth)+" gold"
` A$ is used for any pick-up message that you might get, and is displayed later.
endif
if v=6
` here, you found a piece of armor. it inc's your armor stat.
play sound 7
inc arm,10+(depth*2) + DIFF : ` +DIFF*3
v=0
a$="You found "+w$(11+Rnd(4))+w$(16+Rnd(4))
endif
if v=7
` you found a new sword.
play sound 7
inc wep,10+(depth*3) + DIFF : ` +(DIFF*3)
v=0
a$="You found a "+w$(Rnd(4)+1)+w$(Rnd(4)+6)
endif
if v=8 : ` TRAP!
` ouch, you triggered a trap. traps do about 11% of your current HP in damage.
` this damage is also more the deeper you go, and a little more for higher difficulties.
play sound 8
D=(hp/9)+1+depth+rnd(diff*5)
dec HP,d
if HP<5
HP=5
endif
v=0
a$="You took "+str$(d)+" damage from a trap"
endif
if v=9 : ` HEALING POTION
play sound 7
` NEW: adds to your total POTIONS. drink later with Q
inc potions
v=0
a$="You found a healing potion"
endif
if v=11
` door. 2 cases: whether or not you have the KEY.
if key>0
v=0
a$="The "+key$+" unlocks the door"
key=0
play sound 6
else
play sound 8
a$="The locked door blocks your path"
endif
endif
if v=12
` key . this gives you the key to the door on this level
play sound 7
key=1
key$=w$(Rnd(4)+1)+" Key"
a$="You found a "+key$
v=0
endif
if v=13
` you found the special easter egg item
play sound 11
` this gives you some game perks for finding this elusive pick-up:
HP=15+rank*25
if arm<20 then arm=20
if wep<50 then wep=50
gosub draw
paste image 46,192,144,1
text 60,724,"...ooh..."
sync
wait 1000
wait key
a$="...Knowledge Has Blessed You..."
v=0
endif
if v=10
` YOU FOUND THE GRAIL!
play sound 9
inc Super_Explored,EXPLORED
goto Game_Over
endif
if v=0
` this is the case where you move into an unocupied square.
m(px,py)=0
m(px+dx,py+dy)=2
px=px+dx
py=py+dy
endif
` this displays the message of whatever you encountered, or nothing if nothing:
if a$<>""
gosub draw
ink col(7),0
text 60,724,a$
sync
wait 1000-(depth*50) : ` after a while, you know what things are.
endif
` this is how you LEVEL UP:
if XP>=(rank*3)
dec XP,(rank*3)
inc rank
inc HP,50 : ` heal a little bit.
if HP>(15+rank*25)
HP=15+rank*25
endif
` you also get an attack and defense bonus when you level up. higher level characters get more.
inc wep,10+rank + diff
inc arm,10+rank
gosub draw
ink col(7),0
text 60,724,"You gained a LEVEL!"
play sound 5
sync
wait 1000
endif
LOOP : ` this is the end of the game-action loop.
wait 250
EXPLORED=0
goto come_here
` and that is the end of the BIG game loop.
` everything else is the subroutines and functions.
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
` "Place" has been explained above, where it is called.
Place:
for G=1 to Times
fail=0
repeat
repeat
x=rnd(18)+2
y=rnd(18)+2
until m(x,y)=0
inc fail
` this counts the number of WALLS surrounding a particular square:
w=0
NNG=0
for t=x-1 to x+1
for u=y-1 to y+1
if m(t,u)=1
inc w
endif
` new condition: items can NOT go right next to EXIT or GRAIL. NNG is Not Near Goal.
if m(t,u)=3 or m(t,u)=10
NNG=1
endif
next u
next t
` ww=0 also allows ANYTHING to be placed in a space with NO walls around it, but that doesn't really happen much.
until ( (w=ww or ww=0) AND NNG=0 ) or fail>2500
` as long as we didn't "fail out" of the loop, we can call this a victory, and place the item:
if fail<2500
m(x,y)=ITEM
endif
next G
Return
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
` this subroutine draws the play area and prints all your information.
DRAW:
cls
paste image 45,0,0 : ` this is the background image.
for x=1 to 20
for y=1 to 20
if D2D(x,y,px,py)<4 OR n(x,y)=1
v=m(x,y)
n(x,y)=1
` draw floor and walls first,
` so that anything transparent will show up ON the floor, instead of in empty space.
if v=0 or v>1
` floor
paste image 1,x*32,y*32,1
else
` wall
paste image 2,x*32,y*32,1
endif
IF V=5
` if it's a monster, we CALCULATE which monster it is PROCEDURALLY,
` using the following equation:
v=((x*y) mod 3)+depth
paste image v+20,x*32,y*32,1
ELSE
if v=8 or v=13
` this is either a trap or a special. you must be CLOSE to see this.
if D2D(x,y,px,py)<2
paste image v+1,x*32,y*32,1
endif
else
` everything but monsters, traps, and specials. DRAW IT!
paste image v+1,x*32,y*32,1
endif
if v=0 : ` see if there's a MONSTER CORPSE here
` if this is an empty square, check to see if there's a dead monster here, and if so , draw it.
if MC(x,y)=1
paste image 6,x*32,y*32,1
endif
endif
ENDIF
endif
next y
next x
` this is done out of turn to show how dead you are.
a=(hp*255) / ( (15+rank*25) )
ink RGB(255,a,a),0
text 740,270,"Health: "+str$(hp)
ink col(1),0
text 740,30,"Depth: "
text 740,60,"Level: "
text 740,90,"XP: "
text 740,120,"Gold: "
` health used to go here.
text 740,210,"Sword: "
text 740,240,"Armor: "
text 740,300,"Potions: "
text 740,480," Explored : "
text 740,560," Difficulty : "
` -------------------VALUES----------------
ink col(2),0
text 826,30,str$(depth)
text 822,60,str$(rank)
text 784,90,Str$(xp)+" / "+str$(rank*3)
text 812,120,str$(gold)
text 834,210,str$(wep)
text 834,240,str$(arm)
text 850,300,str$(potions)
text 882,480,str$((EXPLORED*100)/400)+" %"
if key>0
text 740,390,"A "+key$
endif
ink col(3+diff),0
text 890,560,w$(21+DIFF)
ink col(1),0
` this is some old DEBUG TEXT. i was coming up with playthroughs with 132% explored.
` i wrote these variables to the screen to see how they behaved.
` i discovered that i was not clearing "super_explored",
` and successive games added the previous games' exploration.
` text 312,300,str$(Explored)
` text 312,320,str$(Super_explored)
` text 312,340,str$(Dtot)
` text 312,360,str$(((super_explored*100)/((8+(DIFF*2))*400)))
return
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
Door_And_Key:
` this will try to place a LOCKED DOOR in your path.
` it will also then try to place a key.
` it should be impossible to get to the exit without the key,
` but the key must always be theoretically get-able (not behind the door :) )
SXS=0 : ` sxs = Success
Superfail=0
` superfail will be used to try 25 times to place a door/key combination. a sxs=1 will get us out.
REPEAT
` we will try 50 times to place a DOOR
fail=0
DoorX=0
DoorY=0
repeat
x=rnd(7)+9
y=rnd(14)+3
inc fail
if m(x,y)=0
if ( m(x+1,y)=1 AND m(x-1,y)=1 ) OR ( m(x,y+1)=1 AND m(x,y-1)=1 )
DoorX=X
DoorY=y
endif
endif
until DoorX>0 OR Fail>50
if doorX>0
` we found a place to put a door, now see if it blocks the exit
GOSUB FILL_ROUTINE
if fill(goalX,goalY)=0
` this checks to see that the EXIT can NOT be reached from the players starting position,
` because goalX and goal Y are not "filled" by the above loops,
` and this is what we want, because it means the DOOR blocks the EXIT.
` now just place the key
fail=0
repeat
inc fail
x=rnd(12)+2
y=2+(17*rnd(1)) : ` yields 2 or 19, the outer edges.
until ( x<doorX AND m(x,y)=0 AND fill(x,y)>(round-4) AND fill(x,y)>0 ) or fail>250
if m(x,y)=0
` once we found a place for the key, we can finalize everything.
` this way, if we run the loop again, we don't already have a door sitting around.
KeyX=x
KeyY=y
GOSUB FILL_ROUTINE
if fill(keyX,keyY)>0 and fill(GoalX,GoalY)=0
` a final check to see that everything is right
M(DoorX,DoorY)=11
m(keyX,keyY)=12
SXS=1
endif
endif
endif : ` if fill(goalX,goalY)=0 endif
endif : ` if doorX>0 endif
inc superfail
Until SXS=1 or superfail>25
Return
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
FILL_ROUTINE:
` fill() will be used to see if the character can get to the exit from his starting position
Dim Fill(21,21)
for x=0 to 21
for y=0 to 21
fill(x,y)=0
next y
next x
` px and py are the player's coordinates. start there, marking it as 'filled' >0
` then fill all non-walls next to other filled non-walls. repeat. don't go thru the door.
` when no new fills are made in a pass, we exit the loop via done=1.
fill(px,py)=1
Round=0
repeat
inc round
done=1
for x=1 to 20
for y=1 to 20
if fill(x,y)>0
for t=x-1 to x+1
for u=y-1 to y+1
if fill(t,u)=0
if m(t,u)<>1 and (t<>doorX or u<>doorY)
fill(t,u)=Round
done=0
endif
endif
next u
next t
endif
next y
next x
until done=1
` 'Round' is now used, to count how many ROUNDS of filling we do before it tapers out.
` the idea being that the last square filled should be the furthest (most convoluted path) from the player.
Return
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
Load_Media:
` this loads most of the images used by the game.
` I have a tendency to name my media as numbers, for easy loading.
` so, we create the file name based on a loop,
` and it tries to load .png files.
` if that is not found, we try to load a .bmp of the same name.
` if nothing is found, the loop continues, we get no errors, and stuff gets loaded.
for t=1 to 50
a$="gotg-ti/"+str$(t)+".png"
IF file exist(a$)
load image a$,t
ELSE
a$="gotg-ti/"+str$(t)+".bmp"
if file exist(a$)
load image a$,t
endif
ENDIF
` sounds are loaded in a similar way.
a$="gotg-ti/S ("+str$(t)+").wav"
if file exist(a$)
load sound a$,t
endif
next t
` late additions and music get their owned named files.
load sound "gotg-ti/special.wav",11
load music "gotg-ti/YouWin.MID",97
load music "gotg-ti/Finalet.MID",98
load music "gotg-ti/BGLOOP1.MID",99
return
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
Game_Over:
gosub DRAW
gotg$="Grail Of The Gods"
` depending on whether you won or died, this sets up various text considerations:
if HP<1
A$="You were killed by a "
B$="Posthumous Honors :"
DOW$="Killed by a "+mku$
win=0
paste image 48,192,144,1
else
a$="You Found The"
b$="Heroic Achievements :"
DOW$="Found The Grail!"
win=1
paste image 47,192,144,1
endif
` this calculates the total % of all the levels you've explored:
Dtot=(8+(DIFF*2))
Dtot=Dtot*400
if dtot<1 then dtot=3200
a=(super_explored*100)/Dtot
` calculate score
score=depth*1111
inc score,rank*555
inc score,kills*77
inc score,gold*3
inc score,a*66
if win=1
score=score/49
else
score=score/199
endif
score=score*10
stop music 99
set text size 18
` the win and lose screens are different, with different colors and words, so there is a lot of specific treatment here.
if win=1
ink col(1),0
else
ink col(4),0
endif
CENTER TEXT 620,200,A$
if win=0
ink col(5),0
set text size 24
CENTER TEXT 620,230,mku$
set text size 18
else
ink col(1),0
CENTER TEXT 620,200,a$
set text size 30
ink col(4),0
CENTER TEXT 620,220,gotg$
set text size 18
endif
ink col(1),0
text 470,270,B$
if win=1
ink col(6),0
else
ink col(2),0
endif
TEXT 470,300,"You Descended to Level"
TEXT 470,325,"You Gained Experience Level"
TEXT 470,350,"You Defeated Monsters"
TEXT 470,375,"You Collected Gold"
TEXT 470,400,"You Explored %"
if win=1
ink col(5),0
else
ink col(1),0
endif
TEXT 694,300,str$(depth)
TEXT 740,325,str$(rank)
center TEXT 616,350,str$(kills)
center TEXT 638,375,str$(gold)
center TEXT 618,400,str$(a)
set text size 24
ink col(1),0
TEXT 540,500,"Your Final Score:"
ink col(2),0
center text 650,530,str$(score)
sync
wait 1200
if win=1
loop music 98
else
loop music 97
endif
WAIT KEY
` NOW, we are going to the new Hall Of Fame screen.
` grab the screen image for that special touch:
Grab_Screen()
cls
Slide_Image(99,0,768,8)
` we disable/enable here for the split second it takes to mess with the high score list,
` so we don't accidentally delete it without re-saving it.
disable escapekey
D$=get date$()
Sort_High_Scores(score,diff,dow$,D$,depth)
Save_High_Scores()
enable escapekey
` grabbing it *without* sync allows us to slide it nicely.
Display_High_Scores()
Grab_Screen()
cls
Slide_Image(99,768,0,-8)
Display_High_Scores()
sync
wait key
Grab_Screen()
if win=1
STOP music 98
else
STOP music 97
endif
cls
` more David Gervais SFX code:
` (we grabbed the screen image, and scolled it out, then scrolled in the beginning game screen)
Slide_Image(99,0,768,8)
Slide_Image(40,768,0,-8)
LOOP : ` this sends you back to the INTRO screen. used to be a GOTO , but i know how people hate them.
end
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
Function Slide_Image(Img,Sta,Sto,Ste)
` Image, Start, Stop, Step
` this function is based on code written by David Gervais,
` all i did was turn it in to a moreversatile function.
` this was his idea, and i think it's great.
` it allows us to slide an image up or down the screen,
` giving those beautiful transitions.
for dg=sta to sto step ste
paste image img,0,dg
sync
wait 5
cls
next dg
Endfunction
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
Function Grab_Screen()
` this gets image 99 as the current screen, so we can paste it and move it on or off, with the slide_image() function.
if image exist(99)
delete image 99
endif
get image 99,0,0,1024,768,3
Endfunction
` #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
Function D2D(x,y,a,b)
` Distance in 2D function.
D# = sqrt( (x-a)*(x-a) + (y-b)*(y-b) )
Endfunction D#
` storing stuff in data statements is a great way to save space in a 20 liner.
DATA "Rat "," Bites","Snake "," Bites","Goblin "," Pokes","Zombie "," Touches","Orc "," Stabs","Cockatrice "," Claws at","Troll "," Knocks","Will-O-Wisp "," Shocks"
data "Manticore "," Lunges at","Elemental "," Burns","Beholder "," Bites","Dragon "," Breathes Fire at","Umber Hulk"," Crushes","Archdemon"," Curses"
DATA "Rusty ","Bronze ","Steel ","Silver ","Magic ","Dagger","Short Sword","Broadsword","Longsword","Claymore"
data "Cloth","Leather","Chain","Scale","Plate"," Boots"," Gloves"," Greaves"," Helm"," Cuirass","Easy","Medium","Hard"
` HIGH SCORES:
Function Generate_High_Scores()
` this will generate a high score list if none exists.
if file exist("GOTG.HoF")=0 : ` my paranoia checks it twice ;)
open to write 1,"GOTG.HoF"
for t=1 to 10
write string 1," "
write string 1," "
write string 1," "
write long 1,0
write long 1,0
next t
Close File 1
endif
Endfunction
Function Read_High_Scores()
` read the high score file right into the proper array.
if file exist("GOTG.HoF")=0
Generate_High_Scores()
endif
open to read 1,"GOTG.HoF"
for t=1 to 10
Read string 1,HS(T).Date
Read string 1,HS(T).DIFF
Read string 1,HS(T).DOW
Read long 1,HS(T).score
Read long 1,HS(T).depth
next t
close file 1
Endfunction
Function Display_High_Scores()
` there is a lot of specific coloring and arrangement of text here.
Read_High_Scores()
paste image 49,192,144,1
SET TEXT SIZE 12
for t=1 to 10
IF hs(t).score>0
ink col(1),0 : text 270,250+T*28,hs(t).date
set text size 16
ink col(2),0 : center text 374,250+T*28,str$(hs(t).score)
SET TEXT SIZE 12
` e,m,h
a$=upper$(left$(hs(t).diff,1))
ink col(3),0
if a$="M" then ink col(4),0
if a$="H" then ink col(5),0
center text 440,250+T*28,hs(t).diff
if upper$(left$(hs(t).dow,1))="K"
ink col(5),0
else
ink col(4),0
endif
center text 560,250+T*28,hs(t).dow
ink col(1),0 : center text 696,250+T*28,"At Depth "+str$(hs(t).depth)
ENDIF
next t
Endfunction
Function Sort_High_Scores(score,diff,dow$,date$,depth)
dim s(11) as High_Scores
` import your current score as High Score 0, so we can compare it to the others.
hs(0).score=score
hs(0).diff=w$(21+DIFF)
hs(0).dow=dow$
hs(0).date=date$
hs(0).depth=depth
` find the highest #. store it in a temporary place.
` zero it out.
` find the next.
num=0
repeat
sxs=-99
score=0
` find the highest score in the current list.
for t=0 to 10
if HS(t).score>score
score=hs(t).score
SXS=t
endif
next t
` once we have find a high score, we put it into our temporary list,
` as the next item.
` this search method is destructive; it destroys the old data to create the new list.
if score>0 and SXS>-99
` num starts at 0 at incs each time we find a valid high score.
inc num
s(num).date=HS(sxs).Date
s(num).diff=HS(sxs).DIFF
s(num).dow=HS(sxs).DOW
s(num).score=HS(sxs).score
s(num).depth=HS(sxs).depth
` destroy: this is done so we don't read the same highest score 10 times:
HS(sxs).score=0
endif
until sxs=-99 or num>9
` we now have an ordered list stored in s(). transfer it back to HS()
for t=1 to 10
HS(t).Date=s(t).date
HS(t).DIFF=s(t).diff
HS(t).DOW=s(t).dow
HS(t).score=s(t).score
HS(t).depth=s(t).depth
next t
close file 1
Endfunction
Function Save_High_Scores()
` open a file and write in the data.
if file exist("GOTG.HoF") : ` my paranoia checks it twice ;)
delete file "GOTG.HoF"
endif
open to write 1,"GOTG.HoF"
for t=1 to 10
write string 1,hs(t).date
write string 1,hs(t).diff
write string 1,hs(t).DOW
write long 1,hs(t).score
write long 1,hs(t).depth
next t
Close File 1
Endfunction
I'd like to thank IBOL for letting me have some fun with his little Roguelike. It was a pleasure to work with him over the course of the last month and I hope that this little upgrade will help to show that 'simple' does not mean 'bad', and that perhaps others might take some inspiration from this shining example of how a simple 20-liner game can be taken to the next step.
As an artist who often gets frustrated in my lack of programming skills, I know that many programmers feel the same way where their art skills are concerned. With that in mind, I'd like to encourage all you programmers to use 'Placeholder Graphics' when developing your games. It is the easiest way to 'lure' an artist like myself out of the woodwork. I for one like the idea of 'customizing' or 'upgrading' placeholder graphics. I can't promise that I'll upgrade every project that uses placeholder graphics, but if you use them, you never know who might take an interest in helping.
Cheers!
P.S. o..m..g IBOL caught my eye again.. a Space RTS in 20 lines..
http://forum.thegamecreators.com/?m=forum_view&t=170425&b=11
hmmm I wonder...