Hey all, a few months ago I started on an Axe project that was inspired the Mystery Dungeon series as well as by this tutorial series:



The goal was to make a relatively simple graphical roguelike for the TI-83+/84+ calculators as well create general engine with code that can be reused for similar projects in the future. I have been working on it on and off, but this month I plan to get some real progress done. I'm hoping this thread will serve a few purposes:
First, keep me motivated/focused. I'm not sure if I'll complete this by the end of the month, but I would like to make a fair bit of progress and hopefully get it in a playable state.
Second, get some feedback. I posted a few screenshots in the Discord here and there but not everyone on the forums uses Discord. I'm hoping to have a bit more visibility and interest here.
Lastly, I'm hoping that I can use this thread like a sort of dev blog, detailing my progress and my thought process throughout. I also plan to make it completely open source once it is complete. If I somehow lose all motivation I plan on sharing the source regardless of the game's state.

So with that being said, let's start with what's been done so far (with complementary eye candy, every screenshot is in 6MHz mode):



This is the very first iteration of the tilemapper, complete with tile collisions. The code came from yunhua's tilemapper tutorial over on Omnimaga. It was pretty simple afterwards to make the game constantly loop over the player sprite animations Mystery Dungeon style. Once that was complete I went to work trying to implement a very rudimentary mob system. First step was to simply display a mob to the screen... which kinda worked.



I fixed the jittery-ness of the scrolling but I would learn later that I did not completely remove all of the bugs with displaying mobs. Next up was mob collision, which was basically making it so you can't just walk right through mobs on the map.



Initially this was pretty difficult for me to implement, but thanks to E37 over on Omnimaga I got it working. However, there was a problem that I knew I was going to have to deal with: speed. In this iteration, the tilemap was redrawn every frame. Which also meant that the more mobs I added, the slower the game would get simply by drawing them. This was without factoring in any other possible systems like AI. So my next thought was "how can I make this faster?" So I looked high and low through the forums, both Omnimaga and Cemetech to see if I could make the tilemapper faster. I found the answer in Runer112's YAAM and GRAYLIB programs. I spent the better part of a week reading through and trying to understand the YAAM source and the GRAYPKMN source to see how he was getting the speed he was. Eventually I was able to put together a nice and fast unaligned tilemapper that was not redrawn every frame. The code is pretty much entirely his, I just made a few adjustments/adaptations. The result?



With the new tilemapper up and running, adding mobs would no longer be as detrimental to the performance, so mission accomplished there.



I then spent about 2 days trying to fix the aforementioned issue with displaying mobs, which looked something like this:



There was supposed to be one mob added to the map in this screenshot, but it was displaying at least three times over. The fix was a pretty simple line of code that performed double duty: it prevented the mobs from repeating out of bounds, and it also made it so that mobs are no longer drawn if they are a certain range away from the player. This is pretty important for performance as well, as it meant that the game was no longer drawing mobs off-screen. From there it was simple to re-implement mob collision.



Thanks to Hayleia over on the Codewalrus server, I was also able to write a custom 4x4 font routine:



So what's next? Right now, I am designing the general gameplay. Currently my idea is to make the game around 20 or so procedurally generated floors with various slime types, each with their own unique behavior. Each floor will be 48x48 tiles in size. In terms of player progress, since the scope of the game is relatively small, I plan to have the player earn gold every time they kill a slime, and for there to be a shop (think Kecleon shop in Mystery Dungeon) for the player to buy items and weapons on every floor. This is still very much subject to change, and I have a few other ideas floating around in my head. Since the game won't have an XP or level up system, I plan to have the player increase their max health through purchasing an item from the dungeon shops. I am also unsure if I am going to include a hunger mechanic but I'm currently leaning towards yes at the moment. There will be an item throwing mechanic and I'm also thinking of adding a simple charge-based magic system. If you're familiar with Shiren the Wanderer, think staves. I think the combination of these mechanics will make for a simple yet interesting rougelike experience.
There's a lot of work to be done but the next thing I'm thinking of working on is either floor generation or making it so you can attack mobs. Floor generation sounds a little more fun so I might go with that.
What do you guys think so far? Any questions, feedback or suggestions? Thanks for reading!
Fascinating progress so far! I see you have a lot of features planned, but what I am curious about the most is how the primary gameplay loop will turn out — how fun it will be, what fights will be like, etc. I look forward to the progress Smile
I love the progression screenshots and I'll always have a soft spot for the monochrome calcs.

