Author Topic: Rogue-Like (work in progress)  (Read 10115 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Rogue-Like (work in progress)
« on: September 07, 2019, 09:26:57 am »
Having a little free time once more, I decided to play around with one of those old game concepts from ages lost, and toss my hat into the ring with trying to create a "Rogue-like" game.  (You can watch this poor fellow try out the game for the first time in a video here:
OR, you can have fun and test out this timeless classic yourself with a modern emulator here: https://www.myabandonware.com/game/rogue-4n/play-4n )

So, here's my little start to the game, which generates us a rogue-like map and lets us roam our hero around it and explore:
Code: QB64: [Select]
  1. TYPE Light_Type
  2.     Name AS STRING * 20
  3.     Reach AS INTEGER
  4.     Left AS INTEGER
  5.  
  6. TYPE Hero_Type
  7.     X AS INTEGER
  8.     Y AS INTEGER
  9.     HP AS INTEGER
  10.     Level AS INTEGER
  11.     EXP_Earned AS LONG
  12.     EXP_Needed AS LONG
  13.     Light AS Light_Type
  14.  
  15. DIM SHARED Hero AS Hero_Type
  16. DIM SHARED Level AS INTEGER: Level = 1
  17.  
  18.  
  19. SCREEN _NEWIMAGE(800, 600, 32)
  20. REDIM SHARED MapArray(0, 0) AS _BYTE
  21.  
  22. Init
  23. CreateMap 99, 74, 10, 1
  24.     CLS
  25.     DrawMap
  26.     _DISPLAY
  27.     GetInput
  28.  
  29. SUB Init
  30.     Hero.HP = 10
  31.     Hero.Level = 1
  32.     Hero.EXP_Earned = 0
  33.     Hero.EXP_Needed = 1000
  34.     Hero.Light.Name = "Magic Candle"
  35.     Hero.Light.Reach = 2
  36.     Hero.Light.Left = -1 'infinite
  37.  
  38. SUB GetInput
  39.     DO
  40.         k = _KEYHIT
  41.         SELECT CASE k
  42.             CASE 18432 'up
  43.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  44.                     IF MapArray(Hero.X, Hero.Y - 1) AND (2 OR 4) THEN 'and it's a room or passageway
  45.                         Hero.Y = Hero.Y - 1
  46.                     END IF
  47.                 END IF
  48.             CASE 19200 'left
  49.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  50.                     IF MapArray(Hero.X - 1, Hero.Y) AND (2 OR 4) THEN 'and it's a room or passageway
  51.                         Hero.X = Hero.X - 1
  52.                     END IF
  53.                 END IF
  54.             CASE 20480 'down
  55.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  56.                     IF MapArray(Hero.X, Hero.Y + 1) AND (2 OR 4) THEN 'and it's a room or passageway
  57.                         Hero.Y = Hero.Y + 1
  58.                     END IF
  59.                 END IF
  60.             CASE 19712 'right
  61.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  62.                     IF MapArray(Hero.X + 1, Hero.Y) AND (2 OR 4) THEN 'and it's a room or passageway
  63.                         Hero.X = Hero.X + 1
  64.                     END IF
  65.                 END IF
  66.             CASE 32 'space to just wait and skip a turn
  67.             CASE 60 ' "<" key
  68.                 Level = Level + 1
  69.                 IF MapArray(Hero.X, Hero.Y) AND 8 THEN CreateMap 99, 74, 10, Level
  70.             CASE ASC("+"), ASC("=")
  71.                 IF Hero.Light.Reach < 5 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  72.             CASE ASC("-"), ASC("_")
  73.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  74.         END SELECT
  75.         _LIMIT 60
  76.     LOOP UNTIL k
  77.     _KEYCLEAR 'one keystroke at a time
  78.  
  79. SUB Illuminate (TX, TY, Range)
  80.     X = TX: Y = TY
  81.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  82.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  83.     IF X >= XL AND X <= XH AND Y >= YL AND Y <= YH THEN
  84.         IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 16 'illuminate the room
  85.         IF Range > 0 THEN
  86.             IF X > XL THEN IF MapArray(X - 1, Y) <> 0 THEN Illuminate X - 1, Y, Range - 1
  87.             IF X < XH THEN IF MapArray(X + 1, Y) <> 0 THEN Illuminate X + 1, Y, Range - 1
  88.             IF Y > YL THEN IF MapArray(X, Y - 1) <> 0 THEN Illuminate X, Y - 1, Range - 1
  89.             IF Y < YH THEN IF MapArray(X, Y + 1) <> 0 THEN Illuminate X, Y + 1, Range - 1
  90.         END IF
  91.     END IF
  92.  
  93.  
  94. SUB DrawMap
  95.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  96.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  97.  
  98.     Illuminate Hero.X, Hero.Y, Hero.Light.Reach
  99.  
  100.     FOR y = 0 TO YH
  101.         FOR x = 0 TO XH
  102.             LOCATE y + 1, x + 1
  103.             IF MapArray(x, y) AND 1 THEN 'It's a visible part of the map, draw it
  104.                 IF MapArray(x, y) AND 2 THEN 'it's a visible room
  105.                     COLOR 0, &HFF000000
  106.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(219)
  107.                 END IF
  108.                 IF MapArray(x, y) AND 4 THEN 'it's a visible path
  109.                     COLOR &HFF000000, &HFF777777
  110.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), "."
  111.                 END IF
  112.                 IF MapArray(x, y) AND 8 THEN 'it's the stairs to the next level
  113.                     COLOR &HFF00FF00, &HFFFFFF00
  114.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(240)
  115.                 END IF
  116.                 IF MapArray(x, y) AND 16 THEN 'it's currently illuminated by the lightsource
  117.                     COLOR &H40FFFF00, 0
  118.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(219)
  119.                     MapArray(x, y) = MapArray(x, y) AND NOT 16
  120.                 END IF
  121.  
  122.             ELSE
  123.                 COLOR &HFF0000FF, &HFF000000
  124.                 _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(219)
  125.             END IF
  126.         NEXT
  127.     NEXT
  128.     COLOR &HFFFFFF00, 0 'Yellow Hero
  129.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  130.  
  131.  
  132.  
  133.  
  134.  
  135. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  136.     ERASE MapArray 'clear the old map and reset everything to 0
  137.     REDIM MapArray(XLimit, YLimit) AS _BYTE
  138.  
  139.     DIM RoomCenterX(Rooms) AS _BYTE, RoomCenterY(Rooms) AS _BYTE
  140.  
  141.     FOR i = 1 TO Rooms
  142.         DO
  143.             RoomSize = INT(RND * 9) + 2
  144.             RoomX = INT(RND * (XLimit - RoomSize))
  145.             RoomY = INT(RND * (YLimit - RoomSize))
  146.             'test for positioning
  147.             good = -1 'it's good starting out
  148.             FOR y = 0 TO RoomSize: FOR x = 0 TO RoomSize
  149.                     IF MapArray(RoomX + x, RoomY + y) = 2 THEN good = 0: EXIT FOR 'don't draw a room on a room
  150.             NEXT x, y
  151.         LOOP UNTIL good
  152.         FOR y = 0 TO RoomSize: FOR x = 0 TO RoomSize
  153.                 MapArray(RoomX + x, RoomY + y) = 2 'go ahead and draw a room
  154.         NEXT x, y
  155.         RoomCenterX(i) = RoomX + .5 * RoomSize
  156.         RoomCenterY(i) = RoomY + .5 * RoomSize
  157.     NEXT
  158.     FOR i = 1 TO Rooms - 1
  159.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  160.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  161.         DO UNTIL StartX = EndX AND StartY = EndY
  162.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  163.             Meander = 10
  164.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  165.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  166.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  167.                     Ychange = 0
  168.                 ELSE
  169.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  170.                     Ychange = 0
  171.                 END IF
  172.             ELSE
  173.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  174.                     Ychange = SGN(EndY - StartY)
  175.                     XChange = 0
  176.                 ELSE
  177.                     Ychange = INT(RND * 3) - 1
  178.                     XChange = 0
  179.                 END IF
  180.             END IF
  181.             StartX = StartX + XChange
  182.             StartY = StartY + Ychange
  183.             IF StartX < 0 THEN StartX = 0
  184.             IF StartY < 0 THEN StartY = 0
  185.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  186.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  187.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 4
  188.         LOOP
  189.     NEXT
  190.     DO
  191.         Hero.X = INT(RND * XLimit + 1)
  192.         Hero.Y = INT(RND * YLimit + 1)
  193.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 2 'place the hero randomly, until they're in a room somewhere
  194.     DO
  195.         x = INT(RND * XLimit + 1)
  196.         y = INT(RND * YLimit + 1)
  197.     LOOP UNTIL MapArray(x, y) AND 2 'get a random spot in a room, for the stairs to the next level
  198.     MapArray(x, y) = MapArray(x, y) OR 8
  199.  

I'm actually rather happy with what I've got here so far, and I really like the illumination code, which can be used to simulate various light sources for us.  A candle has a minor glow, only illuminating the ground close to our hero, whereas a torch might illuminate further, and a lantern even more than that...

Navigation/Key commands
For testing purposes, at this point, you can alter your light's range manually with the "+" and "-" keys. 
Arrow keys move your hero across the screen.
The "<" key allows you to go down stairs to the next level. 



Test it out, see what you think of things as they currently exist, and let me know how it performs on your machine/OS.  The illumination routine isn't half as efficient as it could be, as it recursively calls itself over and over and repeats lighting checks on the same areas multiple times, but I don't think it should really be that big an issue since a lightsource should never be larger than 5 or 6 tiles.  If I was going to have the light extend dozens of tiles, I'd work on fixing the repetitive recursive calls, but in this case it seems minor enough to keep it simple as it is.  On my machine, it runs perfectly fine with no issue, but if it lags or uses too much CPU power on somebody else's, I'll redo it with a little better logic checking.

Try it out, tell me what you think, and if there's any issues with navigation, illumination, or such, let me know and I'll work on correcting it before I move on and start adding monsters, gear, and everything else into the game one step at a time.
* courbd.ttf (Filesize: 786.37 KB, Downloads: 159)
« Last Edit: September 12, 2019, 12:15:25 pm by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Ashish

  • Forum Resident
  • Posts: 630
  • Never Give Up!
    • View Profile
Re: Rogue-Like (work in progress)
« Reply #1 on: September 07, 2019, 09:59:32 am »
Hi Steve! Nice.. The only issue is that it lags. Seems like it wait for a bit to respond to keys.
if (Me.success) {Me.improve()} else {Me.tryAgain()}


My Projects - https://github.com/AshishKingdom?tab=repositories
OpenGL tutorials - https://ashishkingdom.github.io/OpenGL-Tutorials

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #2 on: September 07, 2019, 10:12:46 am »
Hi Steve! Nice.. The only issue is that it lags. Seems like it wait for a bit to respond to keys.

I was a little worried that it might on lower-end systems. 

Quick question: Is the lag in general, or when you've got a strong light illuminating your surroundings?  The game should be one which operates on a "turn-based" style, where nothing changes or happens, until the player makes a keypress.  It's not a "real-time" game, where monsters and such will move independently and the game progresses consistantly; instead it only updates with each keystroke from the player.  (And even then, only from registered keystrokes.  Arrow keys to move, Space to wait and skip a turn, "<" to go down stairs... Other keys for future functionality...)

Are you seeing a general sluggishness to respond, with high CPU usage (such as the light calling itself over and over in an unregulated loop), or is it just the style which might not be what you were expecting?  I know a lot of you younger folks have probably never been exposed to Rogue (after all, it was a 1980 text game which ran on consoles/terminals), and I think the movement seems fairly faithful on my system, but I'm still worried there might be an issue in there somewhere for folks with older machines.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Ashish

  • Forum Resident
  • Posts: 630
  • Never Give Up!
    • View Profile
Re: Rogue-Like (work in progress)
« Reply #3 on: September 07, 2019, 10:35:44 am »
  The game should be one which operates on a "turn-based" style, where nothing changes or happens, until the player makes a keypress.
Ok. Got it. No, no high cpu usage.
if (Me.success) {Me.improve()} else {Me.tryAgain()}


My Projects - https://github.com/AshishKingdom?tab=repositories
OpenGL tutorials - https://ashishkingdom.github.io/OpenGL-Tutorials

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: Rogue-Like (work in progress)
« Reply #4 on: September 07, 2019, 12:07:33 pm »
Plays pretty smooth for me, looks a lot like ye old ROGUE games. Which I have to admit I always found them particularly hard with the lack of a visible interface and usually a lack of instructions\information on game play.

I find the lighting trick rather cool, lots of possibilities with that one; blindness, candle, torch, ect...

The mapping always interested me. I was once told, by a guy who made probably my fave roguish type game ever 'Reaping the Dungeon', that he generated some 'rooms' then had a 'worm' make tunnels connecting them. Alas I have tried many a time to get that to work and just never really succeed, the maps are either big messes of tunnels everywhere or half the rooms can never be reached. couldn't get a good balance.

I've done a lot of work on my own Reaping clone, thats when I asked Galleon about being able to change the BIOS font and he set that up for me way back when. But never was happy with the maps I was able to generate, and that always held me back from finishing it.

Anyway it looks really awesome and nostalgic! 
Granted after becoming radioactive I only have a half-life!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #5 on: September 07, 2019, 12:35:54 pm »
Plays pretty smooth for me, looks a lot like ye old ROGUE games. Which I have to admit I always found them particularly hard with the lack of a visible interface and usually a lack of instructions\information on game play.

I find the lighting trick rather cool, lots of possibilities with that one; blindness, candle, torch, ect...

The mapping always interested me. I was once told, by a guy who made probably my fave roguish type game ever 'Reaping the Dungeon', that he generated some 'rooms' then had a 'worm' make tunnels connecting them. Alas I have tried many a time to get that to work and just never really succeed, the maps are either big messes of tunnels everywhere or half the rooms can never be reached. couldn't get a good balance.

I've done a lot of work on my own Reaping clone, thats when I asked Galleon about being able to change the BIOS font and he set that up for me way back when. But never was happy with the maps I was able to generate, and that always held me back from finishing it.

Anyway it looks really awesome and nostalgic!

The lighting trick is also going to become an “awareness trick”.  Using the same behavior as the light, with the monsters, I’ll be able to give the monsters a “sight” and “hearing” range.  The way I envision it, something like a bat will have almost 0 sight but a decent hearing range, so sneaking might allow you to bypass it without being found, whereas an invisibility potion would be useless.  A different creature might have a high visibility, but be deaf... 

Just as we produce a light around us, monsters should have an awareness range around them.  At least, that’s my intention for how things should work...  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: Rogue-Like (work in progress)
« Reply #6 on: September 07, 2019, 02:19:52 pm »
wow that lighting system is really slow! I played around with it a bit and when you get up near 10-11 distance it darn near locks. no super site abilities I guess! which makes me wonder just how well the performance will hold up if your using something similar for awareness, 2 or 3 creatures and that would be it.

Though looking at the Illuminate code I don't see anything directly that makes it look slow unless its all the recursion? even at 10 should only be checking what, 220-221 spaces?

anyways, I did add a little info area, very very basic.
Code: QB64: [Select]
  1. SUB Display_Info_Area
  2.  LOCATE 1, 1: PRINT "ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿"
  3.  FOR i%% = 0 TO 2
  4.   LOCATE 2 + i%%, 1: PRINT "³"; TAB(94); "³"
  5.  NEXT i%%
  6.  LOCATE 2 + i%%, 1: PRINT "ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ"
  7.  LOCATE 2, 2: PRINT "HP:"; Hero.HP; TAB(12); "Map X:"; Hero.X; TAB(24); "Map Y:"; Hero.Y; TAB(36); "Level:"; Hero.Level;
  8.  PRINT TAB(46); "Candle Power:"; Hero.Light.Reach; TAB(62); "Aflliction:";
  9.  LOCATE 3, 2: PRINT "Load:"; TAB(15); "Right Hand:"; TAB(35); "Left Hand:"
  10.  
kept it all text based but the border might be better as a line statement or something. Granted the map can go into the area, but it was just an idea.

It might be on your list but did you see this code?
Code: QB64: [Select]
  1.    CASE 60 ' "<" key
  2.     Level = Level + 1
  3.     IF MapArray(Hero.X, Hero.Y) AND 8 THEN CreateMap 99, 74, 10, Level
  4.  
so every time the player hits the "<" key level increases! even if there is no 'ladder' there.
Granted after becoming radioactive I only have a half-life!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #7 on: September 07, 2019, 03:46:49 pm »
wow that lighting system is really slow! I played around with it a bit and when you get up near 10-11 distance it darn near locks. no super site abilities I guess! which makes me wonder just how well the performance will hold up if your using something similar for awareness, 2 or 3 creatures and that would be it.

Though looking at the Illuminate code I don't see anything directly that makes it look slow unless its all the recursion? even at 10 should only be checking what, 220-221 spaces?

I agree with all you said here 100%.  The lag generated with higher values is completely beyond my expectations, and I honestly don't see why it's as extreme as it is.  And, I think you're right with it either limiting the creatures to a minute awareness range, or else becoming an anchor on the program which will lag it to the point of insanity...

To correct the issue, I've replaced it with this little set of code:

Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS INTEGER
  3.     High AS INTEGER
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS INTEGER
  8.     Left AS INTEGER
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS INTEGER
  13.     Damage AS Damage_Type
  14.     HitBonus AS INTEGER
  15.     DamageBonus AS INTEGER
  16.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS INTEGER 'Passive Defense (dodge)
  21.     DR AS INTEGER 'Damage Resistance (absorption)
  22.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     X AS INTEGER
  29.     Y AS INTEGER
  30.     Life AS Damage_Type
  31.     Level AS INTEGER
  32.     EXP_Earned AS LONG
  33.     EXP_Needed AS LONG
  34.     Light AS Light_Type
  35.     Weapon1 AS Weapon_Type
  36.     Weapon2 AS Weapon_Type
  37.     Armor AS Armor_Type
  38.  
  39. DIM SHARED Hero AS Hero_Type
  40. DIM SHARED Level AS INTEGER: Level = 1
  41. DIM SHARED XL, XH, YL, YH
  42.  
  43.  
  44. SCREEN _NEWIMAGE(800, 700, 32)
  45. REDIM SHARED MapArray(0, 0) AS _BYTE
  46. REDIM SHARED Distance(0, 0) AS _BYTE
  47.  
  48. Init
  49. CreateMap 99, 74, 10, 1
  50.     CLS , &HFF0000FF
  51.     DrawMap
  52.     DisplayCharacter
  53.     _DISPLAY
  54.     GetInput
  55.  
  56. SUB DisplayCharacter
  57.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  58.     COLOR &HFFFFFFFF, 0
  59.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  60.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  61.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  62.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  63.  
  64.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  65.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  66.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  67.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  68.  
  69. SUB Init
  70.     Hero.Name = "Steve The Tester!"
  71.     Hero.Life.Low = 10
  72.     Hero.Life.High = 10
  73.     Hero.Level = 1
  74.     Hero.EXP_Earned = 0
  75.     Hero.EXP_Needed = 2
  76.     Hero.Light.Name = "Magic Candle"
  77.     Hero.Light.Reach = 2
  78.     Hero.Light.Left = -1 'infinite
  79.     Hero.Weapon1.Name = "Bare Fist"
  80.     Hero.Weapon1.Reach = 1
  81.     Hero.Weapon1.Damage.Low = 1
  82.     Hero.Weapon1.Damage.High = 2
  83.     Hero.Weapon1.HitBonus = 0
  84.     Hero.Weapon1.DamageBonus = 0
  85.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  86.     Hero.Weapon2.Name = "Magic Candle"
  87.     Hero.Weapon2.Reach = 0
  88.     Hero.Weapon2.Damage.Low = 0
  89.     Hero.Weapon2.Damage.High = 0
  90.     Hero.Weapon2.HitBonus = 0
  91.     Hero.Weapon2.DamageBonus = 0
  92.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  93.     Hero.Armor.Name = "Naked"
  94.     Hero.Armor.PD = 0
  95.     Hero.Armor.DR = 0
  96.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  97.  
  98. SUB GetInput
  99.     DO
  100.         k = _KEYHIT
  101.         SELECT CASE k
  102.             CASE 18432 'up
  103.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  104.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  105.                         Hero.Y = Hero.Y - 1
  106.                     END IF
  107.                 END IF
  108.             CASE 19200 'left
  109.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  110.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  111.                         Hero.X = Hero.X - 1
  112.                     END IF
  113.                 END IF
  114.             CASE 20480 'down
  115.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  116.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  117.                         Hero.Y = Hero.Y + 1
  118.                     END IF
  119.                 END IF
  120.             CASE 19712 'right
  121.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  122.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  123.                         Hero.X = Hero.X + 1
  124.                     END IF
  125.                 END IF
  126.             CASE 32 'space to just wait and skip a turn
  127.             CASE 60 ' "<" key
  128.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  129.                     Level = Level + 1
  130.                     CreateMap 99, 74, 10, Level
  131.                 END IF
  132.             CASE ASC("+"), ASC("=")
  133.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  134.             CASE ASC("-"), ASC("_")
  135.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  136.         END SELECT
  137.         _LIMIT 60
  138.     LOOP UNTIL k
  139.     _KEYCLEAR 'one keystroke at a time
  140.  
  141. SUB Illuminate (TX, TY, TRange)
  142.     X = TX: Y = TY: Range = TRange
  143.     PathFind TX, TY, TRange
  144.     FOR X = 0 TO XH
  145.         FOR Y = 0 TO YH
  146.             IF Distance(X, Y) <> 0 THEN 'It's close enough to check for illumination
  147.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  148.             END IF
  149.         NEXT
  150.     NEXT
  151.  
  152.  
  153. SUB DrawMap
  154.     Illuminate Hero.X, Hero.Y, Hero.Light.Reach
  155.     FOR Y = 0 TO YH
  156.         FOR X = 0 TO XH
  157.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  158.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  159.                     COLOR &HFF000000, 0
  160.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  161.                 END IF
  162.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  163.                     COLOR &HFF000000, &HFF777777
  164.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  165.                 END IF
  166.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  167.                     COLOR &HFF00FF00, &HFFFFFF00
  168.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  169.                 END IF
  170.             END IF
  171.             'note: highlighting for the light should come AFTER the map is drawn
  172.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  173.                 COLOR &H40FFFF00, 0
  174.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  175.                 MapArray(X, Y) = MapArray(X, Y) - 1
  176.             END IF
  177.         NEXT
  178.     NEXT
  179.     COLOR &HFFFFFF00, 0 'Yellow Hero
  180.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  181.  
  182.  
  183.  
  184.  
  185.  
  186. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  187.     ERASE MapArray 'clear the old map and reset everything to 0
  188.     REDIM MapArray(XLimit, YLimit) AS _BYTE
  189.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  190.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  191.  
  192.     DIM RoomCenterX(Rooms) AS _BYTE, RoomCenterY(Rooms) AS _BYTE
  193.  
  194.     FOR i = 1 TO Rooms
  195.         DO
  196.             RoomSize = INT(RND * 9) + 2
  197.             RoomX = INT(RND * (XLimit - RoomSize))
  198.             RoomY = INT(RND * (YLimit - RoomSize))
  199.             'test for positioning
  200.             good = -1 'it's good starting out
  201.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  202.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  203.             NEXT X, Y
  204.         LOOP UNTIL good
  205.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  206.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  207.         NEXT X, Y
  208.         RoomCenterX(i) = RoomX + .5 * RoomSize
  209.         RoomCenterY(i) = RoomY + .5 * RoomSize
  210.     NEXT
  211.     FOR i = 1 TO Rooms - 1
  212.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  213.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  214.         DO UNTIL StartX = EndX AND StartY = EndY
  215.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  216.             Meander = 10
  217.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  218.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  219.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  220.                     Ychange = 0
  221.                 ELSE
  222.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  223.                     Ychange = 0
  224.                 END IF
  225.             ELSE
  226.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  227.                     Ychange = SGN(EndY - StartY)
  228.                     XChange = 0
  229.                 ELSE
  230.                     Ychange = INT(RND * 3) - 1
  231.                     XChange = 0
  232.                 END IF
  233.             END IF
  234.             StartX = StartX + XChange
  235.             StartY = StartY + Ychange
  236.             IF StartX < 0 THEN StartX = 0
  237.             IF StartY < 0 THEN StartY = 0
  238.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  239.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  240.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  241.         LOOP
  242.     NEXT
  243.     DO
  244.         Hero.X = INT(RND * XLimit + 1)
  245.         Hero.Y = INT(RND * YLimit + 1)
  246.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  247.     DO
  248.         X = INT(RND * XLimit + 1)
  249.         Y = INT(RND * YLimit + 1)
  250.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  251.     MapArray(X, Y) = MapArray(X, Y) OR 16
  252.  
  253. SUB PathFind (FromX, FromY, Range)
  254.     REDIM Distance(XH, YH) AS _BYTE
  255.     DIM Temp(XH, YH) AS _BYTE
  256.     DIM m AS _MEM, m1 AS _MEM
  257.     m = _MEM(Distance()): m1 = _MEM(Temp())
  258.     Temp(FromX, FromY) = -1
  259.     FOR i = 1 TO Range
  260.         FOR x = 0 TO XH
  261.             FOR y = 0 TO YH
  262.                 IF x < XH THEN
  263.                     IF Temp(x + 1, y) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  264.                 END IF
  265.                 IF x > 0 THEN
  266.                     IF Temp(x - 1, y) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  267.                 END IF
  268.                 IF y < YH THEN
  269.                     IF Temp(x, y + 1) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  270.                 END IF
  271.                 IF y > 0 THEN
  272.                     IF Temp(x, y - 1) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  273.                 END IF
  274.             NEXT
  275.         NEXT
  276.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  277.     NEXT
  278.     _MEMFREE m
  279.     _MEMFREE m1

Now I don't just have an illumination routine; I have a full fledged pathfinding routine in place to make use of later.  Since I can map all points distance from the character, all I need to do is check to see if the tile a mob is on  has a distance value <= their awareness levels....  If so, all I have to do is check the tiles around them and move them to the tile with the next lowest value so they can approach the hero, one menacing step at a time.

Testing this, we can move our lightsource up to a whopping 25 tiles (NUKE!!) and still move around without any lag or issues with gamplay.  (Higher light values probably work just as well, but I didn't bother to test beyond 25.) 

I think the lag from illumination issue has probably been solved for good with this change, and I hope all it'll need is a slight tweak or so, so that it'll work for mob awareness levels and such as well.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: Rogue-Like (work in progress)
« Reply #8 on: September 07, 2019, 04:40:13 pm »
Yep, much more 'stable' in performance. I was going to ask if you were going to use a path finding routine to allow the creatures to follow or intercept the player, guess there is my answer. Looking really good.
Granted after becoming radioactive I only have a half-life!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #9 on: September 07, 2019, 04:49:50 pm »
A slight tweak to illumination/pathfinding... Now we no longer count steps through a solid wall. :P

Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS _UNSIGNED _BYTE
  3.     High AS _UNSIGNED _BYTE
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS _UNSIGNED _BYTE
  8.     Left AS _UNSIGNED _BYTE
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS _UNSIGNED _BYTE
  13.     Damage AS Damage_Type
  14.     HitBonus AS _UNSIGNED _BYTE
  15.     DamageBonus AS _UNSIGNED _BYTE
  16.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  21.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  22.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     Life AS Damage_Type
  29.     Level AS _UNSIGNED _BYTE
  30.     EXP_Earned AS LONG
  31.     EXP_Needed AS LONG
  32.     Light AS Light_Type
  33.     Weapon1 AS Weapon_Type
  34.     Weapon2 AS Weapon_Type
  35.     Armor AS Armor_Type
  36.  
  37. DIM SHARED Hero AS Hero_Type
  38. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  39. DIM SHARED XL, XH, YL, YH
  40.  
  41.  
  42. SCREEN _NEWIMAGE(800, 700, 32)
  43. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  44. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  45.  
  46.  
  47. Init
  48. CreateMap 99, 74, 10, 1
  49.     CLS , &HFF0000FF
  50.     DrawMap
  51.     DisplayCharacter
  52.     _DISPLAY
  53.     GetInput
  54.  
  55. SUB DisplayCharacter
  56.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  57.     COLOR &HFFFFFFFF, 0
  58.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  59.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  60.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  61.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  62.  
  63.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  64.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  65.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  66.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  67.  
  68. SUB Init
  69.     Hero.Name = "Steve The Tester!"
  70.     Hero.Life.Low = 10
  71.     Hero.Life.High = 10
  72.     Hero.Level = 1
  73.     Hero.EXP_Earned = 0
  74.     Hero.EXP_Needed = 2
  75.     Hero.Light.Name = "Magic Candle"
  76.     Hero.Light.Reach = 2
  77.     Hero.Light.Left = -1 'infinite
  78.     Hero.Weapon1.Name = "Bare Fist"
  79.     Hero.Weapon1.Reach = 1
  80.     Hero.Weapon1.Damage.Low = 1
  81.     Hero.Weapon1.Damage.High = 2
  82.     Hero.Weapon1.HitBonus = 0
  83.     Hero.Weapon1.DamageBonus = 0
  84.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  85.     Hero.Weapon2.Name = "Magic Candle"
  86.     Hero.Weapon2.Reach = 0
  87.     Hero.Weapon2.Damage.Low = 0
  88.     Hero.Weapon2.Damage.High = 0
  89.     Hero.Weapon2.HitBonus = 0
  90.     Hero.Weapon2.DamageBonus = 0
  91.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  92.     Hero.Armor.Name = "Naked"
  93.     Hero.Armor.PD = 0
  94.     Hero.Armor.DR = 0
  95.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  96.  
  97. SUB GetInput
  98.     DO
  99.         k = _KEYHIT
  100.         SELECT CASE k
  101.             CASE 18432 'up
  102.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  103.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  104.                         Hero.Y = Hero.Y - 1
  105.                         PathFind
  106.                     END IF
  107.                 END IF
  108.             CASE 19200 'left
  109.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  110.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  111.                         Hero.X = Hero.X - 1
  112.                         PathFind
  113.                     END IF
  114.                 END IF
  115.             CASE 20480 'down
  116.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  117.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  118.                         Hero.Y = Hero.Y + 1
  119.                         PathFind
  120.                     END IF
  121.                 END IF
  122.             CASE 19712 'right
  123.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  124.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  125.                         Hero.X = Hero.X + 1
  126.                         PathFind
  127.                     END IF
  128.                 END IF
  129.             CASE 32 'space to just wait and skip a turn
  130.             CASE 60 ' "<" key
  131.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  132.                     Level = Level + 1
  133.                     CreateMap 99, 74, 10, Level
  134.                     PathFind
  135.                 END IF
  136.             CASE ASC("+"), ASC("=")
  137.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  138.             CASE ASC("-"), ASC("_")
  139.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  140.         END SELECT
  141.         _LIMIT 60
  142.     LOOP UNTIL k
  143.     _KEYCLEAR 'one keystroke at a time
  144.  
  145. SUB Illuminate (Range)
  146.     FOR X = 0 TO XH
  147.         FOR Y = 0 TO YH
  148.             IF Distance(X, Y) <= Range THEN 'It's close enough to check for illumination
  149.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  150.             END IF
  151.         NEXT
  152.     NEXT
  153.  
  154.  
  155. SUB DrawMap
  156.     Illuminate Hero.Light.Reach
  157.     FOR Y = 0 TO YH
  158.         FOR X = 0 TO XH
  159.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  160.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  161.                     COLOR &HFF000000, 0
  162.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  163.                 END IF
  164.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  165.                     COLOR &HFF000000, &HFF777777
  166.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  167.                 END IF
  168.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  169.                     COLOR &HFF00FF00, &HFFFFFF00
  170.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  171.                 END IF
  172.             END IF
  173.             'note: highlighting for the light should come AFTER the map is drawn
  174.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  175.                 COLOR &H40FFFF00, 0
  176.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  177.                 MapArray(X, Y) = MapArray(X, Y) - 1
  178.             END IF
  179.         NEXT
  180.     NEXT
  181.     COLOR &HFFFFFF00, 0 'Yellow Hero
  182.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  183.  
  184.  
  185.  
  186.  
  187.  
  188. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  189.     ERASE MapArray 'clear the old map and reset everything to 0
  190.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  191.     REDIM Distance(XH, YH) AS _UNSIGNED _BYTE
  192.     REDIM Temp(XH, YH) AS _UNSIGNED _BYTE
  193.  
  194.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  195.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  196.  
  197.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  198.  
  199.     FOR i = 1 TO Rooms
  200.         DO
  201.             RoomSize = INT(RND * 9) + 2
  202.             RoomX = INT(RND * (XLimit - RoomSize))
  203.             RoomY = INT(RND * (YLimit - RoomSize))
  204.             'test for positioning
  205.             good = -1 'it's good starting out
  206.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  207.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  208.             NEXT X, Y
  209.         LOOP UNTIL good
  210.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  211.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  212.         NEXT X, Y
  213.         RoomCenterX(i) = RoomX + .5 * RoomSize
  214.         RoomCenterY(i) = RoomY + .5 * RoomSize
  215.     NEXT
  216.     FOR i = 1 TO Rooms - 1
  217.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  218.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  219.         DO UNTIL StartX = EndX AND StartY = EndY
  220.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  221.             Meander = 10
  222.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  223.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  224.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  225.                     Ychange = 0
  226.                 ELSE
  227.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  228.                     Ychange = 0
  229.                 END IF
  230.             ELSE
  231.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  232.                     Ychange = SGN(EndY - StartY)
  233.                     XChange = 0
  234.                 ELSE
  235.                     Ychange = INT(RND * 3) - 1
  236.                     XChange = 0
  237.                 END IF
  238.             END IF
  239.             StartX = StartX + XChange
  240.             StartY = StartY + Ychange
  241.             IF StartX < 0 THEN StartX = 0
  242.             IF StartY < 0 THEN StartY = 0
  243.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  244.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  245.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  246.         LOOP
  247.     NEXT
  248.     DO
  249.         Hero.X = INT(RND * XLimit + 1)
  250.         Hero.Y = INT(RND * YLimit + 1)
  251.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  252.     DO
  253.         X = INT(RND * XLimit + 1)
  254.         Y = INT(RND * YLimit + 1)
  255.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  256.     MapArray(X, Y) = MapArray(X, Y) OR 16
  257.     PathFind
  258.  
  259. SUB PathFind
  260.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  261.     DIM pass AS _UNSIGNED _BYTE
  262.     m = _MEM(Distance()): m1 = _MEM(Temp())
  263.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  264.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  265.     Temp(Hero.X, Hero.Y) = 0
  266.     pass = 0
  267.     DO
  268.         changed = 0
  269.         y = 0
  270.         DO
  271.             x = 0
  272.             DO
  273.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  274.                     IF x < XH THEN
  275.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  276.                     END IF
  277.                     IF x > 0 THEN
  278.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  279.                     END IF
  280.                     IF y < YH THEN
  281.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  282.                     END IF
  283.                     IF y > 0 THEN
  284.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  285.                     END IF
  286.                 END IF
  287.                 x = x + 1
  288.             LOOP UNTIL x > XH
  289.             y = y + 1
  290.         LOOP UNTIL y > YH
  291.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  292.         pass = pass + 1
  293.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  294.     Distance(Hero.X, Hero.Y) = 0

I think this is a much better way for everything to behave, and by using the + key to set your lightsource to an insane level now, you can see how the monster awareness is going to work.  If a bat has a hearing rating of 10, and you boost your lightsource up to a 10, you can see how close you'd have to get to the creature for it to notice and react to your hero.  ;)



Unless somebody sees an issue with something at this point in time, it seems as if it's about time to start adding a few random monsters into the game and then work on the basics of combat, experience, and leveling up.  :)
« Last Edit: September 07, 2019, 04:51:11 pm by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #10 on: September 09, 2019, 01:25:39 pm »
The game now generates monsters for us and places them randomly on the map so we can wander around and ....  do nothing at this point... with them.

Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS _UNSIGNED _BYTE
  3.     High AS _UNSIGNED _BYTE
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS _UNSIGNED _BYTE
  8.     Left AS _UNSIGNED _BYTE
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS _UNSIGNED _BYTE
  13.     Damage AS Damage_Type
  14.     HitBonus AS _UNSIGNED _BYTE
  15.     DamageBonus AS _UNSIGNED _BYTE
  16.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  21.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  22.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     Life AS Damage_Type
  29.     Level AS _UNSIGNED _BYTE
  30.     EXP_Earned AS LONG
  31.     EXP_Needed AS LONG
  32.     Light AS Light_Type
  33.     Weapon1 AS Weapon_Type
  34.     Weapon2 AS Weapon_Type
  35.     Armor AS Armor_Type
  36.  
  37. TYPE Map_Identifer_TYPE
  38.     Symbol AS _UNSIGNED _BYTE
  39.  
  40. TYPE Monster_TYPE
  41.     Name AS STRING * 20
  42.     Life AS Damage_Type
  43.     Level AS INTEGER
  44.     ExpBonus AS INTEGER
  45.     Sight AS INTEGER
  46.     Hearing AS INTEGER
  47.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  48.     Weapon1 AS Weapon_Type
  49.     Weapon2 AS Weapon_Type
  50.     Armor AS Armor_Type
  51.     ID AS Map_Identifer_TYPE
  52.  
  53. TYPE Encounter_TYPE
  54.     Active AS INTEGER
  55.     X AS INTEGER
  56.     Y AS INTEGER
  57.     M AS INTEGER
  58.  
  59. REDIM SHARED Monster(100) AS Monster_TYPE
  60. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  61.  
  62. DIM SHARED Hero AS Hero_Type
  63. REDIM SHARED Monster(100) AS Monster_TYPE
  64. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  65. DIM SHARED XL, XH, YL, YH
  66.  
  67.  
  68. SCREEN _NEWIMAGE(800, 700, 32)
  69. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  70. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  71.  
  72.  
  73. Init
  74. CreateMap 99, 74, 10, 1
  75.     CLS , &HFF0000FF
  76.     DrawMap
  77.     DisplayCharacter
  78.     _DISPLAY
  79.     GetInput
  80.  
  81. SUB DisplayCharacter
  82.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  83.     COLOR &HFFFFFFFF, 0
  84.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  85.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  86.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  87.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  88.  
  89.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  90.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  91.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  92.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  93.  
  94. SUB Init
  95.     Hero.Name = "Steve The Tester!"
  96.     Hero.Life.Low = 10
  97.     Hero.Life.High = 10
  98.     Hero.Level = 1
  99.     Hero.EXP_Earned = 0
  100.     Hero.EXP_Needed = 2
  101.     Hero.Light.Name = "Magic Candle"
  102.     Hero.Light.Reach = 2
  103.     Hero.Light.Left = -1 'infinite
  104.     Hero.Weapon1.Name = "Bare Fist"
  105.     Hero.Weapon1.Reach = 1
  106.     Hero.Weapon1.Damage.Low = 1
  107.     Hero.Weapon1.Damage.High = 2
  108.     Hero.Weapon1.HitBonus = 0
  109.     Hero.Weapon1.DamageBonus = 0
  110.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  111.     Hero.Weapon2.Name = "Magic Candle"
  112.     Hero.Weapon2.Reach = 0
  113.     Hero.Weapon2.Damage.Low = 0
  114.     Hero.Weapon2.Damage.High = 0
  115.     Hero.Weapon2.HitBonus = 0
  116.     Hero.Weapon2.DamageBonus = 0
  117.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  118.     Hero.Armor.Name = "Naked"
  119.     Hero.Armor.PD = 0
  120.     Hero.Armor.DR = 0
  121.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  122.  
  123. SUB GetInput
  124.     DO
  125.         k = _KEYHIT
  126.         SELECT CASE k
  127.             CASE 18432 'up
  128.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  129.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  130.                         Hero.Y = Hero.Y - 1
  131.                         PathFind
  132.                     END IF
  133.                 END IF
  134.             CASE 19200 'left
  135.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  136.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  137.                         Hero.X = Hero.X - 1
  138.                         PathFind
  139.                     END IF
  140.                 END IF
  141.             CASE 20480 'down
  142.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  143.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  144.                         Hero.Y = Hero.Y + 1
  145.                         PathFind
  146.                     END IF
  147.                 END IF
  148.             CASE 19712 'right
  149.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  150.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  151.                         Hero.X = Hero.X + 1
  152.                         PathFind
  153.                     END IF
  154.                 END IF
  155.             CASE 32 'space to just wait and skip a turn
  156.             CASE 60 ' "<" key
  157.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  158.                     Level = Level + 1
  159.                     CreateMap 99, 74, 10, Level
  160.                     PathFind
  161.                 END IF
  162.             CASE ASC("+"), ASC("=")
  163.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  164.             CASE ASC("-"), ASC("_")
  165.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  166.         END SELECT
  167.         _LIMIT 60
  168.     LOOP UNTIL k
  169.     _KEYCLEAR 'one keystroke at a time
  170.  
  171. SUB Illuminate (Range)
  172.     FOR X = 0 TO XH
  173.         FOR Y = 0 TO YH
  174.             IF Distance(X, Y) <= Range THEN 'It's close enough to check for illumination
  175.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  176.             END IF
  177.         NEXT
  178.     NEXT
  179.  
  180.  
  181. SUB DrawMap
  182.     Illuminate Hero.Light.Reach
  183.     FOR Y = 0 TO YH
  184.         FOR X = 0 TO XH
  185.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  186.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  187.                     COLOR &HFF000000, 0
  188.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  189.                 END IF
  190.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  191.                     COLOR &HFF000000, &HFF777777
  192.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  193.                 END IF
  194.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  195.                     COLOR &HFF00FF00, &HFFFFFF00
  196.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  197.                 END IF
  198.             END IF
  199.             'note: highlighting for the light should come AFTER the map is drawn
  200.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  201.                 COLOR &H40FFFF00, 0
  202.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  203.                 MapArray(X, Y) = MapArray(X, Y) - 1
  204.                 FOR i = 1 TO EncounterLimit
  205.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  206.                         COLOR Monster(Encounter(i).M).ID.Color
  207.                         t$ = CHR$(Monster(Encounter(i).M).ID.Symbol)
  208.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), t$
  209.                     END IF
  210.                     Encounter(i).M = RandomMonster
  211.                 NEXT
  212.  
  213.             END IF
  214.         NEXT
  215.     NEXT
  216.     COLOR &HFFFFFF00, 0 'Yellow Hero
  217.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  218.  
  219.  
  220.  
  221.  
  222.  
  223. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  224.     ERASE MapArray 'clear the old map and reset everything to 0
  225.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  226.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  227.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  228.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  229.  
  230.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  231.  
  232.     FOR i = 1 TO Rooms
  233.         DO
  234.             RoomSize = INT(RND * 9) + 2
  235.             RoomX = INT(RND * (XLimit - RoomSize))
  236.             RoomY = INT(RND * (YLimit - RoomSize))
  237.             'test for positioning
  238.             good = -1 'it's good starting out
  239.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  240.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  241.             NEXT X, Y
  242.         LOOP UNTIL good
  243.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  244.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  245.         NEXT X, Y
  246.         RoomCenterX(i) = RoomX + .5 * RoomSize
  247.         RoomCenterY(i) = RoomY + .5 * RoomSize
  248.     NEXT
  249.     FOR i = 1 TO Rooms - 1
  250.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  251.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  252.         DO UNTIL StartX = EndX AND StartY = EndY
  253.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  254.             Meander = 10
  255.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  256.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  257.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  258.                     Ychange = 0
  259.                 ELSE
  260.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  261.                     Ychange = 0
  262.                 END IF
  263.             ELSE
  264.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  265.                     Ychange = SGN(EndY - StartY)
  266.                     XChange = 0
  267.                 ELSE
  268.                     Ychange = INT(RND * 3) - 1
  269.                     XChange = 0
  270.                 END IF
  271.             END IF
  272.             StartX = StartX + XChange
  273.             StartY = StartY + Ychange
  274.             IF StartX < 0 THEN StartX = 0
  275.             IF StartY < 0 THEN StartY = 0
  276.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  277.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  278.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  279.         LOOP
  280.     NEXT
  281.     DO
  282.         Hero.X = INT(RND * XLimit + 1)
  283.         Hero.Y = INT(RND * YLimit + 1)
  284.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  285.     DO
  286.         X = INT(RND * XLimit + 1)
  287.         Y = INT(RND * YLimit + 1)
  288.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  289.     MapArray(X, Y) = MapArray(X, Y) OR 16
  290.     PathFind
  291.     EncounterLimit = INT(RND * 6) + 5
  292.     FOR i = 1 TO EncounterLimit
  293.         Encounter(i).M = RandomMonster
  294.         Encounter(i).Active = -1
  295.         DO
  296.             Encounter(i).X = INT(RND * XLimit + 1)
  297.             Encounter(i).Y = INT(RND * YLimit + 1)
  298.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 'get a random spot in a room, for the stairs to the next level
  299.     NEXT
  300.  
  301. SUB PathFind
  302.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  303.     DIM pass AS _UNSIGNED _BYTE
  304.     m = _MEM(Distance()): m1 = _MEM(Temp())
  305.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  306.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  307.     Temp(Hero.X, Hero.Y) = 0
  308.     pass = 0
  309.     DO
  310.         changed = 0
  311.         y = 0
  312.         DO
  313.             x = 0
  314.             DO
  315.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  316.                     IF x < XH THEN
  317.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  318.                     END IF
  319.                     IF x > 0 THEN
  320.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  321.                     END IF
  322.                     IF y < YH THEN
  323.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  324.                     END IF
  325.                     IF y > 0 THEN
  326.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  327.                     END IF
  328.                 END IF
  329.                 x = x + 1
  330.             LOOP UNTIL x > XH
  331.             y = y + 1
  332.         LOOP UNTIL y > YH
  333.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  334.         pass = pass + 1
  335.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  336.     Distance(Hero.X, Hero.Y) = 0
  337.  
  338. FUNCTION RandomMonster
  339.     'Shared variable level tells us what level of the dungeon we're on.
  340.     STATIC MC, DS 'monster count and data set
  341.     IF NOT DS THEN
  342.         DS = -1
  343.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  344.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  345.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  346.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  347.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  348.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  349.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  350.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  351.         'Monster(1).Armor.Name = ""
  352.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  353.  
  354.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  355.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  356.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  357.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  358.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  359.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  360.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  361.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  362.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  363.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  364.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  365.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  366.         NEXT
  367.     END IF
  368.     SELECT CASE Level 'the starting level
  369.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  370.     END SELECT
  371.     RandomMonster = INT(RND * MC) + 1

Play around with the lightsource a little, and you can really see how much it'll affect game play for us in the future, once things start coming all together.  The monsters are there all the time (unless you defeat them, which is impossible at the moment with no combat implemented yet), but you can now move around and see how they pop on and off our screen, as they come into and out of our light's range.  I have a feeling, strong light sources might end up becoming a treasured resource in this game -- after all, the sooner you see a monster, the sooner you can react to it.  (Shoot it with a bow from a range; zap it with magic from a distance...  If you can't see it, you're just shooting blindly into the dark!)

Currently, all monsters appear as a red question mark on the map, but that's intentional.  You're going to need to encounter and fight a monster at least once, before YOU get to assign the color and symbol you want to use to represent it on your map.  The idea I have here is that the more you progress, and the more you explore in the dungeon, the less you encounter those mysterious and dreaded question marks -- and it allows you to create a custom symbol set for each game, so you can create one which seems familiar, comfortable, and easy to remember/interact with for YOU.

Progress seems to be coming along rather nicely to this point.  Now I just need to add the very basics of combat to the game, and then I'll have the "core" of my rogue-like set, where all I'll do from that point on is flesh it out with extra weapons/armor/gear/minor mechanics...
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #11 on: September 11, 2019, 02:00:44 am »
Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS _UNSIGNED _BYTE
  3.     High AS _UNSIGNED _BYTE
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS _UNSIGNED _BYTE
  8.     Left AS _UNSIGNED _BYTE
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS _UNSIGNED _BYTE
  13.     Damage AS Damage_Type
  14.     HitBonus AS _UNSIGNED _BYTE
  15.     DamageBonus AS _UNSIGNED _BYTE
  16.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  21.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  22.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     Life AS Damage_Type
  29.     Level AS _UNSIGNED _BYTE
  30.     EXP_Earned AS LONG
  31.     EXP_Needed AS LONG
  32.     Light AS Light_Type
  33.     Weapon1 AS Weapon_Type
  34.     Weapon2 AS Weapon_Type
  35.     Armor AS Armor_Type
  36.  
  37. TYPE Map_Identifer_TYPE
  38.     Symbol AS _UNSIGNED _BYTE
  39.  
  40. TYPE Monster_TYPE
  41.     Name AS STRING * 20
  42.     Life AS Damage_Type
  43.     Level AS INTEGER
  44.     ExpBonus AS INTEGER
  45.     Sight AS INTEGER
  46.     Hearing AS INTEGER
  47.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  48.     Weapon1 AS Weapon_Type
  49.     Weapon2 AS Weapon_Type
  50.     Armor AS Armor_Type
  51.     ID AS Map_Identifer_TYPE
  52.  
  53. TYPE Encounter_TYPE
  54.     Active AS INTEGER
  55.     X AS INTEGER
  56.     Y AS INTEGER
  57.     M AS INTEGER
  58.     Life AS INTEGER
  59.  
  60. REDIM SHARED Monster(100) AS Monster_TYPE
  61. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  62.  
  63. DIM SHARED Hero AS Hero_Type
  64. REDIM SHARED Monster(100) AS Monster_TYPE
  65. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  66. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  67.  
  68.  
  69. SCREEN _NEWIMAGE(800, 700, 32)
  70. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  71. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  72.  
  73.  
  74. Init
  75. CreateMap 99, 74, 10
  76.     CLS , &HFF0000FF
  77.     DrawMap
  78.     DisplayCharacter
  79.     _DISPLAY
  80.     GetInput
  81.     MonstersTurn
  82.  
  83. SUB DisplayCharacter
  84.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  85.     COLOR &HFFFFFFFF, 0
  86.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  87.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  88.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  89.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  90.  
  91.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  92.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  93.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  94.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  95.  
  96. SUB Init
  97.     Hero.Name = "Steve The Tester!"
  98.     Hero.Life.Low = 10
  99.     Hero.Life.High = 10
  100.     Hero.Level = 1
  101.     Hero.EXP_Earned = 0
  102.     Hero.EXP_Needed = 2
  103.     Hero.Light.Name = "Magic Candle"
  104.     Hero.Light.Reach = 2
  105.     Hero.Light.Left = -1 'infinite
  106.     Hero.Weapon1.Name = "Bare Fist"
  107.     Hero.Weapon1.Reach = 1
  108.     Hero.Weapon1.Damage.Low = 1
  109.     Hero.Weapon1.Damage.High = 2
  110.     Hero.Weapon1.HitBonus = 0
  111.     Hero.Weapon1.DamageBonus = 0
  112.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  113.     Hero.Weapon2.Name = "Magic Candle"
  114.     Hero.Weapon2.Reach = 0
  115.     Hero.Weapon2.Damage.Low = 0
  116.     Hero.Weapon2.Damage.High = 0
  117.     Hero.Weapon2.HitBonus = 0
  118.     Hero.Weapon2.DamageBonus = 0
  119.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  120.     Hero.Armor.Name = "Naked"
  121.     Hero.Armor.PD = 0
  122.     Hero.Armor.DR = 0
  123.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  124.  
  125. SUB GetInput
  126.     DO
  127.         k = _KEYHIT
  128.         SELECT CASE k
  129.             CASE 18432 'up
  130.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  131.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  132.                         Hero.Y = Hero.Y - 1
  133.                         PathFind
  134.                     END IF
  135.                 END IF
  136.             CASE 19200 'left
  137.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  138.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  139.                         Hero.X = Hero.X - 1
  140.                         PathFind
  141.                     END IF
  142.                 END IF
  143.             CASE 20480 'down
  144.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  145.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  146.                         Hero.Y = Hero.Y + 1
  147.                         PathFind
  148.                     END IF
  149.                 END IF
  150.             CASE 19712 'right
  151.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  152.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  153.                         Hero.X = Hero.X + 1
  154.                         PathFind
  155.                     END IF
  156.                 END IF
  157.             CASE 32 'space to just wait and skip a turn
  158.             CASE 60 ' "<" key
  159.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  160.                     Level = Level + 1
  161.                     CreateMap 99, 74, 10
  162.                     PathFind
  163.                 END IF
  164.             CASE ASC("+"), ASC("=")
  165.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  166.             CASE ASC("-"), ASC("_")
  167.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  168.         END SELECT
  169.         _LIMIT 60
  170.     LOOP UNTIL k > 0
  171.     _KEYCLEAR 'one keystroke at a time
  172.  
  173. SUB MonstersTurn
  174.     '    Sight AS INTEGER
  175.     '    Hearing AS INTEGER
  176.     '    Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  177.  
  178.  
  179.  
  180.     FOR i = 1 TO EncounterLimit
  181.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  182.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  183.             D = Distance(MX, MY) 'distance from monster to the hero
  184.             E = Encounter(i).M 'the actual monster in question
  185.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  186.  
  187.                 attack = 0
  188.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  189.                     'insert attack code here
  190.  
  191.                     _TITLE "ATTACK!"
  192.                     _CONTINUE
  193.                 END IF
  194.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  195.                     'insert attack code here
  196.                     _CONTINUE
  197.                 END IF
  198.  
  199.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  200.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  201.                         IF D > Distance(MX - 1, MY) THEN
  202.                             Encounter(i).X = Encounter(i).X - 1 'move left
  203.                             _CONTINUE
  204.                         END IF
  205.                     END IF
  206.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  207.                         IF D > Distance(MX, MY - 1) THEN
  208.                             Encounter(i).Y = Encounter(i).Y - 1 'move up
  209.                             _CONTINUE
  210.                         END IF
  211.                     END IF
  212.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  213.                         IF D > Distance(MX + 1, MY) THEN
  214.                             Encounter(i).X = Encounter(i).X + 1 'move right
  215.                             _CONTINUE
  216.                         END IF
  217.                     END IF
  218.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  219.                         IF D > Distance(MX, MY + 1) THEN
  220.                             Encounter(i).Y = Encounter(i).Y + 1 'move down
  221.                             _CONTINUE
  222.                         END IF
  223.                     END IF
  224.                 END IF
  225.  
  226.  
  227.             END IF
  228.         END IF
  229.  
  230.     NEXT
  231.  
  232.  
  233.  
  234.  
  235. SUB Illuminate (Range)
  236.     FOR X = 0 TO XH
  237.         FOR Y = 0 TO YH
  238.             IF Distance(X, Y) <= Range THEN 'It's close enough to check for illumination
  239.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  240.             END IF
  241.         NEXT
  242.     NEXT
  243.  
  244.  
  245. SUB DrawMap
  246.     Illuminate Hero.Light.Reach
  247.     FOR Y = 0 TO YH
  248.         FOR X = 0 TO XH
  249.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  250.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  251.                     COLOR &HFF000000, 0
  252.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  253.                 END IF
  254.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  255.                     COLOR &HFF000000, &HFF777777
  256.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  257.                 END IF
  258.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  259.                     COLOR &HFF00FF00, &HFFFFFF00
  260.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  261.                 END IF
  262.             END IF
  263.             'note: highlighting for the light should come AFTER the map is drawn
  264.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  265.                 COLOR &H40FFFF00, 0
  266.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  267.                 MapArray(X, Y) = MapArray(X, Y) - 1
  268.                 FOR i = 1 TO EncounterLimit
  269.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  270.                         COLOR Monster(Encounter(i).M).ID.Color
  271.                         t$ = CHR$(Monster(Encounter(i).M).ID.Symbol)
  272.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), t$
  273.                     END IF
  274.                     Encounter(i).M = RandomMonster
  275.                 NEXT
  276.  
  277.             END IF
  278.         NEXT
  279.     NEXT
  280.     COLOR &HFFFFFF00, 0 'Yellow Hero
  281.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  282.  
  283.  
  284.  
  285.  
  286.  
  287. SUB CreateMap (XLimit, YLimit, Rooms)
  288.     ERASE MapArray 'clear the old map and reset everything to 0
  289.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  290.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  291.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  292.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  293.  
  294.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  295.  
  296.     FOR i = 1 TO Rooms
  297.         DO
  298.             RoomSize = INT(RND * 9) + 2
  299.             RoomX = INT(RND * (XLimit - RoomSize))
  300.             RoomY = INT(RND * (YLimit - RoomSize))
  301.             'test for positioning
  302.             good = -1 'it's good starting out
  303.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  304.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  305.             NEXT X, Y
  306.         LOOP UNTIL good
  307.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  308.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  309.         NEXT X, Y
  310.         RoomCenterX(i) = RoomX + .5 * RoomSize
  311.         RoomCenterY(i) = RoomY + .5 * RoomSize
  312.     NEXT
  313.     FOR i = 1 TO Rooms - 1
  314.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  315.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  316.         DO UNTIL StartX = EndX AND StartY = EndY
  317.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  318.             Meander = 10
  319.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  320.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  321.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  322.                     Ychange = 0
  323.                 ELSE
  324.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  325.                     Ychange = 0
  326.                 END IF
  327.             ELSE
  328.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  329.                     Ychange = SGN(EndY - StartY)
  330.                     XChange = 0
  331.                 ELSE
  332.                     Ychange = INT(RND * 3) - 1
  333.                     XChange = 0
  334.                 END IF
  335.             END IF
  336.             StartX = StartX + XChange
  337.             StartY = StartY + Ychange
  338.             IF StartX < 0 THEN StartX = 0
  339.             IF StartY < 0 THEN StartY = 0
  340.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  341.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  342.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  343.         LOOP
  344.     NEXT
  345.     DO
  346.         Hero.X = INT(RND * XLimit + 1)
  347.         Hero.Y = INT(RND * YLimit + 1)
  348.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  349.     DO
  350.         X = INT(RND * XLimit + 1)
  351.         Y = INT(RND * YLimit + 1)
  352.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  353.     MapArray(X, Y) = MapArray(X, Y) OR 16
  354.     PathFind
  355.     EncounterLimit = INT(RND * 6) + 5
  356.     FOR i = 1 TO EncounterLimit
  357.         Encounter(i).M = RandomMonster
  358.         Encounter(i).Active = -1
  359.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  360.         DO
  361.             Encounter(i).X = INT(RND * XLimit + 1)
  362.             Encounter(i).Y = INT(RND * YLimit + 1)
  363.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 'get a random spot in a room, for the stairs to the next level
  364.     NEXT
  365.  
  366. SUB PathFind
  367.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  368.     DIM pass AS _UNSIGNED _BYTE
  369.     m = _MEM(Distance()): m1 = _MEM(Temp())
  370.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  371.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  372.     Temp(Hero.X, Hero.Y) = 0
  373.     pass = 0
  374.     DO
  375.         changed = 0
  376.         y = 0
  377.         DO
  378.             x = 0
  379.             DO
  380.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  381.                     IF x < XH THEN
  382.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  383.                     END IF
  384.                     IF x > 0 THEN
  385.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  386.                     END IF
  387.                     IF y < YH THEN
  388.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  389.                     END IF
  390.                     IF y > 0 THEN
  391.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  392.                     END IF
  393.                 END IF
  394.                 x = x + 1
  395.             LOOP UNTIL x > XH
  396.             y = y + 1
  397.         LOOP UNTIL y > YH
  398.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  399.         pass = pass + 1
  400.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  401.     Distance(Hero.X, Hero.Y) = 0
  402.  
  403. FUNCTION RandomMonster
  404.     'Shared variable level tells us what level of the dungeon we're on.
  405.     STATIC MC, DS 'monster count and data set
  406.     IF NOT DS THEN
  407.         DS = -1
  408.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  409.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  410.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  411.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  412.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  413.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  414.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  415.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  416.         'Monster(1).Armor.Name = ""
  417.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  418.  
  419.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  420.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  421.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  422.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  423.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  424.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  425.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  426.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  427.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  428.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  429.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  430.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  431.         NEXT
  432.     END IF
  433.     SELECT CASE Level 'the starting level
  434.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  435.     END SELECT
  436.     RandomMonster = INT(RND * MC) + 1
  437.  

