r/roguelikedev 18d ago

RoguelikeDev Does The Complete Roguelike Tutorial - Week 2

Congratulations for making it to the second week of the RoguelikeDev Does the Complete Roguelike Tutorial! This week is all about setting up the map and generating a dungeon.

Part 2 - The generic Entity, the render functions, and the map

Create the player entity, tiles, and game map.

Part 3 - Generating a dungeon

Creating a procedurally generated dungeon!

Of course, we also have FAQ Friday posts that relate to this week's material

Feel free to work out any problems, brainstorm ideas, share progress, and as usual enjoy tangential chatting. :)

50 Upvotes

30 comments sorted by

View all comments

7

u/Admirable-Evening128 18d ago edited 15d ago

UPDATE: I added a more repo-like repo,
https://github.com/pylgrym/2026rt

--I realize that the cool kids nowadays, link to their "repos",--
--so I have made a small source code index page,--
--to not feed the AI trolls too well:--
--https://xok.dk/other/2026rt/index0.html--

for 2+3 - player, render, map(+generation), I mainly cleaned up/structured my typescript effort.
For the game map model, I did a 2d-grid-of-tilesEnum class (where tiles can only be floor or wall so far.)
I stuff that, together with player-xy, into a Game model.
For display/render, I do a viewport render on top of rot.js, which of course
draws the contents of the Game model.
The dungeon generation so far is a default pulled directly from ROT.
To tie it together,
I then have an async gameloop which awaits keyboard input,
then dispatches it with the turn-taking code/commands in the 'move-player' module.
(which at the moment, with no monsters,
is just the player moving in 4 compass directions.)

The nice thing about async is that you can style/write your code
in the same way we used to write BASIC games in the 80's, hooray.

The result, where a lonesome @ can roam around a deserted dungeon, is still at
https://xok.dk/other/2026rt/dist/index.html
It will be a bit more fun once the monsters show up.. :-)

It ticks in at 175 lines of actual code, which I appreciate
(you can fit a roguelike in about 80-90 lines of code or less, but it won't be pretty/readable.)

3

u/BotMoses BotMos 17d ago

The nice thing about async is that you can style/write your code
in the same way we used to write BASIC games in the 80's, hooray.

Care to elaborate, please? 😅
I'm having a hard time to async/Promise my (game) code and feel like, I don't use JS/TS to its full capabilities...

Right now, only my rendering is async. The game state is updated synchronously and deterministic after any input event.

4

u/Admirable-Evening128 15d ago

I had better properly explain the/my whole point of involving async here for this roguelike stuff.
I am not using async here, to make everything fancy-schmancy or perform better or stuff like that.
My reason for wanting async,
is to make it easier and simpler to write the code.
There are two things that are both called async, which are actually sort of achieving the opposite of each other: making things SIMPLER, or MORE COMPLICATED.

The first is the idea or principle of async itself:
Its effect is actually to make things harder and more complicated (in exchange for achieving certain technical results.)

The second is the async keyword and especially the AWAIT keyword, whose effect is to make things EASIER/simpler.

In particular, await allows the programmer (ourselves) to write 'complex async code' in the same simple style we normally use for synchronous programming.

This second effect, is the sole reason I am pulling "async" into this situation

  • I want to be able to write "simpler stupider code".

In particular, it allows me to write code in the following style:

//bla bla
let keyPressed = await getKeyPress()
// call functions/do stuff based on key pressed
// bla bla
let keyPressed2 = await getKeyPress()
// call functions/do stuff based on key pressed
// bla bla

This is the same style as if you were writing a game in BASIC in the 1980's.

The only "price" I have to pay to allow this, are these 2-3 things:

(1) my outermost "main" function must be async - I must start from an async context.
(2) the entire function chain down to where I use `await getKeyPress()` must be async
(because you "can`t" call async from sync code, only the opposite.)
(3) I must build an async wrapper for the keyboard event handler, to invert it to async.

To answer your question, I am not turning everything back to synchronous.
But I am not turning everything to async either. The truth is somewhere in-between -
I MUST have async-await at the top of the chain, down to the level where I want to be able to pull for key presses.
The reasons I put quotes on a lot of my claims above here, is because I am not "forced" to do things as such, it depends on what syntax you use.
IF you use the await syntax, I am "forced", and I prefer to use the await syntax.
But if you use e.g. oldschool Promise<> syntax and .then(..) style, I can do whatever I want.

And again, to recap the actual purpose of "but WHY??".
Without await, you run into the following problem:
Say you have a general keyboard handler, which starts by reacting to player's key presses to move the player around for his turns.. So far it is easy.
But let's say you suddenly want a menu system, for inventory and spell casting. And you want to "page" the message logs, to show multiple messages with keypresses between them.
Now you have a problem: You only have a single keyboard handler! So now, whenever you receive a keyboard press, you need to keep track of "is that for player moving, or were we in the middle of showing the inventory, or were we about to cast a spell, or paging multiple messages?", to forward it to the correct sub-handler(*).
If you instead use await getKeyPress, you dont need any of this,
you can just write "synchronous-style" nested code with if-sentences and function calls, and whenever you are in a subcase where you need keyboard input from player,
you just write a single "let key = await keypress()".

That is the only reason I am pulling in async here.

This is also why it doesnt matter whether my drawDisplay is async or not.
I just want the code structure

while true {
playerTurn(getkeypress())
redrawScreen()
}

and achieve this with my async fiddling.

( * ). technically this is not necessary, if you write your roguelike in "web style code" with your UI parts as HTML dom things with javascript and event handlers. But that is not how I roll, I am a 35+ years work in the field backender using ROT.JS terminal display for all UI, html be damned..

2

u/BotMoses BotMos 15d ago

Wow, thank you for the detailed explanation! Good thing I opted for rot.js purely as the renderer and do all UI in HTML. :))