I'm in the game now. Monster AI has been ported, objects, moreobjects, and inventory all are in and compile. I managed to clear out about 10 functions from the MissingFunctions collection. Unforunately, the main display seems to be suffering from an off-by-one error in a couple of spots (map looks off-by-one and some of the buildings are the wrong characters).
Sunday, March 28, 2010
Friday, March 26, 2010
The smarter the sphere
{
case 1:
case 2: /* change direction to a random one */
sp.dir =rnd(8);
default: /* move in normal direction */
....
Updates delayed due to me catching the Starcraft2 Beta sickness (what a fantastic sickness to have...) and being in Hawaii. Finished converting object.c. Still blocked from the game loop due to not implementing moving Spheres of Annihilation. I need to fix a few more compile errors. This file got much simpler since I'm not using a linked list but a C# generic list now. I did enjoy finding the snippet above though. The smarter you are, the less likely the sphere is to change directions.
Sunday, March 14, 2010
Highly coupled source
Ignoring the fact that everything shares a global namespace, I've managed to hit the most highly coupled parts of the source. To be fair, some portions of a game are all but impossible to make any other way (i.e. AI has to understand all the game data, so even if you try to abstract the information for the AI, something has to convert it into that form which leads to high coupling).
In Larn, lookforobject is the highly coupled function. In its own words:
/* LOOK_FOR_OBJECT
subroutine to look for an object and give the player his options if an object
was found.
do_ident; identify item: T/F
do_pickup; pickup item: T/F
do_action; prompt for actions on object: T/F
*/
It doesn't look like combat is handled here, but I suspect that the next time the source compiles, the game will be 80-90% converted.
Saturday, March 13, 2010
Connecting disparate programming models
I'm still trying to keep my graphical text rendering separate from the actual Larn game code. Unfortuantely, that means that there are two active game loops. I've managed to get around that by threading and sharing the TextConsole object (a char array with terminal functions (add text, move text, etc. Basically a VT100/Ansi equivalent)). The DX9 engine creates the Larn game on a separate thread and waits until it creates a TextConsole, which it then grabs a reference to use for pulling data for rendering.
The biggest issue that I'm having is that I'm very nervous about race conditions with the Cursor object. I currently pass around an object reference, but I think I'm going to need to stop doing that and only pass around the Vector2 position. It's currently possible that an update from the rendering engine will cause the cursor to start being displayed. If the Larn engine then attempts to move the cursor, the two could stomp over each other. I haven't seen it happen yet, but it could.
Finished create.c
Almost into the game. Create.c is converted, now I've got 3 functions left to finish the dependencies for before I'm in the game.
makeplayer
newcavelevel
checkmail
I think I've spent at least 20% of my time doing explicit casts. One core difference between C# and C is that C# considers casting to a smaller size to be a compile error that requires a specific cast (which is fair).
Thursday, March 11, 2010
First bug found
Aw, code, it grows up so fast!
main() calls welcome();
welcome() calls openhelp();
If welcome() can't open the helpfile (I haven't copied the data files over yet), it calls drawscreen()
drawscreen() calls bot_linex()
bot_linex() calls lprintf(" Exp: %-9d %s\n",(long)c[EXPERIENCE],classname[c[LEVEL]-1]);
since c[LEVEL] = 0, that attempts to access classname[-1], which is invalid.
the bug is that drawscreen() shouldn't be calling bot_linex() at this point. There is a special flag (d_flag) that would have prevented it, but it isn't getting set in this corner case. It's possible that the variables used to detect if d_flag should be true are getting set are improperly getting set. I just added an extra flag to drawscreen in this case.
Wednesday, March 10, 2010
Finished converting display
Finally getting into some real game code. I had to stub out functions like:
hitmonster(int x, int y)
act_ignore_altar()
CONVERTED: 12 File(s) 176,026 bytes
OLD : 43 File(s) 458,630 bytes
Sunday, March 7, 2010
Working on display
Converting the display code is very slow going. Unfortunately, I'm having to combat the elegance of C and undoing several "optimizations". Basically, the game keeps the player information as an int[100], and also keeps a backup in another int[100]. So when it comes time to update the display, it loops over each element, compares it to the backup, then updates it (if necessary). This makes the display for active spells actually elegant:
for(int i =0; i <>
if( c[i+spellOffset] != cbak[i+spellOffset] )
botsub( i+spellOffset, screenX, screenY, "%-3d" );
Very nice elegant way of looping over the data, but hell to convert if you actually use a structure (unless you also union it with a big int[], which I'm refusing to do). So the new version involves a switch on the int, which will eventually be converted into a switch on an enumeration of active spells.
Is that _legal_?
sort[k] = sort[ k-1 ], k--;
The comma operator? Wow... I had never even thought of doing that. I suppose it's probably legal.
incontrovertible
Good progress this morning. tok.c, help.c are done. tok.c is weird since it contains yylex (i.e. lex), which seems particularly strange since I don't recall Larn parsing anything more than 3 characters at a time for spells. It's only called by main though, so I may be able to just remove it later.
And then I get to drawscreen(). Yay! Time to start rendering! drawscreen lives in display.c, and the first thing I see is this:
#define botsub( _idx, _x, _y, _str ) \
{ \
cbak[(_idx)] = c[(_idx)]; \
cursor( (_x), (_y) ); \
lprintf( (_str), (long)c[(_idx)] ); \
}
Incontrovertible. I mean, I could always just redefine the character class to be all the fields in a union with an int array. But really, that's terrible. I don't currently understand how many places this is used, but I'm hoping it's 0 or 1 because this is just terrible.
Saturday, March 6, 2010
Almost into the game!
9 File(s) 142,860 bytes / 43 File(s) 458,630 bytes
IO.c is done... mostly. It's in tatters and I may have cross-pollinated the file writing IO with the display writing IO (if it wasn't already cross-pollinated). The whole file is buffer after buffer after buffer. The game has an internal output buffer, that gets buffered for VT100/ANSI, and those have their own buffers too.
As it stands, I'm almost into the game now. Logs are created, the terminal is setup. I'm up to the difficulty level being set (references tok.c, not started yet). I expect to be in the main game tomorrow.
Inverse video?
This does not compute. It's just copying a string to the magic lpnt pointer (actual buffer output) and tagging it with some obscure start and end tags. I'm going to go out on a limb and guess that it means color the background white and the foreground black. Inverse video? Standout mode?
/*
*/
void lstandout(string str)
{
*lpnt++ = ST_START;
while (*str) {
*lpnt++ = *str++;
}
*lpnt++ = ST_END;
}
Screenshots
Let's see if I can get some screenshots posted. This is from the DX9 engine (working fine rendering text). The engine is done, but it is not hooked up to the Larn game engine yet. The first shot is just text rendering, the second shows the underlying wireframe. A friend asked for a screenshot of the text rendering engine. "Open command prompt and type something. That's exactly what it looks like".
Current status - 20% ish
My initial plan was to simply convert everything in place and avoid anything that would change how the code worked. I would start one C file at a time, starting with main.c, and stub out all missing functions. The first files I had to pull in were larncons.c and data.c (constants and data structures).
That's when I decided my original plan was not going to work. Version 12.4 is likely pretty close to the original source, and that's a big problem. I'm pretty sure that the version of C this was written for originally didn't support enumerations and possibly didn't support structs (if it did, I'll cry. There are 3 structs so far -- cel (dungeon cell, 5 fields, sphere of annihilation (5 fields), monster (11 fields). The core character data (20+ fields) is just a byte* with a ton of #defines that identify what field lives where. And the only appropriate name for this pointer is of course "c", which is obviously short for character. You couldn't shorten that to char since that's a type, so it's just c. c[HP] c[GOLD], etc. Monsters are also a giant list of #defines. It's enough to make a person cry.
So I started refactoring while converting. It took several hours to convert the data and constants file, then 2 evenings to finish converting main and stubbing out functions. I tried starting the game, but was immediately hit by not having the save files/scoreboard functions done, so I started converting those (scores.c). That took another evening. I'd estimate that I'm about 20% done. So far the conversion looks like:
CURRENT: 8 File(s) 128,537 bytes
OLD : 43 File(s) 458,630 bytes
Some of my notes from the conversion:
1) Scroll names all begin with \0. I wondered about this for awhile and eventually found out that that entry is being used as the number of scrolls the player has. I don't even think modern C allows true compiled in string entries to be modified. That's right out.
2) I had to change all file serialization. Larn does it in classic C style. lwrite( pointer, sizeof( pointer type ) ); I'm using readers and writers and actually explicitly writing out each field. For the moment, that means I am no longer backwards compatible. That's fine, I'm going to tag files with a header to differentiate between these new saves and old saves. I'll implementreading of old save files later.
3) I remember in college saying how stupid it was to even have a boolean type. It's just an int under the hood anyway. I regret that. I'm converting many implicit booleans into true booleans. It wouldn't be so nice except the code sometimes expects 1 to be true and other times expects 0 to be true (-1 being false).
4) The core data objects are now a LarnData object. Encapsulation, heaven forbid! Of course, that means most functions now need this object passed into them. Honestly, I'm not sure if this was a bad idea yet or not. It was this or make everything (EVERYTHING) static.
5) I changed many longs to UInt64's since it was obvious that you can't carry negative gold. Unfortunately, this has introduced a lot of casting issues. This may have been a mistake.
Starting a blog to document daily progress converting Larn to C#/XNA
I decided to start documenting the process and status of converting the old text based dungeon crawler Larn to C# and XNA.
My motivation is simply that even the current incarnation of Larn running on XP or through Dosbox doesn't quite mesh with my memory. Maybe there is a way to replace the # walls with some of the blockier higher numbered ANSI characters, but I can't seem to find a way to do it. So I decided to just take the latest source, rip it apart, and rebuild it in C# using XNA to actually render the text through DX9. Why DX9? I intend to post-process the console output with a technique similar to Phosphor 3x ( http://nfgworld.com/mb/thread/661,3 ) so even playing on my LCD will still look more like it looked playing on my old 8088.