Our monsters are now detecting the hero and responding to him.  At the moment, they do nothing but move up and stand close enough to the hero to attack him, and they do absolutely nothing else...  After all, I still haven't added any actual combat routines in the game yet!  :P

Currently monsters will stack on top of each other, as they're completely unaware of each other's presence, but I plan on correcting that with the next update.  Movement should be limited to open tiles only; not ones where another creature is currently standing up; but I'm happy with what I've got here so far.  From what I can tell (sans combat), monster movement seems very similar to what we used to see in the actual game Rogue, waaay back in the day.  I still remember being beat up and almost dying, and then having to run backwards to take time to heal, all while hoping I didn't run into a second monster while the first one was still right on my heels....

:D
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #12 on: September 11, 2019, 11:05:58 am »
And the monsters now block each other and the hero's movement.  (As well as the hero blocking the monster's movement.)  At this point, there's still no combat, but you can have fun challenging yourself to a simple little game of, "How many levels can I run through before the monsters catch me and trap me in a corner...)  :P

Code: QB64: [Select]
  1. _CONSOLE ON 'for debugging purposes while making/testing things
  2.  
  3. TYPE Damage_Type
  4.     Low AS _UNSIGNED _BYTE
  5.     High AS _UNSIGNED _BYTE
  6.  
  7. TYPE Light_Type
  8.     Name AS STRING * 20
  9.     Reach AS _UNSIGNED _BYTE
  10.     Left AS _UNSIGNED _BYTE
  11.  
  12. TYPE Weapon_Type
  13.     Name AS STRING * 20
  14.     Reach AS _UNSIGNED _BYTE
  15.     Damage AS Damage_Type
  16.     HitBonus AS _UNSIGNED _BYTE
  17.     DamageBonus AS _UNSIGNED _BYTE
  18.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  19.  
  20. TYPE Armor_Type
  21.     Name AS STRING * 20
  22.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  23.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  24.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  25.  
  26.  
  27.  
  28. TYPE Hero_Type
  29.     Name AS STRING * 20
  30.     Life AS Damage_Type
  31.     Level AS _UNSIGNED _BYTE
  32.     EXP_Earned AS LONG
  33.     EXP_Needed AS LONG
  34.     Light AS Light_Type
  35.     Weapon1 AS Weapon_Type
  36.     Weapon2 AS Weapon_Type
  37.     Armor AS Armor_Type
  38.  
  39. TYPE Map_Identifer_TYPE
  40.     Symbol AS _UNSIGNED _BYTE
  41.  
  42. TYPE Monster_TYPE
  43.     Name AS STRING * 20
  44.     Life AS Damage_Type
  45.     Level AS INTEGER
  46.     ExpBonus AS INTEGER
  47.     Sight AS INTEGER
  48.     Hearing AS INTEGER
  49.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  50.     Weapon1 AS Weapon_Type
  51.     Weapon2 AS Weapon_Type
  52.     Armor AS Armor_Type
  53.     ID AS Map_Identifer_TYPE
  54.  
  55. TYPE Encounter_TYPE
  56.     Active AS INTEGER
  57.     X AS INTEGER
  58.     Y AS INTEGER
  59.     M AS INTEGER
  60.     Life AS INTEGER
  61.  
  62. REDIM SHARED Monster(100) AS Monster_TYPE
  63. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  64.  
  65. DIM SHARED Hero AS Hero_Type
  66. REDIM SHARED Monster(100) AS Monster_TYPE
  67. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  68. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  69.  
  70.  
  71. SCREEN _NEWIMAGE(800, 700, 32)
  72. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  73. '1 map is illuminated
  74. '2 map is uncovered
  75. '4 map is a wall
  76. '8 map is a pathway
  77. '16 map is a stairway
  78. '32 map is simply blocked (perhaps with a monster?)
  79. '64 map is secret (can not be uncovered)
  80.  
  81. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  82.  
  83.  
  84. Init
  85. CreateMap 99, 74, 10
  86.     CLS , &HFF0000FF
  87.     DrawMap
  88.     DisplayCharacter
  89.     _DISPLAY
  90.     GetInput
  91.     MonstersTurn
  92.  
  93. SUB DisplayCharacter
  94.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  95.     COLOR &HFFFFFFFF, 0
  96.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  97.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  98.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  99.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  100.  
  101.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  102.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  103.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  104.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  105.  
  106. SUB Init
  107.     Hero.Name = "Steve The Tester!"
  108.     Hero.Life.Low = 10
  109.     Hero.Life.High = 10
  110.     Hero.Level = 1
  111.     Hero.EXP_Earned = 0
  112.     Hero.EXP_Needed = 2
  113.     Hero.Light.Name = "Magic Candle"
  114.     Hero.Light.Reach = 2
  115.     Hero.Light.Left = -1 'infinite
  116.     Hero.Weapon1.Name = "Bare Fist"
  117.     Hero.Weapon1.Reach = 1
  118.     Hero.Weapon1.Damage.Low = 1
  119.     Hero.Weapon1.Damage.High = 2
  120.     Hero.Weapon1.HitBonus = 0
  121.     Hero.Weapon1.DamageBonus = 0
  122.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  123.     Hero.Weapon2.Name = "Magic Candle"
  124.     Hero.Weapon2.Reach = 0
  125.     Hero.Weapon2.Damage.Low = 0
  126.     Hero.Weapon2.Damage.High = 0
  127.     Hero.Weapon2.HitBonus = 0
  128.     Hero.Weapon2.DamageBonus = 0
  129.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  130.     Hero.Armor.Name = "Naked"
  131.     Hero.Armor.PD = 0
  132.     Hero.Armor.DR = 0
  133.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  134.  
  135. SUB GetInput
  136.     DO
  137.         k = _KEYHIT: valid = -1
  138.         SELECT CASE k
  139.             CASE 18432: IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  140.             CASE 19200: IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  141.             CASE 20480: IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  142.             CASE 19712: IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  143.             CASE 32 'space to just wait and skip a turn
  144.             CASE 60 ' "<" key
  145.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  146.                     Level = Level + 1
  147.                     CreateMap 99, 74, 10
  148.                     PathFind
  149.                 END IF
  150.             CASE ASC("+"), ASC("=")
  151.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  152.             CASE ASC("-"), ASC("_")
  153.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  154.             CASE ELSE
  155.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  156.         END SELECT
  157.         _LIMIT 60
  158.     LOOP UNTIL k AND valid
  159.     _KEYCLEAR 'one keystroke at a time
  160.  
  161. SUB MoveHero (MoveX, MoveY)
  162.     IF MapArray(Hero.X + MoveX, Hero.Y + MoveY) AND (4 OR 8) THEN 'and it's a room or passageway
  163.         IF (MapArray(Hero.X + MoveX, Hero.Y + MoveY) AND 32) = 0 THEN 'and it's not blocked for some reason
  164.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  165.             IF MoveX THEN Hero.X = Hero.X + MoveX
  166.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  167.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  168.             PathFind
  169.         END IF
  170.     END IF
  171.  
  172.  
  173. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  174.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  175.     D = Distance(MX, MY) 'distance from monster to the hero
  176.     E = Encounter(i).M 'the actual monster in question
  177.  
  178.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  179.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  180.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  181.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  182.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  183.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  184.             MoveMonster = -1
  185.         END IF
  186.     END IF
  187.  
  188.  
  189.  
  190. SUB MonstersTurn
  191.     FOR i = 1 TO EncounterLimit
  192.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  193.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  194.             D = Distance(MX, MY) 'distance from monster to the hero
  195.             E = Encounter(i).M 'the actual monster in question
  196.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  197.  
  198.                 attack = 0
  199.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  200.                     'insert attack code here
  201.  
  202.                     _TITLE "ATTACK!"
  203.                     _CONTINUE
  204.                 END IF
  205.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  206.                     'insert attack code here
  207.                     _CONTINUE
  208.                 END IF
  209.  
  210.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  211.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  212.                         IF D > Distance(MX - 1, MY) THEN
  213.                             IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  214.                         END IF
  215.                     END IF
  216.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  217.                         IF D > Distance(MX, MY - 1) THEN
  218.                             IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  219.                         END IF
  220.                     END IF
  221.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  222.                         IF D > Distance(MX + 1, MY) THEN
  223.                             IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  224.                         END IF
  225.                     END IF
  226.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  227.                         IF D > Distance(MX, MY + 1) THEN
  228.                             IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  229.                         END IF
  230.                     END IF
  231.                 END IF
  232.             END IF
  233.         END IF
  234.  
  235.     NEXT
  236.  
  237.  
  238.  
  239.  
  240. SUB DrawMap
  241.     FOR Y = 0 TO YH
  242.         FOR X = 0 TO XH
  243.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  244.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  245.             END IF
  246.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  247.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  248.                     COLOR &HFF000000, 0
  249.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  250.                 END IF
  251.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  252.                     COLOR &HFF000000, &HFF777777
  253.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  254.                 END IF
  255.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  256.                     COLOR &HFF00FF00, &HFFFFFF00
  257.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  258.                 END IF
  259.             END IF
  260.             'note: highlighting for the light should come AFTER the map is drawn
  261.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  262.                 COLOR &H40FFFF00, 0
  263.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  264.                 MapArray(X, Y) = MapArray(X, Y) - 1
  265.                 FOR i = 1 TO EncounterLimit
  266.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  267.                         E = Encounter(i).M
  268.                         COLOR Monster(E).ID.Color
  269.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(Monster(E).ID.Symbol)
  270.                     END IF
  271.                 NEXT
  272.  
  273.             END IF
  274.         NEXT
  275.     NEXT
  276.     COLOR &HFFFFFF00, 0 'Yellow Hero
  277.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  278.  
  279.  
  280.  
  281.  
  282.  
  283. SUB CreateMap (XLimit, YLimit, Rooms)
  284.     ERASE MapArray 'clear the old map and reset everything to 0
  285.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  286.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  287.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  288.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  289.  
  290.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  291.  
  292.     FOR i = 1 TO Rooms
  293.         DO
  294.             RoomSize = INT(RND * 9) + 2
  295.             RoomX = INT(RND * (XLimit - RoomSize))
  296.             RoomY = INT(RND * (YLimit - RoomSize))
  297.             'test for positioning
  298.             good = -1 'it's good starting out
  299.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  300.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  301.             NEXT X, Y
  302.         LOOP UNTIL good
  303.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  304.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  305.         NEXT X, Y
  306.         RoomCenterX(i) = RoomX + .5 * RoomSize
  307.         RoomCenterY(i) = RoomY + .5 * RoomSize
  308.     NEXT
  309.     FOR i = 1 TO Rooms - 1
  310.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  311.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  312.         DO UNTIL StartX = EndX AND StartY = EndY
  313.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  314.             Meander = 10
  315.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  316.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  317.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  318.                     Ychange = 0
  319.                 ELSE
  320.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  321.                     Ychange = 0
  322.                 END IF
  323.             ELSE
  324.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  325.                     Ychange = SGN(EndY - StartY)
  326.                     XChange = 0
  327.                 ELSE
  328.                     Ychange = INT(RND * 3) - 1
  329.                     XChange = 0
  330.                 END IF
  331.             END IF
  332.             StartX = StartX + XChange
  333.             StartY = StartY + Ychange
  334.             IF StartX < 0 THEN StartX = 0
  335.             IF StartY < 0 THEN StartY = 0
  336.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  337.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  338.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  339.         LOOP
  340.     NEXT
  341.     DO
  342.         Hero.X = INT(RND * XLimit + 1)
  343.         Hero.Y = INT(RND * YLimit + 1)
  344.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  345.     MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  346.     DO
  347.         X = INT(RND * XLimit + 1)
  348.         Y = INT(RND * YLimit + 1)
  349.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  350.     MapArray(X, Y) = MapArray(X, Y) OR 16
  351.     PathFind
  352.     EncounterLimit = INT(RND * 6) + 5
  353.     FOR i = 1 TO EncounterLimit
  354.         Encounter(i).M = RandomMonster
  355.         Encounter(i).Active = -1
  356.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  357.         valid = -1
  358.         DO
  359.             Encounter(i).X = INT(RND * XLimit + 1)
  360.             Encounter(i).Y = INT(RND * YLimit + 1)
  361.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  362.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  363.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  364.     NEXT
  365.  
  366. SUB PathFind
  367.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  368.     DIM pass AS _UNSIGNED _BYTE
  369.     m = _MEM(Distance()): m1 = _MEM(Temp())
  370.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  371.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  372.     Temp(Hero.X, Hero.Y) = 0
  373.     pass = 0
  374.     DO
  375.         changed = 0
  376.         y = 0
  377.         DO
  378.             x = 0
  379.             DO
  380.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  381.                     IF x < XH THEN
  382.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  383.                     END IF
  384.                     IF x > 0 THEN
  385.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  386.                     END IF
  387.                     IF y < YH THEN
  388.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  389.                     END IF
  390.                     IF y > 0 THEN
  391.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  392.                     END IF
  393.                 END IF
  394.                 x = x + 1
  395.             LOOP UNTIL x > XH
  396.             y = y + 1
  397.         LOOP UNTIL y > YH
  398.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  399.         pass = pass + 1
  400.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  401.     Distance(Hero.X, Hero.Y) = 0
  402.  
  403. FUNCTION RandomMonster
  404.     'Shared variable level tells us what level of the dungeon we're on.
  405.     STATIC MC, DS 'monster count and data set
  406.     IF NOT DS THEN
  407.         DS = -1
  408.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  409.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  410.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  411.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  412.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  413.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  414.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  415.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  416.         'Monster(1).Armor.Name = ""
  417.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  418.  
  419.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  420.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  421.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  422.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  423.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  424.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  425.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  426.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  427.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  428.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  429.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  430.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  431.         NEXT
  432.     END IF
  433.     SELECT CASE Level 'the starting level
  434.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  435.     END SELECT
  436.     RandomMonster = INT(RND * MC) + 1

