Posts Tagged ‘kroz’

Anatomy of a Web App

Tuesday, October 4th, 2011

Hello! Time to dissect the application I’ve mentioned several times already: Cruz.

Long before starting Cruz, I had built many, many software applications. Some used sophisticated requirements, design, and configuration management techniques. Others, not so much. This one falls into the “not so much” camp.

But Cruz, like many other applications I have built, has very sophisticated programming. With me, that’s just the way I do things. I take pride in my programming skills, and I will go the extra mile to develop to a high level the engine of any game I create.

Cruz is a Flash game. Many Flash games are rich in graphical and sound content, but they lack programming sophistication. Inability to program forces many designers to act at the mercy of whatever tools or engines others have built, which further limits the types of games they can create. Could a graphical designer who knows little about programming create something like Cruz? Well, read on, and see for yourself.

System Components

There are six main components of the Flash application:

  1. Flash file (Cruz.swf)
  2. Graphical content (embedded in Flash file in this case)
  3. Audio content (MP3s loaded at run time)
  4. Level design data (XML files in “Cruz” format)
  5. Server-side scripts and storage (PHP and text files)
  6. Client-side savegame, progress, and config info (Shared objects)

The Flash file is loaded first, usually as an embedded object in a web browser, with graphical content loaded automatically. The audio content is loaded in two contexts: the sound effects at the beginning (a preloader precedes the title screen), and the musical tracks on an as-needed basis.

The level design data is loaded on an as-needed basis per episode, which is only when the user starts a game for a particular episode. The server-side scripts are used when handling high score update and retrieval operations. The savegame, progress, and config info are periodically updated and saved by the Flash environment on the client side.

If you’re building a Flash application, there’s a good chance you won’t need to touch the XML API, or write server-side scripts, or use shared objects. You might not even need a preloader, choosing to embed everything in the SWF. This keeps everything simple, but it limits what you can do, programming-wise.

If you’re building a game designed to give the user a full experience of rich content, savegames, and high scores with competition from everyone in the world, the “web application” way to do it is substantially different from the “home PC” way to do it or the “game system” way to do it. Different APIs, different components, different implementations of components.

But the underlying programming effectively does the same stuff, no matter what the platform is. You have load operations, save operations, ins, outs, etc. Business logic is business logic in any language.

Classes and Programming

What about the programming for Cruz? Well, as managed applications go, there is little to the imagination. Let’s look at what Cruz implements for unique classes:

  • utils: Low-level utility functions, such as sign, absolute value, random chance, etc.
  • IPoint: Coordinate pair of integers (x and y).
  • Dir8: Eight-directional calculation functions and lookups; useful for 2-D gridded games.
  • FxCont: Field effect container class (a rectangular region with specific game properties).
  • objid: Enumerated constant and name lookup container for game object types.
  • fxid: Field effect and level feature lookups.
  • SF: Special flags-per-grid-cell storage and lookup.
  • SaveGame: Represents savegame data.
  • CruzOptions: Represents configurable options for the game.
  • Game_Characters: Storage and functionality for ASCII-noir characters used in background text.
  • RandInfoArray: Grid randomization functions.
  • CruzMedal: Storage and functionality for medals (unlockable achievements found in various parts of the game).
  • Sounds: Storage and functionality for sound effects and music.
  • PathCalc: Mouse click-to-move path calculation functions.
  • MsgParser: Special object message parsing class; used with tablets and special items.
  • GPlayer: The main character’s object (the player).
  • iobj: Individual game objects, capable of interaction. A single GPlayer individual is always present as the first individual.
  • rtobj: Real-time object, representing a short-lived, animated object in the game that does not interact with anything.
  • cruzparse: XML parsing functions for translating level design content into game data.
  • game: The main gameplay class. This covers most general gameplay business logic and functions.
  • cruz: The main interfacial class, controlling Flash-specific API operations and API callbacks, such as user input, timer, load complete, etc.

If I were writing in C# for a PC game, the class layout would look the same. If I were writing in C++ for a game console, the class layout would look the same.

With three exceptions, that is: enumerated constants, polymorphism, and server-side handling.

Enumerated constant definitions will look a bit different across languages. The differences across C++ and Java alone are pretty substantial. Enumerated constants are not directly supported in ActionScript, not even in AS3. If you want to make a constant, you must declare it as a named static constant in a class. I ended up doing this for fxid, objid, and SF classes, to name a few.

