Tidy up main
Honestly main.c looks like it was written by a couple of college seniors taking a joke class who couldn't decide if they cared a lot or not at all about the project. It should probably be reorganized with a lot of what's in main being put elsewhere. It would be nice if the turn system could somehow be re-structured.
Thoughts on what to move the main function to
The basic separation of initialization stuff, moving all the curses init to a function(s) in the ui directory, is pretty obvious and straightforward. Bundling all those 2 letter variables into some structs so the struct main.field names gives some idea of what they all do would be great. I mean, just look at it:
int repeat_act = 0;
int fight_pre = 0;
int run_pre = 0;
int w0;
int h0;
int xn;
int yn;
int xp;
int yp;
int ch;
Admittedly a lot of this stuff is from one of the ncurses tutorials/online books floating around. The example for setting up and managing windows was clearly adapted from one. To be clear, I'm not suggesting that is bad or inappropriate, just not put together well for multiple developers/long-term projects.
Definitely some restructuring of main function, reducing it down to just the core event loop would be good. Along with that, trying to unify the player and enemy code so that other than the player taking inputs from the keyboard, all the logic is the same and not duplicated.
In terms of the main event loop, I thinking some decisions should be made about how speed and general time keeping of effects, might be implemented down the road.
Note I apparently started rambling at some point below. I blame far too many years of post-secondary education and writing things large number of pages required, has rendered me rather long-winded. It's all relevant stuff, but a lot of thinking ahead. I've thought through implementations as part of roguelikes that I failed to get much work done on over the years.
Do all actions by all actors take the same amount of time?
Is there any plan to have player/enemies having different speeds, like some modern roguelike? If turns will always be same 'time' for everyone, the code will be simpler. Description of a pretty common speed system below. I'm not suggesting that if we want something like that eventually, we need do it all now. Just that if we might want that eventually, the main event loop could be structured with it in mind.
Basic roguelike speed system
Instead of thinking in terms of turns we think in terms of ticks. With 100 ticks being an average turn. Different actions may take more or less ticks. Different actors (player, enemies, NPCs, etc) may have different speeds. With a bonus to speed, say +50 speed (huge bonus, but makes example easy), giving a speed of 50 relative to the base 100. With +50 speed, that'd act twice as often as everyone else with a speed of 100.
This is conceptually how you can think of it, a real implementation would be more efficient. There is a global tick time. It increments one by one until you reach the actor with the lowest speed in the current level (or action bubble, or what is the current context). That actor takes their action. Their next action is then set to the tick number they acted on plus their current speed. (This is assuming all actions take the same speed, but you can easily change that here).
So, if we have three actors, {A,B,C} with speeds of {50,100,150} respectively. It’d go like this:
-
tick 0-49 nothing happens
-
tick 50 A acts, is scheduled to act again at current tick (50) + their speed (50) == 100
-
tick 51-99 nothing happens
-
tick 100 Actors A & B act, ties being resolved however, their next turn A->100+50=150, B->100+100=200
-
tick 101-149 nothing happens
-
tick 150 Actors A & C act, tied handled, next turns A->150+50=200, C->150+150=300
-
tick 151-199 nothing happens
-
tick 200 Actors A & B act; A->200+50=250, B->200+100=300
-
tick 201-249 nothing happens
-
tick 250 Actor A acts. A->250+50 = 300
-
tick 251-299 nothing happens
-
tick 300 A, B, & C all act. next turns, A->300+50=350, B->300+100=400, C->300+150=450
So, in the span of 300 ticks
-
A acts 6 times: 50,100,150,200,250,300
-
B acts 3 times: 100,200,300
-
C acts 2 times: 150, 300
This also gives means of handling effects with durations. Actor A drinks a potion of temporary strength that lasts 5 'turns' (each turn being 100 ticks). The potion end event gets scheduled at current tick + 500. So, durations don't be done in terms of a character's speed or only end/start on a particular actor's turn.
Note this also gives a global ‘clock’. Ticks since game start or some other transition point. Probably be handy to have around.
Long run Implementation
Doing this efficiently this isn't all that hard really. Just maintain a priority queue of 'events'. Where events are the next actor's turn or a change in statuses (a potion or spell duration begins/ends). Then the main loop just pops an element off the priority queue, move the current global tick time to the element's scheduled time, do the element's action, determines when the element's next turn will be (if at all) and schedules the next turn by inserting it into the queue.
tick_time <- 0
queue <- all initial actions
while(true):
event <- queue.pop()
tick_time = event.scheduled_time
event.take_turn()
if(event.repeats()) then
next_time = tick_time + event.speed()
event.scheduled_time <- next_time
queue.insert(event, next_time)
else
dispose(event)
end_while
Short term (Thinking Ahead)
Changing the main function to something like this might be too much for the moment (though as you see it's not really that complex). Most priorities queues have a consistent way of handling of priority ties, most often the events keep in the same order they are inserted.
For the moment, changing main function to a proper event loop, we can just have a list of actors (including the player and anything that might need to initiate an action) and loop through those. Until things have different speeds, or there are durations that need to be kept track of, this will work fine. It can be expanded as the need arises.