To still be coming in at less than 500 lines total, I'm quite impressed with what all this little code is actually doing so far.  It's got lighting, navigation, trivial monster AI, random level generation, and basic collision detection.  Basic combat should be the next element I add into the game, so expect to see us being able to actually kill those mysterious monsters soon(tm)!  (Or be killed by them...)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Rogue-Like (work in progress)
« Reply #13 on: September 11, 2019, 02:39:38 pm »
Our hero can now attack, but the monsters can't attack back. Soon(tm) they shall though!

Code: QB64: [Select]
  1. TYPE TextArea
  2.     InUse AS INTEGER
  3.     x1 AS LONG 'left
  4.     y1 AS LONG 'top
  5.     w AS LONG 'width
  6.     h AS LONG 'height
  7.     FrameColor AS _UNSIGNED LONG
  8.     BackColor AS _UNSIGNED LONG
  9.     Xpos AS INTEGER
  10.     Ypos AS INTEGER
  11.     VerticalAlignment AS INTEGER
  12.     Justification AS INTEGER
  13.     UpdateMethod AS INTEGER
  14.     TextColor AS _UNSIGNED LONG
  15.     TextBackgroundColor AS _UNSIGNED LONG
  16.     SavedBackground AS INTEGER
  17.     HideFrame AS INTEGER
  18.     ScreenX AS INTEGER
  19.     ScreenY AS INTEGER
  20.  
  21. REDIM SHARED TextHandles(0) AS TextArea
  22.  
  23. CONST True = -1, False = 0
  24. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  25. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  26. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  27. '********************************************************
  28. '* Text Frames before this line
  29. '********************************************************
  30.  
  31.  
  32.  
  33.  
  34.  
  35.  
  36. _CONSOLE ON 'for debugging purposes while making/testing things
  37.  
  38. TYPE Damage_Type
  39.     Low AS INTEGER
  40.     High AS INTEGER
  41.  
  42. TYPE Light_Type
  43.     Name AS STRING * 20
  44.     Reach AS _UNSIGNED _BYTE
  45.     Left AS _UNSIGNED _BYTE
  46.  
  47. TYPE Weapon_Type
  48.     Name AS STRING * 20
  49.     Reach AS _UNSIGNED _BYTE
  50.     Damage AS Damage_Type
  51.     HitBonus AS _UNSIGNED _BYTE
  52.     DamageBonus AS _UNSIGNED _BYTE
  53.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  54.  
  55. TYPE Armor_Type
  56.     Name AS STRING * 20
  57.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  58.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  59.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  60.  
  61. TYPE Hero_Type
  62.     Name AS STRING * 20
  63.     Life AS Damage_Type
  64.     Level AS _UNSIGNED _BYTE
  65.     EXP_Earned AS LONG
  66.     EXP_Needed AS LONG
  67.     Light AS Light_Type
  68.     Weapon1 AS Weapon_Type
  69.     Weapon2 AS Weapon_Type
  70.     Armor AS Armor_Type
  71.  
  72. TYPE Map_Identifer_TYPE
  73.     Symbol AS _UNSIGNED _BYTE
  74.  
  75. TYPE Monster_TYPE
  76.     Name AS STRING * 20
  77.     Life AS Damage_Type
  78.     Level AS INTEGER
  79.     ExpBonus AS INTEGER
  80.     Sight AS INTEGER
  81.     Hearing AS INTEGER
  82.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  83.     Weapon1 AS Weapon_Type
  84.     Weapon2 AS Weapon_Type
  85.     Armor AS Armor_Type
  86.     ID AS Map_Identifer_TYPE
  87.  
  88. TYPE Encounter_TYPE
  89.     Active AS INTEGER
  90.     X AS INTEGER
  91.     Y AS INTEGER
  92.     M AS INTEGER
  93.     Life AS INTEGER
  94.  
  95. REDIM SHARED Monster(100) AS Monster_TYPE
  96. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  97.  
  98. DIM SHARED Hero AS Hero_Type
  99. REDIM SHARED Monster(100) AS Monster_TYPE
  100. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  101. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  102. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  103.  
  104. SCREEN _NEWIMAGE(800, 700, 32)
  105. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  106. '1 map is illuminated
  107. '2 map is uncovered
  108. '4 map is a wall
  109. '8 map is a pathway
  110. '16 map is a stairway
  111. '32 map is simply blocked (perhaps with a monster?)
  112. '64 map is secret (can not be uncovered)
  113.  
  114. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  115.  
  116.  
  117. Init
  118. CreateMap 99, 74, 10
  119.  
  120.     DrawMap
  121.     DisplayCharacter
  122.  
  123.     GetInput
  124.     MonstersTurn
  125.     CheckForHeroGrowth
  126.  
  127. SUB Init
  128.     Hero.Name = "Steve The Tester!"
  129.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  130.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  131.     Hero.Light.Name = "Magic Candle"
  132.     Hero.Light.Reach = 2: Hero.Light.Left = -1 'infinite
  133.     Hero.Weapon1.Name = "Bare Fist"
  134.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  135.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  136.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  137.     Hero.Weapon2.Name = "Magic Candle"
  138.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  139.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  140.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  141.     Hero.Armor.Name = "Naked"
  142.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  143.  
  144.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  145.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  146.     DrawTextArea PrintArea
  147.     SetPrintPositionX PrintArea, CenterJustify
  148.     SetPrintUpdate PrintArea, NewLine
  149.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  150.     PrintOut PrintArea, "created by STEVE!"
  151.     PrintOut PrintArea, ""
  152.     SetPrintPositionX PrintArea, LeftJustify
  153.  
  154. SUB CheckForHeroGrowth
  155.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  156.         BEEP
  157.         CLS
  158.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  159.         _DELAY 5
  160.         SYSTEM
  161.     END IF
  162.  
  163.  
  164. SUB DisplayCharacter
  165.     LINE (0, 601)-(229, 799), &HFF000000, BF
  166.     COLOR &HFFFFFFFF, 0
  167.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  168.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  169.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  170.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  171.  
  172.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  173.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  174.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  175.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  176.  
  177. SUB GetInput
  178.     DO
  179.         k = _KEYHIT: valid = -1
  180.         SELECT CASE k
  181.             CASE 18432: IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  182.             CASE 19200: IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  183.             CASE 20480: IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  184.             CASE 19712: IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  185.             CASE 32 'space to just wait and skip a turn
  186.             CASE 60 ' "<" key
  187.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  188.                     Level = Level + 1
  189.                     CreateMap 99, 74, 10
  190.                     PathFind
  191.                 END IF
  192.             CASE ASC("+"), ASC("=")
  193.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  194.             CASE ASC("-"), ASC("_")
  195.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  196.             CASE ELSE
  197.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  198.         END SELECT
  199.         _LIMIT 60
  200.     LOOP UNTIL k AND valid
  201.     _KEYCLEAR 'one keystroke at a time
  202.  
  203. SUB MoveHero (MoveX, MoveY)
  204.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  205.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  206.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  207.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  208.             IF MoveX THEN Hero.X = Hero.X + MoveX
  209.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  210.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  211.             PathFind
  212.         ELSE
  213.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  214.             FOR i = 1 TO EncounterLimit
  215.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  216.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  217.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  218.                         Swing 0, i, 1 'swing with the right hand
  219.                     END IF
  220.                 END IF
  221.             NEXT
  222.         END IF
  223.     END IF
  224.  
  225. SUB Swing (Who, AtWhom, HandUsed)
  226.     M = Encounter(AtWhom).M
  227.     BaseChancetohit = 10 'base 10 chance to hit
  228.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  229.         IF Hero.Weapon1.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  230.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  231.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  232.             totalroll = 0
  233.             DO
  234.                 roll = INT(RND * 20) + 1
  235.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  236.                 IF roll = 20 THEN totalroll = totalroll + 20
  237.                 totalroll = totalroll + roll
  238.             LOOP UNTIL roll <> 1 AND roll <> 20
  239.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  240.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  241.             out$ = _TRIM$(Hero.Name)
  242.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  243.                 SetTextColor PrintArea, &HFFF000F0, 0
  244.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  245.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  246.                 Hero.Life.Low = Hero.Life.Low - damage
  247.             ELSEIF totalroll < Chancetohit THEN
  248.                 SetTextColor PrintArea, &HFFF0F000, 0
  249.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  250.             ELSEIF totalroll > Chancetohit + 20 THEN
  251.                 SetTextColor PrintArea, &HFF00FF00, 0
  252.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  253.                 damage = damage * (totalroll \ 20 + 1)
  254.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  255.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  256.             ELSEIF totalroll >= Chancetohit THEN
  257.                 SetTextColor PrintArea, &HFF00FF00, 0
  258.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  259.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  260.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  261.             END IF
  262.             PrintOut PrintArea, out$
  263.         END IF
  264.         IF Hero.Weapon2.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  265.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  266.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  267.             totalroll = 0
  268.             DO
  269.                 roll = INT(RND * 20) + 1
  270.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  271.                 IF roll = 20 THEN totalroll = totalroll + 20
  272.                 totalroll = totalroll + roll
  273.             LOOP UNTIL roll <> 1 AND roll <> 20
  274.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  275.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  276.             out$ = _TRIM$(Hero.Name)
  277.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  278.                 SetTextColor PrintArea, &HFFF000F0, 0
  279.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  280.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  281.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  282.                 Hero.Life.Low = Hero.Life.Low - damage
  283.             ELSEIF totalroll < Chancetohit THEN
  284.                 SetTextColor PrintArea, &HFFF0F000, 0
  285.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  286.             ELSEIF totalroll > Chancetohit + 20 THEN
  287.                 SetTextColor PrintArea, &HFF00FF00, 0
  288.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  289.                 damage = damage * (totalroll \ 20 + 1)
  290.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  291.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  292.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  293.             ELSEIF totalroll >= Chancetohit THEN
  294.                 SetTextColor PrintArea, &HFF00FF00, 0
  295.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  296.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  297.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  298.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  299.             END IF
  300.             PrintOut PrintArea, out$
  301.         END IF
  302.  
  303.         IF Encounter(AtWhom).Life <= 0 THEN 'the monster died!
  304.             SetTextColor PrintArea, &HFFFF0000, 0
  305.             out$ = _TRIM$(Monster(M).Name) + " died!"
  306.             PrintOut PrintArea, out$
  307.             Encounter(AtWhom).Active = 0
  308.             Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  309.             MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) = MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  310.         END IF
  311.     ELSE 'it's a monster attacking
  312.  
  313.     END IF
  314.  
  315.  
  316. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  317.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  318.     D = Distance(MX, MY) 'distance from monster to the hero
  319.     E = Encounter(i).M 'the actual monster in question
  320.  
  321.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  322.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  323.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  324.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  325.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  326.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  327.             MoveMonster = -1
  328.         END IF
  329.     END IF
  330.  
  331.  
  332.  
  333. SUB MonstersTurn
  334.     FOR i = 1 TO EncounterLimit
  335.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  336.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  337.             D = Distance(MX, MY) 'distance from monster to the hero
  338.             E = Encounter(i).M 'the actual monster in question
  339.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  340.  
  341.                 attack = 0
  342.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  343.                     'insert attack code here
  344.  
  345.                     _TITLE "ATTACK!"
  346.                     _CONTINUE
  347.                 END IF
  348.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  349.                     'insert attack code here
  350.                     _CONTINUE
  351.                 END IF
  352.  
  353.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  354.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  355.                         IF D > Distance(MX - 1, MY) THEN
  356.                             IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  357.                         END IF
  358.                     END IF
  359.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  360.                         IF D > Distance(MX, MY - 1) THEN
  361.                             IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  362.                         END IF
  363.                     END IF
  364.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  365.                         IF D > Distance(MX + 1, MY) THEN
  366.                             IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  367.                         END IF
  368.                     END IF
  369.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  370.                         IF D > Distance(MX, MY + 1) THEN
  371.                             IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  372.                         END IF
  373.                     END IF
  374.                 END IF
  375.             END IF
  376.         END IF
  377.  
  378.     NEXT
  379.  
  380.  
  381.  
  382.  
  383. SUB DrawMap
  384.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  385.     FOR Y = 0 TO YH
  386.         FOR X = 0 TO XH
  387.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  388.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  389.             END IF
  390.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  391.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  392.                     COLOR &HFF000000, 0
  393.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  394.                 END IF
  395.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  396.                     COLOR &HFF000000, &HFF777777
  397.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  398.                 END IF
  399.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  400.                     COLOR &HFF00FF00, &HFFFFFF00
  401.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  402.                 END IF
  403.             END IF
  404.             'note: highlighting for the light should come AFTER the map is drawn
  405.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  406.                 COLOR &H40FFFF00, 0
  407.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  408.                 MapArray(X, Y) = MapArray(X, Y) - 1
  409.                 FOR i = 1 TO EncounterLimit
  410.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  411.                         E = Encounter(i).M
  412.                         COLOR Monster(E).ID.Color
  413.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(Monster(E).ID.Symbol)
  414.                     END IF
  415.                 NEXT
  416.  
  417.             END IF
  418.         NEXT
  419.     NEXT
  420.     COLOR &HFFFFFF00, 0 'Yellow Hero
  421.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  422.  
  423.  
  424.  
  425.  
  426.  
  427. SUB CreateMap (XLimit, YLimit, Rooms)
  428.     ERASE MapArray 'clear the old map and reset everything to 0
  429.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  430.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  431.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  432.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  433.  
  434.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  435.  
  436.     FOR i = 1 TO Rooms
  437.         DO
  438.             RoomSize = INT(RND * 9) + 2
  439.             RoomX = INT(RND * (XLimit - RoomSize))
  440.             RoomY = INT(RND * (YLimit - RoomSize))
  441.             'test for positioning
  442.             good = -1 'it's good starting out
  443.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  444.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  445.             NEXT X, Y
  446.         LOOP UNTIL good
  447.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  448.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  449.         NEXT X, Y
  450.         RoomCenterX(i) = RoomX + .5 * RoomSize
  451.         RoomCenterY(i) = RoomY + .5 * RoomSize
  452.     NEXT
  453.     FOR i = 1 TO Rooms - 1
  454.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  455.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  456.         DO UNTIL StartX = EndX AND StartY = EndY
  457.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  458.             Meander = 10
  459.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  460.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  461.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  462.                     Ychange = 0
  463.                 ELSE
  464.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  465.                     Ychange = 0
  466.                 END IF
  467.             ELSE
  468.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  469.                     Ychange = SGN(EndY - StartY)
  470.                     XChange = 0
  471.                 ELSE
  472.                     Ychange = INT(RND * 3) - 1
  473.                     XChange = 0
  474.                 END IF
  475.             END IF
  476.             StartX = StartX + XChange
  477.             StartY = StartY + Ychange
  478.             IF StartX < 0 THEN StartX = 0
  479.             IF StartY < 0 THEN StartY = 0
  480.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  481.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  482.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  483.         LOOP
  484.     NEXT
  485.     DO
  486.         Hero.X = INT(RND * XLimit + 1)
  487.         Hero.Y = INT(RND * YLimit + 1)
  488.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  489.     MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  490.     DO
  491.         X = INT(RND * XLimit + 1)
  492.         Y = INT(RND * YLimit + 1)
  493.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  494.     MapArray(X, Y) = MapArray(X, Y) OR 16
  495.     PathFind
  496.     EncounterLimit = INT(RND * 6) + 5
  497.     FOR i = 1 TO EncounterLimit
  498.         Encounter(i).M = RandomMonster
  499.         Encounter(i).Active = -1
  500.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  501.         valid = -1
  502.         DO
  503.             Encounter(i).X = INT(RND * XLimit + 1)
  504.             Encounter(i).Y = INT(RND * YLimit + 1)
  505.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  506.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  507.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  508.     NEXT
  509.  
  510. SUB PathFind
  511.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  512.     DIM pass AS _UNSIGNED _BYTE
  513.     m = _MEM(Distance()): m1 = _MEM(Temp())
  514.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  515.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  516.     Temp(Hero.X, Hero.Y) = 0
  517.     pass = 0
  518.     DO
  519.         changed = 0
  520.         y = 0
  521.         DO
  522.             x = 0
  523.             DO
  524.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  525.                     IF x < XH THEN
  526.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  527.                     END IF
  528.                     IF x > 0 THEN
  529.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  530.                     END IF
  531.                     IF y < YH THEN
  532.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  533.                     END IF
  534.                     IF y > 0 THEN
  535.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  536.                     END IF
  537.                 END IF
  538.                 x = x + 1
  539.             LOOP UNTIL x > XH
  540.             y = y + 1
  541.         LOOP UNTIL y > YH
  542.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  543.         pass = pass + 1
  544.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  545.     Distance(Hero.X, Hero.Y) = 0
  546.  
  547. FUNCTION RandomMonster
  548.     'Shared variable level tells us what level of the dungeon we're on.
  549.     STATIC MC, DS 'monster count and data set
  550.     IF NOT DS THEN
  551.         DS = -1
  552.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  553.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  554.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  555.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  556.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  557.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  558.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  559.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  560.         'Monster(1).Armor.Name = ""
  561.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  562.  
  563.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  564.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  565.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  566.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  567.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  568.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  569.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  570.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  571.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  572.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  573.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  574.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  575.         NEXT
  576.     END IF
  577.     SELECT CASE Level 'the starting level
  578.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  579.     END SELECT
  580.     RandomMonster = INT(RND * MC) + 1
  581.  
  582.  
  583. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  584. '# SUBroutines and FUNCTIONs below #'
  585. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  586. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  587.     u = UBOUND(TextHandles)
  588.     Handle = WhichHandle
  589.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  590.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  591.     Where = TextHandles(Handle).VerticalAlignment
  592.     How = TextHandles(Handle).Justification
  593.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  594.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  595.  
  596.  
  597. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  598.     'WhichHandle is the handle which designates which text area we want to use
  599.     'Where is where we want it to go in that text area
  600.     '  -- Online prints the text to the current print position line in that text area.
  601.     '  -- CenterLine centers the text to the center of that text area.
  602.     '  -- any other value will print to that line positon in that particular box.
  603.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  604.     'What is the text that we want to print in our text area
  605.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  606.  
  607.     D = _DEST: S = _SOURCE
  608.  
  609.  
  610.     u = UBOUND(TextHandles)
  611.     fh = _FONTHEIGHT
  612.     pw = _PRINTWIDTH(What)
  613.     Handle = WhichHandle
  614.  
  615.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  616.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  617.     IF TextHandles(Handle).HideFrame THEN
  618.         _DEST TextHandles(Handle).SavedBackground
  619.         _SOURCE TextHandles(Handle).SavedBackground
  620.     END IF
  621.     h = TextHandles(Handle).h - 4
  622.     w = TextHandles(Handle).w - 4
  623.  
  624.     SELECT CASE Where
  625.         CASE BottomLine
  626.             y = h \ fh
  627.         CASE OnLine
  628.             y = TextHandles(Handle).Ypos
  629.             IF y = 0 THEN y = 1
  630.         CASE CenterLine
  631.             linesused = 0
  632.             tpw = pw: tw = w: tWhat$ = What
  633.             DO UNTIL tpw <= tw
  634.                 textallowed = WordBreak(LEFT$(tWhat$, w \ _FONTWIDTH))
  635.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  636.                 linesused = linesused + 1
  637.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  638.                 tpw = _PRINTWIDTH(tWhat$)
  639.             LOOP
  640.             linesused = linesused + 1
  641.             py = (h - linesused * fh) \ 2
  642.             y = py \ fh + 1
  643.             IF y < 1 THEN y = 1
  644.         CASE ELSE
  645.             y = Where
  646.     END SELECT
  647.  
  648.     IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  649.     blend = _BLEND
  650.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  651.         'first let's get a temp image handle for the existing area of the screen.
  652.         x1 = TextHandles(Handle).x1 + 2
  653.         y1 = TextHandles(Handle).y1 + 2
  654.         x2 = TextHandles(Handle).x1 + w
  655.         y2 = TextHandles(Handle).y1 + h
  656.         nh = y2 - y1 + 1 - fh
  657.         nw = x2 - x1 + 1
  658.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  659.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  660.         DrawTextArea Handle
  661.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  662.         y = y - 1
  663.     LOOP
  664.     IF blend THEN _BLEND
  665.  
  666.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  667.  
  668.     SELECT CASE How
  669.         CASE LeftJustify
  670.             x = 0
  671.             IF pw > w THEN
  672.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  673.                 text$ = RTRIM$(LEFT$(What, textallowed))
  674.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  675.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  676.             ELSE
  677.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  678.                 finished = -1
  679.             END IF
  680.         CASE CenterJustify
  681.             IF pw > w THEN
  682.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  683.                 text$ = RTRIM$(LEFT$(What, textallowed))
  684.                 x = (w - _PRINTWIDTH(text$)) \ 2
  685.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  686.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  687.             ELSE
  688.                 x = (w - pw) \ 2
  689.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  690.                 finished = -1
  691.             END IF
  692.         CASE RightJustify
  693.             IF pw > w THEN
  694.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  695.                 text$ = RTRIM$(LEFT$(What, textallowed))
  696.                 x = w - _PRINTWIDTH(text$)
  697.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  698.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  699.             ELSE
  700.                 x = w - pw
  701.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  702.                 finished = -1
  703.             END IF
  704.         CASE NoJustify
  705.             x = TextHandles(Handle).Xpos
  706.             firstlinelimit = (w - x) \ _FONTWIDTH 'the limit of characters on the first line
  707.             IF LEN(What) > firstlinelimit THEN
  708.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  709.                 text$ = RTRIM$(LEFT$(What, textallowed))
  710.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  711.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  712.             ELSE
  713.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  714.                 finished = -1
  715.             END IF
  716.     END SELECT
  717.  
  718.     IF finished THEN
  719.         SELECT CASE TextHandles(Handle).UpdateMethod
  720.             CASE NoUpdate 'We don't update the position at all.
  721.             CASE DoUpdate
  722.                 TextHandles(Handle).Xpos = x + pw
  723.                 TextHandles(Handle).Ypos = y
  724.             CASE NewLine
  725.                 TextHandles(Handle).Ypos = y + 1
  726.                 TextHandles(Handle).Xpos = 1
  727.         END SELECT
  728.         _DEST D: _SOURCE S
  729.         COLOR FG, BG
  730.     END IF
  731.  
  732. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  733.     u = UBOUND(TextHandles)
  734.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  735.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  736.     TextHandles(Handle).TextColor = Foreground
  737.  
  738.  
  739. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  740.     u = UBOUND(TextHandles)
  741.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  742.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  743.     TextHandles(Handle).TextBackgroundColor = Background
  744.  
  745.  
  746.  
  747. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  748.     u = UBOUND(TextHandles)
  749.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  750.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  751.     TextHandles(Handle).TextColor = Foreground
  752.     TextHandles(Handle).TextBackgroundColor = Background
  753.  
  754.  
  755. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  756.     u = UBOUND(TextHandles)
  757.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  758.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  759.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  760.     TextHandles(Handle).UpdateMethod = Method
  761.  
  762.  
  763. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  764.     u = UBOUND(TextHandles)
  765.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  766.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  767.     SELECT CASE Y
  768.         CASE BottomLine
  769.             TextHandles(Handle).VerticalAlignment = -2
  770.         CASE CenterLine
  771.             TextHandles(Handle).VerticalAlignment = -1
  772.         CASE ELSE
  773.             TextHandles(Handle).VerticalAlignment = 0
  774.     END SELECT
  775.     IF X < 1 AND X > -4 THEN
  776.         TextHandles(Handle).Justification = X
  777.     ELSE
  778.         TextHandles(Handle).Xpos = X
  779.     END IF
  780.     IF Y < 1 THEN EXIT SUB
  781.     TextHandles(Handle).Ypos = Y
  782.  
  783. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  784.     u = UBOUND(TextHandles)
  785.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  786.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  787.     IF X < 1 AND X > -4 THEN
  788.         TextHandles(Handle).Justification = X
  789.     ELSE
  790.         TextHandles(Handle).Xpos = X
  791.     END IF
  792.  
  793. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  794.     u = UBOUND(TextHandles)
  795.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  796.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  797.     SELECT CASE Y
  798.         CASE BottomLine
  799.             TextHandles(Handle).VerticalAlignment = -2
  800.         CASE CenterLine
  801.             TextHandles(Handle).VerticalAlignment = -1
  802.         CASE ELSE
  803.             TextHandles(Handle).VerticalAlignment = 0
  804.     END SELECT
  805.     IF Y < 1 THEN EXIT SUB
  806.     TextHandles(Handle).Ypos = Y
  807.  
  808.  
  809. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  810.     u = UBOUND(TextHandles)
  811.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  812.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  813.     GetPrintPositionY = TextHandles(Handle).Ypos
  814.  
  815. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  816.     u = UBOUND(TextHandles)
  817.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  818.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  819.     GetPrintPositionX = TextHandles(Handle).Xpos
  820.  
  821.  
  822.  
  823. FUNCTION WordBreak (text$)
  824.     CONST Breaks = " ;,.?!-"
  825.     FOR i = LEN(text$) TO 0 STEP -1
  826.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  827.         loopcount = loopcount + 1
  828.     NEXT
  829.     IF i = 0 THEN i = LEN(text$)
  830.     WordBreak = i
  831.  
  832.  
  833.  
  834. SUB ClearTextArea (Handle AS INTEGER)
  835.     u = UBOUND(TextHandles)
  836.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  837.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  838.     IF TextHandles(Handle).SavedBackground THEN
  839.         w = TextHandles(Handle).w
  840.         h = TextHandles(Handle).h
  841.         x1 = TextHandles(Handle).ScreenX
  842.         y1 = TextHandles(Handle).ScreenY
  843.         x2 = x1 + w - 1
  844.         y2 = y1 + h - 1
  845.         blend = _BLEND
  846.         _DONTBLEND
  847.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  848.         IF blend THEN _BLEND
  849.     END IF
  850.     DrawTextArea Handle
  851.  
  852.  
  853.  
  854. SUB DrawTextArea (Handle AS INTEGER)
  855.     u = UBOUND(TextHandles)
  856.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  857.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  858.     w = TextHandles(Handle).w
  859.     h = TextHandles(Handle).h
  860.     x1 = TextHandles(Handle).ScreenX
  861.     y1 = TextHandles(Handle).ScreenY
  862.     x2 = x1 + w - 1
  863.     y2 = y1 + h - 1
  864.  
  865.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  866.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  867.  
  868.  
  869.  
  870. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  871.     u = UBOUND(TextHandles)
  872.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  873.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  874.     TextHandles(Handle).FrameColor = FrameColor
  875.     TextHandles(Handle).BackColor = BackColor
  876.  
  877.  
  878.  
  879. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  880.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  881.     x2 = tx2: y2 = ty2
  882.     IF x1 > x2 THEN SWAP x1, x2
  883.     IF y1 > y2 THEN SWAP y1, y2
  884.     w = x2 - x1 + 1
  885.     h = y2 - y1 + 1
  886.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  887.     'Error checking for if the user sends coordinates which are off the screen
  888.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  889.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  890.  
  891.     u = UBOUND(TextHandles)
  892.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  893.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  894.     NEXT
  895.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  896.         Handle = u + 1
  897.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  898.     END IF
  899.     TextHandles(Handle).x1 = x1
  900.     TextHandles(Handle).y1 = y1
  901.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  902.     TextHandles(Handle).InUse = True
  903.     TextHandles(Handle).Xpos = 0
  904.     TextHandles(Handle).Ypos = 1
  905.     TextHandles(Handle).UpdateMethod = NewLine
  906.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  907.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  908.  
  909.     IF SaveBackground THEN
  910.         imagehandle = _NEWIMAGE(w, h, 32)
  911.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  912.         TextHandles(Handle).SavedBackground = imagehandle
  913.     END IF
  914.     TextHandles(Handle).ScreenX = x1
  915.     TextHandles(Handle).ScreenY = y1
  916.  
  917.     NewTextArea% = Handle
  918.  
  919. SUB FreeTextArea (Handle AS INTEGER)
  920.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  921.         IF TextHandles(Handle).InUse THEN
  922.             TextHandles(Handle).InUse = False
  923.             IF TextHandles(Handle).SavedBackground THEN
  924.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  925.                     w = TextHandles(Handle).w
  926.                     h = TextHandles(Handle).h
  927.                     x1 = TextHandles(Handle).ScreenX
  928.                     y1 = TextHandles(Handle).ScreenY
  929.                     x2 = x1 + w - 1
  930.                     y2 = y1 + h - 1
  931.                     blend = _BLEND
  932.                     _DONTBLEND
  933.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  934.                     IF blend THEN _BLEND
  935.                 END IF
  936.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  937.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  938.             END IF
  939.         ELSE
  940.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  941.         END IF
  942.     ELSE
  943.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  944.     END IF
  945.  
  946. SUB HideFrame (Handle AS INTEGER)
  947.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  948.         TextHandles(Handle).HideFrame = -1
  949.         w = TextHandles(Handle).w
  950.         h = TextHandles(Handle).h
  951.         x1 = TextHandles(Handle).ScreenX
  952.         y1 = TextHandles(Handle).ScreenY
  953.         x2 = x1 + w - 1
  954.         y2 = y1 + h - 1
  955.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  956.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  957.         IF TextHandles(Handle).SavedBackground THEN
  958.             blend = _BLEND
  959.             _DONTBLEND
  960.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  961.             _FREEIMAGE TextHandles(Handle).SavedBackground
  962.             IF blend THEN _BLEND
  963.         END IF
  964.         TextHandles(Handle).SavedBackground = imagehandle
  965.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  966.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  967.     END IF
  968.  
  969. SUB RestoreFrame (Handle AS INTEGER)
  970.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  971.         TextHandles(Handle).HideFrame = 0
  972.         w = TextHandles(Handle).w
  973.         h = TextHandles(Handle).h
  974.         x1 = TextHandles(Handle).ScreenX
  975.         y1 = TextHandles(Handle).ScreenY
  976.         x2 = x1 + w - 1
  977.         y2 = y1 + h - 1
  978.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  979.         blend = _BLEND
  980.         _DONTBLEND
  981.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  982.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  983.         _FREEIMAGE TextHandles(Handle).SavedBackground
  984.         IF blend THEN _BLEND
  985.         TextHandles(Handle).SavedBackground = imagehandle
  986.         TextHandles(Handle).x1 = x1 'When the frames are frames are restored, we need to recalculate our print position
  987.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  988.     END IF
  989.  
  990. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  991.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  992.     u = UBOUND(TextHandles)
  993.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  994.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  995.     HideFrame Handle
  996.     TextHandles(Handle).ScreenX = x1
  997.     TextHandles(Handle).ScreenY = y1
  998.     RestoreFrame Handle
  999.  

The code has doubled in size, but that's because I plugged in my text frame library and am using it to wordwrap/justify/scroll text on the screen for me, without interfering with the rest of the program.  A lot of the library I can strip out later, as I don't think I'll need to be able to move/hide/restore frames, or anything of that sort, in this program, so honestly things aren't half as complex as they might seem at first glance in the program now.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Rogue-Like (work in progress)
« Reply #14 on: September 11, 2019, 03:18:33 pm »
Hi Steve,

Getting interesting and the text frame library too, but text is kinda small for these old eyes.

I have allot of room on left or right of graphics area for large text areas, aren't most screen wider than high? Might be better use of screen area, just an idea. Maybe you prefer preserving retro look?