ActionScript does not support virtual functions. Even if you derive from a base class, you must cascade functions manually at every level of function implementation. This is annoying. For this reason, some objects, like iobj, were not broken into individual subclasses.

Server-side scripts are necessary for Flash applications that want to maintain a central data repository; there is no dedicated client-side API for handling server-side data. The PHP scripts are very simple. All that the scripts do is take HTTP requests from the Flash application and fire back resources at the application. Just a little text file cooking, that’s it.

Special considerations

As far as the class implementation is concerned…well, it’s here where software engineers earn their pay. I have been programming for well over a decade. It takes skill to know how to properly use arrays, vectors, integers, floating point numbers, and strings. A surgeon can show off the anatomy of a rat, but he can’t tell the lay observer how to sew the rat up so that he instantly knows how to make it live and breathe again.

So I won’t go into the detailed design and cover every function in Cruz. Not today, at least. But I will discuss a very important aspect of programming for web applications: respecting the fact that what you are creating is a web application.

Decades ago, we never would have dreamed that so many of these web applications would conduct advanced calculations in the background. That’s what a client-side web application usually is: a background application. It can be an advertisement, or a game, or a slideshow, or a fundamental user interface needed to enter data into a form. Regardless of what the application is used for, it should be expected that the user’s computer will be doing other things than paying every bit of attention to running your web application.

This means that slack needs to be built into timing routines that might have demanded precision otherwise. Synchronization of animation frames and timer ticks is not guaranteed. CPU performance could be drained by a computer function not even remotely related to the web browser or its applications. The user might destroy your application’s operating environment at any time by closing the window, forcing you to update shared objects or call server-side scripts at key moments to avoid data loss.

Oh, and one final hiccup: the gargantuan task of making Cruz, a cutting-edge netted AS3 application, resemble the gameplay and timing mechanisms of Kroz, a non-netted Pascal application, written over 20 years earlier without any event-driven programming.

Now you know what went into Cruz. Web applications aren’t necessarily easier or harder to program than general-purpose applications–they’re just different.

But in some ways, they’re the same as ever.

That Smoooooth User Interface

Friday, July 22nd, 2011

Haven’t you ever gotten annoyed when you want to do something on a computer or handheld device, but you can’t? The user interface is the one thing stopping you from doing “that one obvious thing” that you want?

I have just identified one of the most common sources of “feature creep” in software. When people test an application’s user interface, the feedback is often one of extraordinary inconvenience to the developers of the user interface. No matter how easy or hard it is to give the user what he or she wants, it has to be done. Because people are only human.

But computers are not. Computers are dumb, and a programmer often has to reinvent in code what a human has already thought of and has no trouble executing in reality. The subject of today deals with a difficult, and yet necessary, user interface improvement to a video game.

Remember Diablo? You click the mouse on a spot, and you move to it. If there is an item, you pick it up. Or if there is an enemy, you attack it. If you have a long-distance weapon, you attack it from far away. Why did this system work so well?

Because it reflects what people want. If a person wants to do something in a user interface, the most intuitive thing to do is to interact with the object or location where the action must occur. For this reason, a top-menu is good, but context menus are better, since the action and options are localized. Keyboard shortcuts are good, but allowing a more intuitive (if longer) mechanism is better. Drag-and-drop for directory files, as well as inventory item screens, is reflective of the pick-up and let-go motions that a person will perform when carrying a real, physical object.

Once a popular game uses click-to-move controls, people start to expect, and then demand, click-to-move controls in similar games. Kroz, a game of the late 1980s and early 1990s, got by with keyboard-only controls because mice did not play a big role in the games market back then. But this is 2011, so keyboard-only controls are not acceptable for many people today.

When I developed Cruz, which modernizes Kroz, I realized I was dealing with a serious user interface problem: now that people like to use their mice in dungeon-crawler-type games, how should I allow the user to employ the mouse for a game that was never designed to work with one?

I decided that click-to-move would work best for Cruz. Some games featuring ASCII characters use mice in a different fashion. ZZT, for example, had analog mouse movements move the player, instead. This was clunky and unreliable, and for Kroz, where precise movements are very important, a click-to-move scheme is much more desirable.

But click-to-move is not straightforward to implement at all. The click-to-move system in Diablo is quite sophisticated, because so many implicit desires of the user need to be taken into consideration, all without the user ever telling the computer anything! These advanced, not-so-obvious features are all part of Diablo’s click-to-move system:

1) Moving around obstacles. The system must reconcile how the player can get to the destination, even if no direct line of progress exists. This is easy for a human who can see, but for a computer, it’s only as easy as for a human who is blind. The heuristics require probing objects to “feel around corners” in order to find the ideal path to the destination.Normal destination for click-to-move

2) Give-up factor given impassible terrain. The user might click a location that cannot be reached, no matter how close the player is physically to the spot clicked. The “feelers” will never find an impassible spot, so the heuristics need to “know” when to give up and flag the movement as impossible.Impossible destination for click-to-move

3) Target tracking. If the user clicks on a moving target, the path to the destination must be constantly recalculated as the target moves. It’s even possible that the destination will become impassible mid-way through the player’s path traversal.

4) Target prioritization. Is there always one possible target in Diablo? Nope. More typical is a horde of 10 or more foes, all charging at once. If the player clicks on a spot at the end of the pack, or beyond it (if he or she wants to run away), what happens when the remaining foes block the destination? Well, the effects may vary, but the rational assumption that most people have is that a recalculated “flight” response is appropriate if no foe is the target, but a “fight” response, with the nearest foe, should be chosen if a foe is the target, or if a “flight” response is no longer an option.

People want simple. But simple is not simple to implement. Clearly, one would run into the same issues faced by the Diablo development team while developing a click-to-move system. I could forgo movement around obstacles, but that would annoy people. At the very least, handling obstacles was an absolute necessity for Cruz.

But a game’s user interface must be shaped around the game. A direct copy of another game’s methods never works. Cruz does not require things like target tracking, but it does require something similar to target prioritization. There are a lot of object types in this game, and many of these objects have both positive and negative consequences when touched.

I had to develop a set of “navigation rules” based on the types themselves and where the user had explicitly clicked. These are the rules:

1) Commit to movement to a position if the user directly clicks on a spot, regardless of consequences.

2) If user had clicked once on a spot, the path taken is a careful one. Only visible items that are worth getting and don’t hurt the player are picked up automatically. For objects that hurt the player, the path avoids them completely, even if it is possible to move into them. Single-click (avoids hazards)

3) If user had double-clicked a spot, the path taken is a hasty one. Passage over objects, even those that hurt, is possible, as long as the object isn’t a path dealbreaker. A path dealbreaker is something that would never allow access to the destination if crossed, such as a teleport trap or a bottomless pit.Double-click (cuts through hazards)

Some objects are only reachable by click-to-move if clicked on directly: solid walls, doors, lava, pits, etc. Since Kroz games require careful movement, recklessness in the form of clicking explicitly on the wrong spot is punished the same as with an errant key press. But implicit movement into danger should not happen simply because the user didn’t spoon-feed the computer an exact path. Why should the computer err on the side of a fundamentally nonsensical decision?

The click-to-move system would be great even if just left with the above rule set. This is pretty much the way it works now, if you want to try it out at http://www.chriskallen.com/cruz/cruz.html.

Alas, Cruz is a more complex game than that! What about boulder pushing? Puzzles must be solved by pushing boulders in eight directions. When should click-to-move push objects? Also, several levels in Cruz have gravity. How should “feelers” work if the player must climb and jump in order to reach certain destinations?

Diablo offers no guidance, since neither pushables nor gravity effects factored into that game’s navigation system. Cruz does implement rules for these special cases, but more work is still needed. What is intuitive movement for dungeon-crawler combat is not intuitive for puzzle-solving or side-scrolling action.

Now you have an idea of some of the user interface issues that a software engineer has to deal with! So the next time you find the need to complain about not being able to do that…

Remember, “that” is often a really tough challenge behind the scenes.

A Game Resurrected!

Wednesday, June 8th, 2011

Hello; it’s been a while. There hasn’t been much to say on the formatting blog, since I’ve been busy working at GD and making the game engine Cruz in my spare time.

This is the subject today: Cruz, and its level design format. Pictured below is a screenshot of “Return to Kroz” played in the Cruz engine. As you can see, the game turned out rather well.

Cruz

Others seem to really like this game. You can play it at one of these locations:

http://www.chriskallen.com/cruz/
http://www.newgrounds.com/portal/view/569388

Moving beyond “amateur-experiment” games into “pro” games is something I’ve wanted to do for a long time, and it appears I’ve met success with the Cruz demo: I have successfully resurrected the Kroz series from the annals of DOS history.