This looks great so far, especially the speed and animation.
tr1p1ea wrote:
I love the progression screenshots and I'll always have a soft spot for the monochrome calcs.

This looks great so far, especially the speed and animation.

I second this, plus I also have a soft spot for top-down games, whether they're RPG, ARPG, shoot-em-up, etc. I look forward to seeing this develop more!
This looks very promising! I like the main character movement especially and scrolling seems smooth. Smile
OMG HOW????
In more normal terms, This looks really good! I like the greyscale, especially for an axe program, and the smooth scrolling blows me away. Don't sprites get clipped if they go offscreen? That blows me away!
Thanks for the kind words everyone. Once again, the credit for the tilemapper really should go to Runer. The optimized code and the concept behind how it all works is all his. Basically, I'm using 2 buffers. The back buffer, L3, for the tilemap and L6 for the player and mobs. The tilemap works by drawing the rows or columns of tiles depending on the direction you're moving in and then shifts them in with a for loop. Then I use RecallPic to copy the back buffer to the front buffer and sprites are drawn with overwrite logic.
As for drawing the mobs? I'm doing this with several different functions. First I have a function that adds a mob to an array. For now it just takes their X and Y position on the map and uses that to calculate their "real" position in the map data. It looks like this:
Code:
Lbl AddMob
   If L<18
      [r2]*48+[r1]->{[r2]->{[r1]->{L+1->L*4+L1-4}+1}+1}ʳ
   End
Return

This was written thanks to Deep Thought's array tutorial. It was a big help.
Next comes the actual drawing. Here is the code responsible for drawing the game and the mobs:

Code:
Lbl DrawGame
   F++
   RecallPic
   DrawMob()
   Pt-Off(44,28,°Player+(F/8^4*8)+D)
   DispGraph
Return
Lbl DrawMob
   For(I,1,L)
      I*4+L1-4->Z
      {Z}-PX*8->[r1]
      {Z+1}-PY*8->[r2]
      If ([r1]>>~64) and ([r1]<<64) and ([r2]>>~48) and ([r2]<<48)
         Pt-Off(44+[r1]-MX,28+[r2]-MY,°Slime+(F/8^4*8))
      End
   End
Return

Essentially what I'm doing here is drawing the mobs relative to the player's pixel position on the screen. This only works because the player sprite never actually moves. But what about the scrolling? To keep this short, I'm going to just go over movement in one direction.

Code:
If getKey(1)
   0->D
   8 CheckTile()
   MoveDown()
End
Lbl ScrollDown
   Vertical -ʳ
   -12 :ClearRow()
   Y++
   35 :DrawRow()
Return
Lbl MoveDown
   !If Solid
      If NoMob(48)
         For(8)
            MY++
            DrawGame()
            ScrollDown()
         End
         PY++
         PY*48+PX->PPos
      End
   End
   0->MY
Return

The scrolling code is all Runer. I just changed the names of the functions for readability. The code for movement, however is mine. As you may have noticed, I was using MX and MY in the display code for the mobs. I'm simply incrementing by 8 or decrementing by 8 in the for loop to shift their pixel positions to correspond to the movement of the tilemap, then resetting their values back to 0 at the end. The result is some nice smooth scrolling and sprites that don't clip when they shift partially offscreen. I'm definitely going to have to change this a bit when I make them able to move, and probably refactor the movement code to account for it, but I already have an idea as to how I'm going to do it.
Hey everyone! It's time for a quick little update post!

I've been busy with work and other life stuff these last 2 weeks, but I did manage to sneak in some time to work on the dungeon generation. So far, I've written out in pseudocode notes a pretty complete algorithm for generating all the rooms that are going to be on a floor. In HCWP yesterday, I got started on translating my notes into code and completed the first part which is simply generating a room. Right now I generate a single room that lies within the bounds of the map and is the appropriate size. The most important part was making sure that the starting upper left corner of the room was a valid location. After that I was able to design a method checking the width and height so that if the upper right and lower left corners of the room lie outside an acceptable range to simply reselect a new size.
The screenshot isn't particularly spectacular, but it shows the room generation working:



Here I show the the bounds of the map then show that the randomly generated room does in fact lie within those bounds.
I do run into 2 bugs with the program so far. The first bug being that when ever I hit [Clear] to exit the program, my calculator crashes and my RAM clears. The second bug is the bit of corrupted tiles in the upper left corner at the beginning of the screen shot. The tilemapping and movement code is all the same from before, and I didn't encounter any issues when exiting when I didn't generate the room but used the hardcoded map. In regards to the second bug, I'm not sure what is even causing it. The code for creating a blank map is as follows:

Code:
GetCalc("appvFLOOR",2304)->Map
Fill(°Map,2304,1)

The code simply creating a room is this:

Code:
For(I,0,RoomHeight-1)
      Fill(°Map+RoomStart+(I*48),RoomWidth,0)
   End

When I first tested this code by manually creating a room, the corrupted tile issue did not occur. This only happened when the program generates a room as opposed to me hard coding one. Perhaps there's an issue with how I create the appvar? That could be the source of both of my bugs. Not sure. If anyone who knows Axe has any idea what's causing them, let me know.
With that being said, I'm going to continue working on floor generation. Next up is translating the notes I have in determining whether subsequent rooms are too close to or overlap with already existing rooms. Wish me luck and see you hopefully next week!
Assuming RoomStart is correct, I think your code is fine. One thing I've noticed is sometimes after a ram clear, the OS doesn't return the correct pointer to a new file. One thing you can try is sending over a file you manually created, then use your program to overwrite the contents.

I never really looked into the issue, but it was obnoxious.
Hey all!
It's been seven months since my last post here which was not something I expected. There was a period of time where I was really busy and couldn't really work on this. Last time I posted I was stuck with how to approach the level generation. I did do some research into how the Pokemon Mystery Dungeon games did their procedural generation, and found a really good video on YouTube here. Using that as a template I came up with a basis for how I was going to generate levels.

I decided to keep the map 48x48 but limit the actual space to 36x36 in the center. The idea is that now I divide this 36x36 map into a 3x3 grid of 12x12 regions, and treat each region as an index in an array. With that in mind it was actually pretty easy to generate a random amount of rooms all with random sizes and locations within certain parameters. Then I wrote a debug program in order to be able to check both the values and the map of each generated level:



Like I said, however, that was the easy part. The hard part for me was connecting those rooms together. Using the same basic algorithm from the Mystery Dungeon video, I wrote some code to generate corridors. To make things easier for myself, rooms are only connected from left to right or from top to bottom. Put more simply, they are only connected from a lower index to a higher index. Once that was done I ran a quick test:



With the code that handles connections done, now it was time to connect all of the rooms which proved to be more difficult for me than I thought it would be. First I took the lazy approach and simply looped through all of the rooms and connected them:



It worked, but there were 2 key issues. The first and most important, was that were isolated rooms or separate groups of rooms that were not connected to each other. Can't have rooms you can't get to. The second issue was that if there weren't isolated rooms, the levels were boring and predictable. Not a good formula for replayability.
I had originally skipped the step of adding dummy rooms to the level because I thought I wouldn't need them, but it turned out they were actually really important and useful.
There needed to be a way to generate a corridor from a region even if there wasn't a real room there. The solution was to generate a 1 tile dummy room in every region that did not have a real room. That way when the time came to connect the rooms together, there was something to connect to:





With the first issue solved, it came time to tackle what was really the hardest part of this whole process, generating a strongly connected map. At first I had some weird and convoluted solution that tried to use arrays to keep track of rooms that were connected to each other and rooms that needed to be connected to.
If there's one thing I have learned from this project, simple is often better. And while I kept trying to come up with complex solutions, there was a much simpler way. All I needed to do was perform a depth-first search on the region array. The algorithm itself and implementing the stack was very simple, the only part that took some time was trying to randomly choose an unvisited neighbor consistently.
Here is the code:

Code:
Lbl GenerateHalls
  Fill(°VisitedRooms,15,0)
  Fill(°Stack,9,0)
  ⁻1→Top
  0→TrueRooms
  While 1
    Rand()^9→R
  EndIf {°RegionArray+R}
  1→{°VisitedRooms+R}
  TrueRooms++
  Push(R)
  While 1
    GetNeighbor(Peek())→Neighbor
    If Neighbor=⁻1
      Pop()
    Else
      1→{°VisitedRooms+Neighbor}
      If {°RegionArray+Neighbor}
        TrueRooms++
      End
      .connect rooms here
      Push(Neighbor)
    End
  EndIf StackIsEmpty() ?? (TrueRooms=Rooms)
