Wa - A Roguelike Game
[Most Recent Entries]
[Calendar View]
[Friends]
Below are the 20 most recent journal entries recorded in
Wa - A Roguelike Game's LiveJournal:
[ << Previous 20 ]
| Friday, February 13th, 2004 | | 4:33 pm |
Fixed a couple more little glitches, improved the functionality of the method I use to write full screen messages (now with page breaks!). I thought for a while there was a bug in my event code, but it turns out it was just a display problem in my debug functions :) I've started adding shopkeepers to the town, so we're getting closer to the demo release! I haven't assigned version numbers yet, but I suppose we're around Wa 0.1.0, assuming the demo with the complete Progloue will be Wa 0.2.0. | | Thursday, February 12th, 2004 | | 5:57 am |
Stuff I've been working on lately:
- proper saving of light sources between levels and games.
- light source now varies by time of day (before I was altering the player's vision radius).
- split the UI class into two seperate classes - one that actually does the display and another that forms the bridge between the main game class and the display. Eventually the bridge class will be strictly abstract, but right now makes some assumptions about how the display will work. However, to use a different display (say, Curses), all I would need to do is create a different DungeonRenderer (what I've called the display class) and pass it to the UI. While doing that, I fixed up a lot of older, crappy code.
- added a couple more townsfolk to interact with.
- added some debugging commands. I can now view the EventQueue and occupants list in game.
- a few minor user interface tweaks.
| | Tuesday, February 10th, 2004 | | 3:12 am |
Got the overlapping light sources working, although I ended up redoing the functions which handle the player moving a couple of times :) I switched the light-sources and the results returned by the Shadowcaster class to be sets (which is sensible anyway, from a conceptual viewpoint). So, when a lightsource moves, the squares that need to be turned off are all the squares which aren't in the intersection of all the light sources. | | Monday, February 9th, 2004 | | 3:41 am |
I've been working on independent lightsources in the game (lightsources that aren't carried by the player). It always kind of bugged me that in, say, Angband, a dropped torch doesn't provide any illumination or that light hounds don't light up the dungeon as they move through it, so I wanted that feature in my game. It's done, or nearly done, anyway. I had to rework the code that decides which squares should be drawn when the player moves, but it was worth it because I found a place where I can probably get a bit of a performance boost. As the old proverb goes, the bottleneck wasn't what I thought it was. When I first did the code, moving with a large light radius was a bit slow and painful. I worried at first it was my LOS algorithm, but it turns out I was making many unneccesary calls to the drawing routine. Once I eliminated those, things got much better. I can still improve things further, I think, because I am drawing each updated square one at a time. In the case of the player moving, though, I can be updating several dozen squares at one go. So in the Pygame UI code, I am calling screen.blit() and display.update() many more times than are really required. It shouldn't be too hard to implement drawing a batch of squares at a time, but it's something I might leave for later. Two things are left to do for the lighting. One is tracking overlapping lights. At the moment, when the player walks through another lightsource, as he passes through, the game considers those squares dark, but the other lightsource should keep them illuminated. I had this working, but that's when I discovered the performance issues and ended up scuttling that code while fixing the main vision function. I also have to save lightsources along with the level (much like I do occupants) for purposes of events (such as a torch burning out). Once I get all the kinks out of the modified lighting code, I think I'll work on content for the town and quests in the prologue. Screenshot of the new lighting code in action. I've been toying with the idea of an honour system for Wa. One of the requirements to win might be that your character has achieved a certain level of honour and respect. I can think of how honour should be awarded or penalized for warriors (don't attack monsters weaker than you, don't flee battle, defeat powerful monsters), but don't have any ideas yet for sorcerers, ninjas or soheis. | | Wednesday, February 4th, 2004 | | 2:02 am |
Ok, I've got the events working to the point where I'm satisfied with them for now. Probably I'll have to make some changes later, but I'm not too worried about them right now. The next feature I want to implement is light sources that are independent from the player. The changes to the sight code won't be bad. Currently, the game acts as though the player is the only lightsource, so his vision radius changes (from a maximum of radius = 12) depending on the lighting conditions (time of day or in a dungeon). All I have to do is always run the LOS again the player's maximum vision radius, but only display squares in that radius which are lit. The tricky part is the bookkeeping entailed in tracking light sources that aren't player. There are two cases to worry about - moving light sources (eg., a glowing monster) and fixed sources (a lantern sitting on the ground). I need to track each light source because if one moves or is extinguished, some of the squares it was lighting might remain lit, if there were overlapping light sources. For the monster and player, I can check to see which squares are lit or unlit when the move in the __agent_moves_to_sqr method. But I'll also need a list of light sources to run through to check to see if a square should be lit. That means when a monster is added or killed, I need to check to see if it was (or was carrying) a light. Same thing for items. I wonder if it would be worthwhile to define a LightSource class that can be inherited by items and monsters? I can then do a datatype test to see if they are a light. (I'd still need to check their inventories, for torches and such, though) | | Saturday, January 31st, 2004 | | 1:35 pm |
I've made a few strides toward fixing the problems mentioned earlier. My solution is to have the GameLevel track which items it currently contains. This means I had to provide an interface for item access through the GameLevel class (I had been access the dungeon location's item stack directly), which is probably ultimately better. This resulted in my having to modify how the item stacks worked, since if a new stack is created (and stacks are items that happen to contain other items) the GameLevel's item dictionary had to be updated. So, with events like a corpse decomposing, I'll check to see if the item exists in the current level and if it doesn't, I'll put the Event back on the queue as suspended. When the player moves to a new level, the game will check to see if any of the suspended events apply to the current level. I don't *think* the list of suspended events should grow too big. (I'll need to check the inventorys of the monsters in the level as well) | | Friday, January 30th, 2004 | | 1:07 pm |
I've been wrestling with a flaw (in fact, flaw s) in how I conceived the EventQueue, which revolves around saving the EventQueue between games. The first such problem was that you can't pickle references to functions that are methods of a class. So, I changed the queue to store the name of the function to be called, then I can do this:
f = getattr(self,e.get_event())
params = e.get_parameters()
if params <> None:
f(*params)
else:
f()
So pretty much the same. The only limitation is that any method to be called this way must be public (you can get at the private methods with a name-mangling trick, but that just uglies up the code). A more fundamental flaw is that if you pickle and unpickle an object, it's going to be a different object. Ie., the == operator for comparisons tests to see if objects have the same reference. So, if I refer to an item in my event, save (pickle) the game and reload it, the item refered to in the event will no longer exist. I solved this by providing a unique_id for items and agents created in the game (at the moment, they'll be unique across all games until the counters are reset). However, this doesn't take care of all the problems. For instance, if you save will you are carrying a monster's corpse, then load a drop the corpse, the event will fire for when the body decomposes, but the item that is now on the ground won't be removed from the game. This is because of the previously mentioned pickling problem. I have a reference to an object in the event and a reference in the inventory. Those are saved in different files, so when the game is loaded, they now refer to different objects. So, the item in the event queue is saved with a reference to it's owner (as is the one in the inventory). Now, when the item is dropped, the owner is set to None, but because the corpse object refered to in the event object (they are now distinct, seperate objects) still refers to it's owner. While thinking about this problem, I recognized another flaw, this time in my player/level event dichotomy scheme. What happens if the player picks up a corpse and drops it on another level. Because there is no corresponding decompose event on the new level, it will never be removed. At the moment, I'm honestly without a solution. One idea is to have a master list of items (and probably agents) hashed by their unique_ids. Then, at any given time I know where the item is and can apply events to it. Conceivably, if an event fires on an object in an unloaded level but I knew what level it was on, I could load that level, act out the event and close it again. But a lot of unknowns and potential logistical problems arise. Also, how big would that table get throughout the course of the game? When the player is near the end, I can be 99.999999% sure that he isn't going to walk all the way back to the first dungeon in the game and pick up the +0 knife he left there. So there is little point to storing it. But if he did want to, he should be able to. This sort of mirrors the performance hit of loading the entire level into memory when the player is only going to be exploring it. I need to develop some kind of persistent storage that will let me fetch, say, a few squares of the dungeon at a time, rather than all or nothing. A database more or less. Sigh. There are a lot of implications which are difficult to wrap my brain around. | | Thursday, January 29th, 2004 | | 1:46 am |
Event queue is implemented, pretty much like how I described. I discovered a handy language feature I'd never used before. Events are created: e = Event(time,method_ref,parameters) time is the turn in the game the event should fire, method_ref is the function to call to perform the event and parameters are a list of parameters to be passed to method_ref. Now, I knew that if you had:
def foo(*x):
# do stuff
foo(1,2,3)
Foo would get the three parameters in a list called x. What I didn't was that if you had:
def foo(p1,p2,p3):
# do stuff
x = [1,2,3]
foo(*x)
Sending the parameter *x unpacks the list and assigns the elements to each of foo's parameters. Which is how I'm calling the functions referred to in Events. I've converted the changing amount of sunlight to use the EventQueue and implemented corpses rotting away as well and there are a bunch other functionality that will more cleanly use the queue system. | | Sunday, January 25th, 2004 | | 1:58 pm |
I've been thinking about my lighting issue. Before I tackle that, I'm going to integrate an Event Queue in the game. This has been in the planning stages for a while - simply a Priority Queue to track upcoming events (spells expiring, timed occurances. I'd like to make the events fairly generic and dynamic, so that I don't have to do something like:
event = event_queue.pop()
if event.type == 'spell expires':
# stuff
elif event.type == 'sunrise':
# stuff
elif ...
I wonder if storing the method to call would work?
turn = 800
event = Event(800)
event.action = self.light_source_expires # where light_source_expires is a method belonging to
# the DungeonMaster object
then do:
event = event_queue.pop()
event.action()
(Of course, if there are variable # of parameters it will be slightly awkward, but not too bad) My plan is to have an event queue for each dungeon level and one for the player (and maybe some monsters). That way, game level events (a door that unlocks at a certain time, a bomb going off, etc.) are seperate from events specific to the player (spell expiring, etc.) and when a player moves from one level to another, I don't have to go and find events that should follow the player along. When he returns to an old level, I'll need to run through the queue of events and see if any happened 'while he was away'. It seems like a simple, effective scheme, but I'll have to stew over it for a bit and see if I come up with any problems. It's nice because it might eliminate a bunch of code that's going to have to go in the __do_turn() method. Stuff like:
if self.curr_turn() % 200 == 0:
self.add_monster()
That would all be taken care of by events on the queue. | | Saturday, January 24th, 2004 | | 12:33 pm |
Fixed a couple of display glitches that had been on the back burner for a while, as well as a couple of behaviour bugs (you could close doors when items were in the doorway). I changed the game start so that if you don't enter a name, the game will provide a randomly generated one for your character. I want to re-work how I handle player vision. At the moment, use my shadowcasting algorithm to calculate which points are visible to the player. The radius used is determined by either their light radius or vision radius depending on whether the area they're in is lit or not. However, I'd like to have light sources independent from the player, so I am going to have to track things a little differently and always check the maximum vision radius with shadowcaster, then look for lit squares within the set of points it returns. | | Wednesday, January 21st, 2004 | | 8:44 pm |
Level and character loading and saving works. I have to clean up the code somewhat, but the mechanism is there. Entering the first quest with a character was pretty exciting. Working on the game has become a lot more fun now that I can actually do stuff in it. Moving around the quest level, though, I realized there are a couple of glitches in the user interface when trying to detect when the player is moving off the screen. I hate modifying that pieces code. I can never get it quite right. | | Tuesday, January 20th, 2004 | | 4:38 am |
Fixed a bunch of bugs, made a bunch of little changes. Added a priest who teaches meditation in the first town. Major re-work of how Level information is stored. To try to save space, I'm doing lazy instantiation of the DungeonSquare objects. They aren't created until the square is referenced. It saves some space, but a 'release' version of Wa is going to have to do something better. Implementing a 2-d grid that for lazy instantiation (essentially a sparse matrix without all the matrix operations) is super easy in Python. ( lazy grid source )In any case, I've worked out how dungeon levels are going to be stored (I'm starting to understand why the creators of Moria/Angband decided to have non-persistant levels!), so the first quest will be done, hopefully tomorrow. Once that is all worked out, the second dungeon of the prologue won't take that long (I think). Then I just need to fill out some content for the town and such and a demo release (Wa 0.2.0) can be readied. I still need to design a goddamn magic system! | | Saturday, January 17th, 2004 | | 4:34 am |
Two days of productivity in row! Implemented today:
- Message history stored. Easy to do, although I had to remove many instances where the DungeonListener display_message() method was called in favour of DungeonMaster's alert_player() method, which I had been meaning to do for a while. It even stacks messages when you get the same one several times in a row.
- Added random monsters to the wilderness. Now I actually have something to do in the game! I also gave them a small chance to have some money. No items yet, I'm not ready to deal with monsters having an inventory they can manage.
- I store town borders now to ensure random monsters aren't generated inside the town. Later, I can craft the monster AI so that they usually avoid entering the town.
- Began setting up the first quest. I haven't 100% thought through how I'm going to store meta-data regarding dungeons, but I have some ideas.
I spent quite a bit of time running around the wilderness killing monsters, advancing Jiro the sohei a few levels. | | Friday, January 16th, 2004 | | 3:17 am |
So I implemented a few things today:
- Time tracking. For now, 1 turn = 1 minute. The player can check the time with the '?' and your vision radius varies depending on the time of day. Perhaps I'll eventually have seasons so that in the winter the days are shorter.
- Now that there is time, the resting at the in actually has a game effect.
- Maps are now pre-processed. It's still too slow, but the problem is related to the next point.
- Rudimentary save files. I can save and load games and as I suspected, the pickle module is immensely useful. However, the prologue wilderness area is 200X300 and when I save the game the file is 5.8 megs! I can reduce this by storing the dungeon square information in a sparse-matrix. So I only generate the dungeon squares that I need. If the player explores the entire wilderness, though, it will still bloat up so eventually I might have to split the wilderness maps into smaller pieces.
- Cleanup up every place where objects in the game stored references back to the DungeonMaster class. Now each reference is passed in the method calls. Storing a reference back to DM posed problems when pickling - circular references.
- Fixed a bug that was making samurai always start with zero gold.
It looks like Wa is back in development for the time being. God there's so much that needs to be done... | | Thursday, January 15th, 2004 | | 4:38 pm |
8 months later... This afternoon, I tried the cPickling technique and it works like a charm. I'll need to polish it up eventually, but the proof of concept is there. Now I just need to decide what, of the many, many things that need to be finished, should be done next. | | Friday, May 16th, 2003 | | 12:28 am |
It looks my cPickle will save the day in terms of the time it takes to load the map templates. A little experimentation suggested that while minidom takes 3.2 seconds to parse the xml map template file, if I pickle it, I can load it through cPickle in about .19 seconds. Much better. So, I can create a 'map compiler' that will parse and pickle the xml templates (which will be distributed with the game, of course). Some things will be a pain, though, going this route. There are parts of the level templates (such as some NPCs' names) that I want to be random from game to game, so I'm going to have to come up with a way to let the MapLoader class know it needs to fill in some details. A nice corollary is that it looks like cPickle will be more than viable as a save file format. The biggest datastructure in the game will be the map, so saving the rest of the data shouldn't add much time. There are currently some shortsighted aspects of how the UI and game control classes interact. Right now, the DungeonMaster class is the first one loaded, and it creates an instance of DungeonUI. It would be nice if, for pickling the save file, I could just dump the entire DungeonMaster object, but this will mean the DungeonUI will have to be much more independent and, in fact, be created first. Then, a DungeonMaster instance is either created (for new games) or restored from file (for old games). The re-structuring will be worth it, though. The prospect of saving and loading games being one line apiece is just too nice. | | Wednesday, May 14th, 2003 | | 11:52 pm |
For a break from messing with XML files (and wondering what to do with my level files), I decided to implement Nethack-style boulders. This was interesting as the detail needed for roguelikes starts to become evident. First of all, it was an interesting deciding what sort of entity they should be in the game. They are similar to walls and closed doors in that they can block the player's passage, but they aren't really a terrain. Should they be an item? They act like an item, but you can't pick them up (well, giants can in NH), moving onto a tile with a boulder moves the boulder (if possible). It turned out to be almost a non-problem. When I create boulders, I just set them to be the occupant of the square. (There aren't any type issues because typing in Python is dynamic and lazy - it only checks types if it really needs to) So, a tile with a boulder is occupied and in all the places where the player interacts with an occupant have to lookout for boulders. For the moment, that's basicly moving, chatting and bashing. | | 1:23 am |
One idea to think about...what if I pre-generated and pickled the fixed (huge) wilderness levels? Must experiment to see if that is faster. My MapLoader class could still be used for small fixed maps and would also be included to update the wilderness map caches. As an aside, I hope cpickle is fast, otherwise my easy file saving scheme is screwed. | | 1:16 am |
I should mention that the consensus on r.g.r.d. seems to be that a glossary combined with a good e[X]amine command will be sufficient and that I won't need to bother implementing a 'turn Japanese words off' option. | | 1:07 am |
Started developing the town npcs. I'm working on innkeepers right now. (I realized at this point I wasn't yet giving the player any money so that was added to the character generator class). Made a UI tweak to eliminate a bug that was costing the character a turn when they hit alt, ctrl or shift. Loading the 200 X 300 map is really slow (on my celeron 366 laptop with 96 megs ram). On the bright side, I've got it down from 9 seconds to 3 seconds. I'm going to have to test to see if this is the result of xml.minidom being too slow or python xml processing in general is too slow or if it's just that my file is huge. Hopefully I won't have to do it as a plain text format and write my own parser. (The parser would be an interesting problem to solve, but it's supplementary code, not game code) BTW, the service provider for the Wa website is facing issues right now, unfortunately, but don't worry it will be back soon. |
[ << Previous 20 ]
|