CruzEditor, the subject of my last post, has not changed much. The game itself just reads the XML content created with CruzEditor. The devil, of course, is in the details of the XML content.

ActionScript 3 provides a robust API for parsing XML. You can reference individual branches of the XML “tree” via dynamic properties. For example, if XML has encapsulated an attribute of an element called “stuff,” you can access it from the top-level XML object by writing in the code “outer.inner.element.@stuff” without having to wade through string parsing routines or numerous “get” API functions.

Okay, but how does one store the level design content for Cruz, let alone all the Kroz games?

Of the eight Kroz games made by Apogee, each one was its own executable. Having a single application take on the level set-up responsibilities for all the games, each with its own subtle variations on format, was a big challenge. I have written a whole technical article about all the Kroz/Cruz differences here:

http://www.chriskallen.com/cruz/differences.html#differences

I decided that a hybrid of the original level design formats for Kroz and my own additions would be suitable. The character encodings for ordered levels would stay the same (with the exception of text), the unordered levels would be encoded as individual named assignments (not the hard-to-read DF assignments in the original Pascal), and level/episode features would have their own containers to allow for basic design tweaks, such as gravity, terrain growth, quake drop type, whether level was sideways, etc.

The advantage of the format is that it is way more manageable than before–as human-readable XML, it allows for no misunderstanding of what is represented.

The disadvantage is that the content is a lot larger than the original Kroz binary-encoded data. This might have been an issue 10 years ago, but with people streaming video to their computers, a few hundred kilobytes no longer matters as much.

Load times are reasonable for Cruz. Oddly enough, the John Wilson “Hidden Caves of Kroz” episode, which I had nothing to do with, has very long load times for some levels. I suspect that game’s metadata is handled much less effectively.

That there was no modding community for Kroz was disappointing. With CruzEditor, we can develop such a community–one in which anyone can write their own Kroz adventures. Finally!

Popularizing the series once again will be easy, too. Before Cruz, basic attempts at porting Kingdom of Kroz II had been done, but not the entire series. Now, at last, gamers can play the entire series online, including the mother lode of Kroz adventures, Lost Adventures of Kroz!

Of course, you can’t build a modding community if the moddable game isn’t good. Generally speaking, people don’t mod stuff that sucks, because it doesn’t become popular. So, I didn’t just simply recreate Kroz when I made Cruz–I gave it a big overhaul in terms of engine.

ASCII characters have been replaced with actual sprite models. Animation is now much better. Sound effects are mostly the same due to nostalgia, but there are a few new ones as well. Timing is controlled and reasonable, which was a big problem with Kroz games as CPU speed increased. And most important of all–I wrote an entire musical score for the game, something Kroz didn’t have originally.

There is one thing missing from this whole enterprise–the author’s blessing. You might wonder why I’d do all this without consulting him?

First of all, I didn’t think I needed any sort of official go-or-no-go from 3dRealms. The source code was released as Freeware, and I picked it up and ran with it–my own way.

The other reason is that they probably don’t care much about the Kroz franchise anymore. At this time, it seems like they’re actually going to release Duke Nukem Forever, after a decade of anticipation. Who cares about some stupid, outdated text-mode game that has aged terribly and means almost nothing to modern gamers?

Answer: I do!!

Programming Made Easier with the Right Formats

Friday, March 26th, 2010

We’re going to go WAY back in my programming career this time. I’m almost embarrassed, in fact, to be mentioning this. My first real attempt at action game programming was making a game out of a silly paper drawing that my friends and I had worked on, called a “Torture Course.” Screenshot is below.

Torture Course

It looks like your typical “get through a dungeon full of traps without dying” type of game. More or less, it’s true. ASCII characters plopped on the screen, leaving much (okay, just about everything) to one’s imagination.

Incredibly, a game this early in my programming career (circa 1991) had sound programming, mostly in the form of creative effects when your character gets zapped, stabbed, burnt, cut up, etc.

But how was the level information stored? How did I edit it? How to test it?

Heh heh…well, folks, I hadn’t yet learned that you can actually call API functions to save and load information. What’s that, you say? You can SAVE your designed data and LOAD it later?

If you don’t believe that the level was generated ENTIRELY by code, just look at a sampling of how the BASIC code was constructed:

19 I$ = INKEY$: IF I$ = “y” OR I$ = “Y” THEN SYSTEM ELSE IF I$ = “n” OR I$ = “N” THEN 0 ELSE GOTO 19
20 I$ = INKEY$: IF VAL(I$) > 6 OR VAL(I$) < 1 THEN 20 ELSE TYPEVAL = 0: CHAIN "intermis.bas"
49 L = 40: FL$ = CHR$(15)
50 A = 2: B = 1: C = 102: SQ = 6: F1 = 26: F2 = 26: F3 = 30: F4 = 30: UP = 1: LEFT = 1: F$ = "M": T1 = 3: T2 = 6: WP1 = 10: WP2 = 25: JUMP = 2: H = 3: LF = 1
60 LA$ = CHR$(175): W$ = CHR$(219): U$ = CHR$(24): D$ = CHR$(25): R$ = CHR$(17): L$ = CHR$(16): LS$ = CHR$(176): WP$ = CHR$(254): V$ = CHR$(179): LN$ = CHR$(196)
61 CLS : COLOR 8: LOCATE 1, 1: PRINT STRING$(45, W$); : LOCATE 8, 1: PRINT STRING$(45, W$); : LOCATE 8, 40: PRINT " "; W$; " "; W$; " "
62 FOR X = 2 TO 7: LOCATE X, 1: PRINT W$; : LOCATE X, 9: PRINT W$; : LOCATE X, 16: PRINT W$; : LOCATE X, 24: PRINT W$; : LOCATE X, 45: PRINT W$: NEXT
63 LOCATE 2, 2: COLOR 10: PRINT V$; " "; V$; " "; V$; " "; V$; " "; : COLOR 6: PRINT WP$; SPC(13); : COLOR 8: PRINT W$; : COLOR 6: PRINT WP$; : COLOR 8: PRINT " "; W$; " "; : COLOR 10: PRINT V$; : COLOR 8: PRINT "oooooo "; LS$; " "; LS$
64 LOCATE 3, 2: COLOR 10: PRINT V$; " "; V$; " "; V$; " ": LOCATE 3, 16: PRINT " ": LOCATE 3, 32: PRINT V$: LOCATE 4, 2: PRINT V$; " "; V$: LOCATE 4, 27: COLOR 8: PRINT W$; W$; " "; : COLOR 10: PRINT V$
65 LOCATE 5, 2: PRINT V$: LOCATE 5, 28: COLOR 8: PRINT LS$; " "; : COLOR 10: PRINT V$; : LOCATE 6, 28: COLOR 8: PRINT W$; " "; : COLOR 10: PRINT V$: LOCATE 7, 2: COLOR 12: PRINT U$; U$; U$; U$; U$; U$; U$; : COLOR 8: PRINT W$; : COLOR 14
66 PRINT LS$; LA$; LA$; LA$; LA$; LS$: LOCATE 7, 24: COLOR 8: PRINT " "; LS$; " "; : COLOR 10: PRINT V$;

Ha ha ha ha he he ha ha ha–oh, whoops. I gotta continue this entry. You can continue laughing if you want. If a software engineer in his right mind would ever code like this, he would have to be under one intense deadline. Obviously, coding discipline and coding standards are very important, neither which I had even heard of back then.

If only I had thought ahead of time how to put the program’s architecture together. But when you have only your own resourcefulness, every line of code you write is a proof of concept in some fashion. I will note that programming courses on the level of what I was trying to do were NOT offered at my schools. So I improvised.

Walls and movers were drawn on the screen with for…next loops. If…then statements were used to check for collisions. And I mean a LOT of If…then statements. One for each wall segment or obstacle. The Torture Course bears a striking resemblance to a game known as “Escape from Epsilon,” which at the time, I had never seen. But the creators of THAT game had it together. I clearly did not.

It would have been far better if I had designed the levels in a text editor, with various symbols representing the walls and obstacles. It would have been much better if I had simply chosen an intra-code gridded format, like what Scott Miller did with Kroz. Making my own independent level editor would have been excessive, though. Especially when you consider that I’d have to create an entirely new format.

Speaking of which, there are a LOT of game design formats out there. Since every game is its own beast (and often its own programming masterpiece), it’s reasonable to assume that just about every game has its own custom formats, specific to JUST that game. Well, sort of. The resources used to create games are now a lot more standardized than before: GIF, BMP, JPEG, PNG for images, MPEG, MOV, and AVI for video, WAV, AIFF, and MP3 for audio.