Return
Lbl GetNeighbor
  0→Neighbors->N
  r₁^3->RegionX
  r₁/3->RegionY
  If RegionX=1
    {°VisitedRooms+r₁-1}=1??r₁-1→{(Neighbors++)+°NeighborArray-1}
  End
  If RegionX<2
    {°VisitedRooms+r₁+1}=1??r₁+1→{(Neighbors++)+°NeighborArray-1}
  Else
    {°VisitedRooms+r₁-1}=1??r₁-1→{(Neighbors++)+°NeighborArray-1}
  End
  If RegionY=1
    {°VisitedRooms+r₁-3}=1??r₁-3→{(Neighbors++)+°NeighborArray-1}   
  End
  If RegionY<2   
    {°VisitedRooms+r₁+3}=1??r₁+3→{(Neighbors++)+°NeighborArray-1}
  Else
    {°VisitedRooms+r₁-3}=1??r₁-3→{(Neighbors++)+°NeighborArray-1}
  End
  {°NeighborArray+(Rand()^Neighbors)}→N
  Neighbors?N,⁻1
Return
Lbl Push
  r₁→{(Top++)+°Stack}
Return
Lbl Pop
  Top--
  {°Stack+Top+1}
Return
Lbl Peek
  {°Stack+Top}
Return
Lbl StackIsEmpty
  (Top=⁻1)
Return

The result? Unique and random maps that are strongly connected:



The final tweak I wanted to add was to ensure that there were no dummy room dead ends. Since the level is already acyclic I felt that running through a long corridor just to end in dead end would not only feel bad, but would end up punishing exploration of the level. I started with some awful and complex solution (again!) but ended up with simply adding a real room if a dummy room had no unvisited neighbors.



And with that, I now had a solid base for level generation! The next step was to take a procedurally generated map and use it with the tilemapper:




My next goal is to probably continue with generation start working on the dynamic tilemapping so I can be rid of those horrendous wall sprites. I've already written a simple routine that allows me to get a tile's signature (spoiler!) as a binary number. As always feedback and suggestions are always welcome. This was a long one, but I hope you enjoyed. I did promise that this project will be open source, but right now that source is a mess. Once I've cleaned it up some more I will definitely be posting on GitHub. More progress soon!
This has come a long way, wow :0
Love the solution and the results are great - Looks really cool to see it as a real tilemap too Smile.
Fantastic, I really enjoyed reading through this latest update! In particular I like the room viewer tool you made to visually inspect the generation testing. Stuff like this is why I love tinkering with game development!
Glad you managed to solve the map issue. Nice update Smile
Thank you all once again for your kind words. Today I am back with a juicy update post about:

Auto-Tiling

What I thought was dynamic tilemapping was actually just auto-tiling, but what is auto-tiling?
Auto-tiling is a process in which an appropriate tile is selected and based on its surrounding tiles. Because each level is procedurally generated, the tiling solution needs to be robust enough to handle any generated map and auto tile it correctly.
So now the problem is how do we get a tile's surrounding tiles and use that information? The answer is tile bitmasking. There's a pretty good article that explains it here. Essentially we look at a tile's surroundings and set a bit pertaining to a particular direction if there was a tile there or not. What we now have is a tile's bitmask in the form of a number. I'm loosely following the roguelike tutorial series that I posted in my first post, and they refer to the bitmask as a tile's signature, which I like and will be using from now on.
In Axe, writing to bits was not as straight forward as I thought it would be. But thanks to some Runer magic, I found a routine that he wrote that lets me do just that:

Code:
Lbl BitSet
  {} or Bit()
  →{r₂}
Return
Lbl Bit
  e^(7-r₁)
Return

With that done, I could move on to generating a tile's signature. I'm using the same method as the tutorial series using 2 direction arrays, but 1 array will work as long as the direction order is the same. Here is how that looks:

Code:
Data(⁻1,1,0,0,1,1,⁻1,⁻1)→°DirX
Data(0,0,⁻1,1,⁻1,1,1,⁻1)→°DirY
Lbl GetSignature
  0→Signature
  r₁→r₃
  r₂→r₄
  For(S,0,7)
    r₃+sign{°DirX+S}→A
    r₄+sign{°DirY+S}→B
    If {B*48+A+Floor}>1
      BitSet(S,°Signature)
    End
  End
  Signature
Return

These functions are the core of the auto tiling process, but the tileset is also important.



At the minimum, there will be 48 tiles in your tileset. 1 floor tile, 1 full wall tile, and the other 46 are the remaining wall tiles. In my case a full wall tile is just a black tile. The order in which these are stored is important since we're going to access everything by index.