But the level designs for games are all over the place. I’ve got several of my own: NIB, NGR. There are FR files, UNR files, MAP files, and the list goes on. Savegame formats? Once again, all over the place. What do these mean? Should we care?

Granted, there are somewhat standardized level design formats. A software engineer must decide on the ease of implementation when figuring out how to store the formats. I’ve mentioned how BARfly can transform text into binary with the formats used in Brian’s Journey. But really, a designer needs to start out with concepts, and not so much formats.

What is the designer trying to represent? If it’s a static 3-D map, try using an off-the-shelf editor. If it’s a standard 2-D grid, try using an off-the-shelf editor. If you need to start adding game-specific features that no off-the-shelf formats support, think about XML or other text-based formats. There should be no shame in starting out a level design with multiple formats per level. After all, many maps are designed in such a fashion: static elements on one layer, and actors and dynamic elements in additional layers, which are placed at pre-map based on a specific context.

Software engineers are supposed to be resourceful. Use text when you want to edit quickly and experiment. Use off-the-shelf editors to generate static designs.

But whatever you do, DO NOT create your own level editor unless you think it’s value-added in terms of time. It can save loads of time if the editor and the game app are the same program, but remember, the cost of developing the editor is something you can’t get back. Try to figure it out:

Is (Cost of Editor Creation) + [(Less Time Editing) * (Total Number of Levels)]

less than or greater than [(More Time Editing with COTS editor and text files) * (Total Number of Levels)] ?

In my case with the Torture Course, and in Scott Miller’s case with Kroz, a single change to a single level feature required a recompile of the ENTIRE program. And back in the day, compile times were not fast. That adds to your development and testing time!

In the case of Nibbler, an intra-game level editor allowed much more rapid development and testing of levels, even though it’s just a compressed gridded format.

I guy I know named Josh uses text files to script when enemy ship formations appear in his side-scrolling shooter. Is that wrong? He has claimed it could be viewed as lazy, but I think, what’s a more straightforward way to fine-tune level features than to modify a text file?

There is no consistent answer to how you should use formats to make your development job easier. But if you ask the right questions, you’ll be in good shape.

But one thing is clear: good riddance to “FOR X = 2 TO 7: LOCATE X, 1: PRINT W$;” !!!!

Software Development Case Study!

Wednesday, September 30th, 2009

Now here’s something software developers will find very useful! The following is a case study on how to use BARfly to design and implement levels for a video game.

I’ve already got several video game file types supported here. In particular, WAD (for Doom), NIB and NGR (for Nibbler), and a special Kroz level extractor from source code, which I’ve mentioned in previous entries.

But what if your level designs are more complex than linear? Whether it’s in binary or text format, many developers can get by with just a linear file format. A grid speaks for itself, with or without run-length encoding, and objects can be spot-placed with coordinate pairs (or triplets). Unfortunately, many games have trickier design formats. Like these:

  1. Doom. This and other FPS games geometrically organize the points, lines, and textures. But when it comes to linking all these objects together, you’ll need a system to “compile” them into something usable before a game can be played.
  2. Abuse. Not as well-known as Doom, but still worth a mention. Abuse is a very curious beast because LISP is used to drive almost all parts of the gameplay. What is LISP? It’s a linked-list based language, one you’re likely to encounter only in grad-level computer science. Each object, each enemy, etc. has one or more “links” to another object or enemy. Pretty open-ended!
  3. Diablo. This game, and its sequel, Diablo II, have dynamic level design. This means you don’t just have a level grid or spot-placements of objects: you have patterns, probabilities, and schemes determining what a level “might” look like. Levels are mixed in a similar fashion to Nethack or Rogue, which are text-based predecessor games that influenced Diablo’s creators significantly.

Questions to ask: how do you store links and relationships between objects? How do such relationships shape the data structures and storage formats themselves? How much work must be done in the construction of the levels versus the game engine’s actual setup process?

For credibility’s sake, I have no choice but to answer the above questions myself. I’m coming out with a new game soon, called Brian’s Journey. This uses a new game engine, which, at the core, is just a platform-independent graphical wrapper around the BAR engine. To design a level in this game, you’ll need two files: a room definition file and a shape interpretation file.

Text or binary? How about both? This is a profound “gotcha” for game developers. People care immensely about the end product (the game), but the path to the end product can be impossibly bumpy. Do you make text files, which means you use just a text editor, saving time, but perhaps making visualization harder? And making a tricky text-to-binary conversion part of the engine? Or do you pack everything in binary, forcing you to construct a complex editor that few people might ever use, and even fewer like using? Giving you the advantage, perhaps, of less pre-map processing when the level is actually played?

Thanks to BARfly, you can actually have both. Here’s what the shape interpretation file looks like for Brian’s Journey:

Shape file (text)

Looks kind of like source code. In a manner of speaking, it is. Now look at what happens when BARfly loads it:

Shape file (BARfly)

Awesome. Everything’s a node. But do we really want to cycle through all that filler syntax to get at the associations we need? After running the I.F. function “Normalize,” the data looks like this:

Shape file (final)

This function strips out comments and whitespace and gives us just name-value pairs! Architecturally, a program won’t have any trouble accessing this vectored data. Here’s my actual game engine code that loads a shape interpretation file:

//Load interface to shape file.
sh8 = i.Create_BAR(“lf8r_shapeinterp.bar”);
if (!sh8) return 1;

//Load file.
if (sh8->Load(filename) < 0) return 2;

//Normalize (get rid of extra components).
assoc_count = sh8->Function_Call_L(“ShapeCharAssociations.Normalize”);

Not many lines, huh? With so much of the parsing and conversion done in the I.F., the game developer is free to keep the game’s business logic pegged to architecture, rather than low-level parsing.

Okay, great. But that was the easy part. What about the room definition file format? Here’s an snippet of a room definition file:

Room file (text)

Quite complex, indeed. The room definition has some parts ordered and some parts random–taking the best principles from Nethack, Diablo, Kroz, Vintage Hyperactive, and a variety of static level design formats. And because it’s text-based, you won’t need to design rooms in an editor (although you can later). Loading in BARfly gives you the following:

Room file (BARfly)

Do we have normalization I.F. functions here, too? We do. We run first Assimilate_Binary_Style, which removes comments, whitespace, etc…

Room file (intermediate)

…and then Normalize, which translates the remaining text-based nodes to binary-based nodes.

Room file (final)

Before, everything was a text field. Now, everything is a binary structure. If you save this file and reload it, it stays a binary file!

This is a rather novel method, from a software architect’s perspective, when translating text into binary. Traditionally, an architect will need to store text and binary fields separately, forcing a kind of “double vision” to develop. On one side you have your human-readable text, and on the other side you have your binary structures and relationships, which require their own separate architectures for processing.

But with BARfly, you can implement and test all the parsing and conversion logic without ever having to make the video game that uses it (I haven’t yet). There is no “dual architecture.” There is only one. Rather than throw away the source, you preserve the physical relationships of the nodes, and gradually “nibble around the edges” of the text architecture until it becomes a binary format.

Because the I.F for the room definition files does all the heavy lifting, the game engine only needs to deal with the binary format. Compilation isn’t just implicit as part of the level’s load process: it’s totally invisible to the game’s architecture!

Some of you sharp developers might have started to wonder what this means for games that would like to have the ability to read other games’ file formats. Currently, it’s not worth it, because you’d need to invest in a whole separate library of code devoted to reading just that other format. But if the architecture is pre-built into its own BAR implementation file? You’d just need to distribute that I.F., nothing more.

RegExp in BAR: An Application

Monday, August 24th, 2009

We know regular expressions are used all the time. But what do they look like, and how do they fit into the larger scheme of things with BAR?

I’ve provided a case study here. There is an I.F. for Kroz level files (one of Apogee’s earliest game creations) on this site. When I designed the I.F. originally, it picked apart data from source code by using BAR’s 1.0 functionality, which was relatively limited–binary parsing only. But source code is best parsed as text, by regular expressions…which only debuted with version 1.3b of BAR.

No one should reasonably expect to write binary parsing code for most text formats, especially if you aren’t dealing with a cornucopia of powerfully optimized functions in your arsenal. Instead, just write regular expressions for the simplest of syntaxes and work your way up in complexity. Here’s a complete list of the regular expression assignments used in kroz_alt.bar, the 1.3b-capable version of the Kroz level file reader:


//Unordered level syntax
block unorganized textual df_header ::= "DF[" ["0-9"]+ "]:=" [^"'"]* "'";
block unorganized textual df_chars ::= [^"'"]*;
block unorganized textual df_transition ::= "'" ["\x0-\x20"]* "+" [^"'"]* "'";
block unorganized nofragment df_spawncounts { unittype = unsigned short; };

//Ordered level syntax
block unorganized textual procedure_header ::= "procedure Level" ["0-9"]+ ";" ["\x0-\x20"]* "begin";
block unorganized textual fp_padding ::= ^"FP[";
block unorganized textual fp_header ::= "FP[" ["0-9"]+ "]:=" ["\x0-\x20"]* "'";
block unorganized textual fp_chars ::= [^"'"]*;
block unorganized textual fp_remainder ::= ^"end;";
block unorganized textual nofragment fp_symbol_line {
unittype = unsigned char;
enum = kroz_char_enums;
};

//Transition syntax
block unorganized textual df_filler ::= ^"DF[";
block unorganized textual df_filler2 ::= "DF[" ^"DF[";
block unorganized textual fp_filler ::= ^"procedure Level";
block unorganized textual fp_filler2 ::= "procedure Level" ^"procedure Level";

You'll never need to write memcmps and strcmps and strtoks and mids and strlens and...you get the idea. All the above unorganized blocks instantly validate and size properly if the pattern matches!

Of course, there is often a need to put fields together in particular patterns, in which you must extract individual portions of each field. You don't want to make a truly massive regular expression for the entire dataset in such a case--it gives you only one node of data. Instead, you'll want to employ organized blocks to characterize the text fields in a more list-friendly fashion:


block organized ordered_level_line {
mainbody nodelist {
block fp_padding;
block fp_header;
block fp_chars;
};
bool Termination() { return (++iterations >= max_ylines); };
};

block organized ordered_level {
void Initialization() { iterations = 0; };

mainbody nodelist {
block procedure_header;
block organized ordered_level_lines {
mainbody nodelist repeats {
block ordered_level_line;
};
};
block fp_remainder;
};
};

block organized unordered_level {
mainbody nodelist {
block df_header;
block df_chars;
choice optional { block df_transition; };
choice optional { block df_chars; };
choice optional { block df_transition; };
choice optional { block df_chars; };
};
};

In the final release of the I.F., I've made the block definitions a bit more complex, of course. This is because the above definitions only give you the text fields as an ordered list--people would find the data a heckuva lot more useful if the fields had undergone alphanumeric-to-binary conversion, had enumerations broken out, etc.

To do this, just add some Deserialize calls and you're good to go.

The output for an unordered level looks like this:


struct unordered_spawn_counts {
slow_enemy = 600,
medium_enemy = 0,
fast_enemy = 0,
breakable_block = 0,
whip = 20,
stairs = 1,
chest = 0,
slow_time = 5,
gem = 30,
blindness_potion = 0,
teleport_scroll = 5,
key = 0,
door = 0,
solid_wall = 0,
speed_time = 0,
teleport_trap = 0,
river = 0,
power_ring = 0,
forest = 0,
tree = 0,
bomb = 0,
lava = 0,
pit = 0,
staff = 0,
tunnel = 0,
freeze_time = 0,
nugget_or_artifact = 20,
quake_trap = 5,
invisible_breakable_block = 0,
invisible_solid_wall = 0,
invisible_door = 0,
enemy_stop_space = 0,
enemy_activator = 0,
enemy_zap_spell = 0,
enemy_creation_trap = 0,
enemy_generator = 0,
enemy_activator2 = 0,
moving_block = 700...

A list of descriptive spawn counts for a randomly generated level! Now that's useful. But is it an improvement? See for yourself how Scott Miller encoded them originally:


DF[21]:=
{ 1 2 3 X W L C S + I T K D # F . R Q B V = A U Z * E ; : - @ ] G ( M )}
'600 20 1 5 30 5 20 5 700 '+
{ P ! O H N [ | " 4 5 6 7 8 9 Y 0 ~ $}

Good Lord! I’m dead serious! If you can make sense of that, you let me know!

As far as parsing difficulty is concerned, I’d place Kroz level files in the “moderate” category when it comes to what you can do with regular expressions. XML would fall into the “easy” category because it’s very sound and has a well-defined syntax. METAR would fall into the “hard” category because it’s ill-defined, inconsistent, and barely even human-readable. Not that any text format would be impossible.

Since BAR is a relatively new technology, I’m all ears for interesting new challenges people have with ETL or other readabilty/portability/conversion issues. Chances are good that BAR can tear it to pieces within hours.