The last sets of data we need are 2 arrays. The first array contains 46 different tile signatures in the order they are stored as tiles, and the second array contains an array of masks. This array of masks contains masks for each of the tile signatures, and is in the same order. The masks are for the lower 4 bits of the signature which represent the corners. A far better illustration of this concept from the creator of the tutorial series can be found here.
I wanted to use my own ordering, and so with the signatures and masks for the wall tiles calculated here are the resulting arrays:

Code:
Data(251,233,253,179,0,124,247,214,254,84,146,104,161,80,16,144,64,240,128,96,32,160,192,48,112,208,224,176,241,248,242,244,210,177,116,232,225,120,
178,212,245,250,243,249,246,252)->°WallSig
Data(0,6,0,12,15,3,0,9,0,11,13,7,14,11,15,13,15,0,15,7,15,14,15,15,3,9,6,12,0,0,0,0,9,12,3,6,6,3,12,9,0,0,0,0,0,0)->°WallMask


With the groundwork finished, we can now write our shiny auto-tile routine:

Code:
Lbl AutoTile
  For(J,6,41)
    For(I,6,41)
      J*48+I→L
      GetSignature(I,J)
      If {L+Floor}
        For(K,0,45)
          If SigComp(Signature,{°WallSig+K},{°WallMask+K})
            K+3→{L+Floor}
          End
        End
      End
    End
  End
Return
Lbl SigComp
  (r₁ or r₃)=(r₂ or r₃)
Return


It loops through all of the floor tiles, and after masking, checks for a match in the WallSig array and sets the value of that tile to the appropriate value based on the index of the match. I've offset that by 3 since that's where the first wall tile begins in memory.
With that lengthy explanation out of the way, it's time to show the results!




The final touch I wanted to add was to change the floor tiles with wall tiles above them to give the game a more 3D feel:




I'm in love with the result and I'm so glad that it works as well as it does. There is but one problem. It's slow. Like way too slow. Floor generation takes about 0.07 seconds. Auto-tiling it takes more than 100x that at around 9-10 seconds. I've been trying to find a way to optimize this but would greatly appreciate it if any Axe programmers have any ideas on how to make it faster. With this done I might start working on basic mob movement. I always love to hear your thoughts and I'll be back with more progress!
Your game looks so pretty ! I love traditional roguelikes, happy to finally see one on a calculator, with a such beautilful tileset <3
Good luck optimizing your game Smile
The progress on this game is incredible! It runs very smoothly (Especially for not being written in ASM) and I'm very impressed with the tilemap generation. How large is the project so far?
This looks amazing so far! Perhaps start working on interesting mechanics and character bg transparency?

One mechanic that would make it a bit more interesting is a "fog" -- if you don't have line-of-sight, you can't see what's there, and parts of the map you have already visited but don't have lin-of-sight on slowly fade over time.

One other thing: is this real-time or turn-based?
I like the auto-tiling idea. Many of my old calculator RPGs had some minimal auto-tiling being done to maps but it was much more simple as it only checked one direction.
I think I've done all I can to optimize the auto-tiling. I got it down from around taking 10 seconds to around 5 or so. I wanted to keep as much compatibility as I could with the 83+ calcs, so I was hesitant to use Full but I decided to compromise and only use full for the auto-tiling for now. Here is a quick comparison of load times.

Without Full:

With Full:



I think the speed boost is worth it, it just means that if you have a TI-83+ your load times between floors will be longer but normal gameplay should be unaffected.
As for size, the program sits at 6.3kb when compiled atm but that will only continue to grow. For now it also uses 2.3kb of RAM but this is just for the map.

In regard to the "fog-of-war" idea. I do like it, and I did consider it at one point, but I decided that I wouldn't try to implement it in this game. The game will be a turn-based roguelike, as it was inspired by the Mystery Dungeon games. I did a lot of research while I was searching for ideas and inspiration and I came across Shiren the Wanderer: Moonlit Village Monster GB which was the only Mystery Dungeon game that was released for the original Game Boy. I noticed that that game didn't implement fog. From a design perspective I feel that this decision makes sense because since the screen is much smaller, I can sort of leverage its size as a form of fog. The tilemapper is smooth scrolling so as you're moving what you can see is limited to an 8x12 area. So you never really know what's ahead since every level is different.

I've done a lot of code cleanup and organization and found a really nice way to automate tokenizing my files for quick testing. I've also recently began integrating some of my older mob system code back into the project and working on getting mob movement to work. Thanks for the feedback! I really do appreciate it. Also, the project source is now officially on GitHub available here. Go check it out!
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 2
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement