Tuesday, April 28, 2009

Quivira - Planning 2

This afternoon I'm using a hex editor to analyze the old Commodore 64 "Map Disk" for 7 Cities of Gold, and here's the physical data, starting from file position 00000000:

"Empty" cells are initially marked with an 01 decimal.

Every 0x100 bytes there's a decimal 10 marker.

Starting in cell $01500, and every 0x100 bytes after, there's an 0x4b.

Finally, we get to the disk header block, starting at 0x16500 with:

(hex) 12 01 41 00,
(hex) 15 ff ff 1f, repeated 17 times,
(hex) 11 fc ff 07,
(hex) 13 ff ff 07, repeated 6 times,
(hex) 12 ff ff 03, repeated 6 times,
(hex) 11 ff ff 01, repeated 5 times.

After that comes the disk's title label, in this case, simply "MAP" in PETSCII:

$16590: 4d 41 50, followed by 0xa0 15 times.

Then there's the disk's two identifiers, separated by an a0:

$165a2: 45 41 a0 32 41, then four more a0's.

Then it's zeros until $16601, which is a big fat FF, then another pile of zeros.

Starting at $16700, we resume the 4b every 0x100 bytes, with 01s in between.

It looks like the actual data begins at $17300, with what seems to be blocks of data 0x100 bytes long. In some of the data blocks, there's a two byte header, then another two byte header at 0x080 bytes (I think).

$17300 to $173ff is very sparse.
$17400 to $174ff is a bit less so.
$17500 to $17599 has even more data.
$17600 to $176ff has more.
$17700 to $177ff is quite busy.

$17800 to $178ff is also busy, but has an unusual pattern not found with the others. Starting at 0x66, descending to 0x10, then climbing up to the 0x90s and 0xa0s at the end of the block. Almost a random walk, but with a deterministic bent, whereas the others are relatively random. Climate data? Climate and altitude?

Then $17900 to $179ff, every value except 5 are between 0xf0 and 0xf8. The exceptions are 0x0a, 0x03, 0x03, and 0x0a, scattered among the 0xf- data. These look like "flags" tied to areas of the map, or something.

$17a00 to $17aff are nearly all 0x3a through 0x3f, with 3 exceptions (0xff, 0x00, and 0x00). Interestingly, the two 0x00's correspond exactly to two of the relative positions in the previous data block. I suspect these data may represent native villages, whether they've been visited, whether there's a mission or fort there, etc. I am probably wrong.

$17b00 to $17bff is random-looking data again.

$17c00 to $17cff is somewhat random, with a lot of zeros surrounding strings of numbers that appear related.

$17d00 to $17dff is mostly zeros, with occasional random-appearing numbers. $17dc0 to $17dff are all zeros. I suspect this is not a single block of data.

$17e00 to $17eff is similar to the previous block, except toward the end: from $17e90 to $17ebf there are nothing but zeros, and from $17ec0 to $17eff it's 0xff's.

$17f00 goes back to our "no data" format: 0x4b every 0x100 bytes, with 0x01 filler.


And then we get some funny data starting at $1a400 and lasting until $1a7ff. Lots of repeating values, and maybe some headers, but I'm not sure.

Then from $1a800 to $1bbff we're back to "no data".

Then there's some data structures from $1bc00 to $1bdff.

Then a block of data from $1be00 to $1beff.

Then, something interesting -- a repeated pattern from $1bf00 to $1bfff: a block that appears to be identical to $17800 to $178ff. A copy of the map? What part of the map? There will have to be a copy when something is changed on the player map, so you can reset and start over. Maybe this is native village data, but then why would the numbers decrease, and then increase again?

$1c000 is regular, random-walk-like numbers, starting at 03 and creeping down to 00, then bouncing back up to 04 halfway through... hey wait, that halfway point has a two-byte marker. Many of the ones that I think are "map" related must have this.

$1c100 starts out with zeros, then a semi-repeating pattern of ff ff's interspersed with zeros, with 7s and 8s as boundaries... in other words, multibyte bitstrings of all 1s followed by all 0s, most of the time. This peters out by $1c1a0, turning into mostly zeros with an occasional number.

From $1c1d0 to $1c4ff there's just zeros, except for a 0x01 and an 0x11 at the tail end of the block.

$1c400 to $1c46f are zeros, then there's a string of 0x11s and an 0x21, then from $1c480 on it's zeros again.


But finally I think I can see the actual map -- the terrain. It begins around $1ca40 (probably the ocean is zeros, which could start at any point before here) and billows along until around $1ceff. Then again from $1cf00 (starting with a short header and a pile of zeros) with actual land appearing around $1d200, and again (I think) at $1d700
to $1dfff, again from $1e000 to $1e7ff (?), $1e800 to $1efff, $1f000 to $1f7ff, $1f800 to $1ffff, $20000 to $207ff, $20800 to $20fff, $21000 to $217ff, $21800 to $21fff... I think.

The data consists of 0x01, 0x11, 0x12, 0x1c, 0x2b, 0xb1, 0xbc, 0xcb, 0xbd, 0xdc, 0xcd, 0xbb, 0xcc, 0xdd, 0xdb. I believe these bit patterns correspond to a particular set of tiles.

The blocks here appear to be in 0x80 byte blocks. More interestingly, every 8 bytes appears to represent one layer of a block of map data that's probably 0x80 bytes in size.

The map ends at $289ff. After that, it's just 0x4b every 0x100 bytes, padded in between with 01s.

Analysis comes later, or maybe never.

Wednesday, April 22, 2009

def() and a module idea

Since I don't use Ruby for my job, this blog is in danger of dying a horrible but quick death.

I like def

...however, there is one thing I'd like to put on the record about Ruby. I like "def" to define methods, functions, subroutines, whatever you want to call them. Def has more currency than Perl's "sub". If Perl6 allowed "def" as a synonym for "sub" I'd be pleased.

Sound like a meaningless detail? The devil, I say, is in the details.

Something I don't think I like, on the other hand, is using "end" to close definitions (class...end. def...end.). True, it's obvious, and it's only one more keystroke than [shift+close bracket]. But, I seem to prefer keywords having more utility than as a block delimiter.

But that IS a reasonably trivial detail, one I can overlook.


A module idea

There's a science-fiction game called Traveller that's been in print for thirty years, and one of the key elements in the game is a world abstract, called the Universal World Profile, or UWP for short. It is self-contained with a handful of data elements, some fully derived data, some externally imposed data, and some computations you can perform. In short, perfect for a module. I think a good learning exercise is to port my Perl UWP module into Ruby.

Spacewars - planning

Today, I'll shift to musing about Spacewars. Back when I did this in Java, a co-worker and I hit on a Gravity Model to manage Newtonian variations in the game. Plug in your custom gravity model based on your preferred settings and all that.


Design Thought

I'm taking design a step further and implementing a Chain of Command pattern on top of this pluggability. There are discrete steps in these sorts of games, and all of them operate directly on the units in the playing field -- essentially a Blackboard model, except in order to prevent chaos I'll sequence the blackboard elements into a Chain of Command. So the game loop is managed by GameStep objects, which operate independently of each other:

- Gravity Model
- Input Model
- Position Tracker
- Impact Model

The interface may simply be:

public function accept(state:GameState):void {...}
public function name():String {...} // for game settings chooser...


The control loop therefore becomes very lightweight.

I could implement these as Template Methods, but I like the "pluggable" modularity of Chain of Command. It's one of the side-effects of Object-Oriented programming that I enjoy (OO tends to still be a bit overrated at times).

It's also easily extensible: if my game becomes cool and I want to play my friend Dan Terrill over the internet, I can later code up a NetworkInputModel that lets us play. If I want to create a Lunar Lander variant, I code up a SurfaceGravityModel that exerts a "downward" pull on all objects in the game, and a variant ImpactModel that detects a safe vs. unsafe landing. Plus it could retain the combat elements... combat lunar lander... cool.


Implementation Thought

One of the few things I know about Flex is that the Canvas (and Panel) objects are positionable graphics objects which "own" things which are displayed. That's all fine and well, but I found a side effect that I hope I can exploit: point rotations of objects on a Canvas are rotated relative to the origin of the Canvas.

Here's why that's important: If I create a separate Canvas for each object in play, centered on its origin, then I can move and rotate the Canvas very easily.

Normally, when you rotate an object, you translate its points relative to the origin, then apply the transformation (rotation), then re-translate back to their actual position, then move the object to that new position. It's lots of work, and also a solved problem... for decades. If I can simply set the rotation angle of an owning Canvas, however, all that work is done for me. Simpler application code. Less boring work. Win-win.

Tuesday, April 21, 2009

Quivira - planning

First some data structure musings.

In Seven Cities of Gold (the original game by EA), the map was an array of bytes, where the bit patterns represented terrain, flora, and settlements.

My adaptation is still very data-light, but it's not that light. Instead of using bitfields to represent the Map, I'm using a Set of maps, where each map is an Array of Strings. Thus I can store the maps in text format, and can inspect and edit them with a text editor...

Monday, April 20, 2009

Retro coding

Last week I had an attack of nostalgia for retro gaming. If you're Generation X, you may remember the 8-bit games that came out between 1982 and 1986 or so, especially from Electronic Arts. And you'll also remember when Space Invaders, Asteroids, and similar came out.

I have two abiding games of interest: Seven Cities of Gold by Electronic Arts (http://en.wikipedia.org/wiki/The_Seven_Cities_of_Gold_(video_game)), and Spacewars (http://en.wikipedia.org/wiki/Space_Wars).

I tried to build Spacewars in Java ten years ago. Now I want to write it for Flex, which I think will be an easier task by the way. Ships, projectiles, and the gravity source are the interacting objects in the game. I use the term "gravity source" because I believe that the Spacewars codebase is nearly identical to that of the classic "lunar lander" game.

I've tried to design and write "retro"-styled games that use ideas from Seven Cities of Gold for a long time. Once again, Flex has rekindled my imagination, and I'd like to create an interactive simulation, with retro graphics reminiscent of the original Seven Cities game. The main feature of this game is the Map.

In both cases, the Sprite class will feature prominently.

Monday, April 6, 2009

Metric Clock

For those who like the bizarre for the sake of the bizarre, a clock which divides the day into 100 hours of 100 'moments' (each is about 9 seconds long).

Check it out here: http://eaglestone.pocketempires.com/bin/metrictime/MetricClock.html

I've added some spaces in the MXML to make it displayable...

< ?xml version="1.0" encoding="utf-8"?>
< mx:Application 
 xmlns:mx="http://www.adobe.com/2006/mxml" 
 layout="absolute"
 creationComplete="creationCompleteHandler()"
 >
 < mx:Script>
  < ![CDATA[
           import flash.display.Sprite;
           import flash.utils.Timer;

           private var clock:Sprite = new Sprite();
           private var hour:Sprite = new Sprite();
           private var sec:Sprite = new Sprite();

           public function creationCompleteHandler():void
           {
             clock.graphics.lineStyle(2);
              clock.graphics.drawCircle(0,0,100);
              
              // draw tick marks on clock face
              var outerRadius:int = 99;
              
              for ( var m:int=0; m<100 data-blogger-escaped-br="" data-blogger-escaped-m="">              {
                var innerRadius:int = 96;
                clock.graphics.lineStyle(1, 0x000000, 100);
                
                if ( m % 10 == 5 )
                {
                 innerRadius = 94;
                    clock.graphics.lineStyle(2, 0x000000, 100);
                 }
                 
                var theta:Number = m * 3.6 * Math.PI / 180;
                
                 clock.graphics.moveTo(Math.cos( theta ) * outerRadius, 
                                       Math.sin( theta ) * outerRadius);
                 clock.graphics.lineTo(Math.cos( theta ) * innerRadius, 
                                       Math.sin( theta ) * innerRadius);
              }
              
              myCanvas.move(150,150);
              myCanvas.rawChildren.addChild(clock);
              myCanvas.rawChildren.addChild(hour);
              myCanvas.rawChildren.addChild(sec);

              // draw 'hour' hand
              hour.graphics.lineStyle(2, 0x000000, 100);
              hour.graphics.moveTo(0, 0);
              hour.graphics.lineTo(0, -60);

              // draw 'second' hand
              sec.graphics.lineStyle(0, 0x0ff00f, 100);
              sec.graphics.moveTo(0, 0);
              sec.graphics.lineTo(0, -90);
 
              var timer:Timer = new Timer(400);
              timer.addEventListener(TimerEvent.TIMER, tick);
              timer.start();
           }

           public function tick(evt:TimerEvent):void
           {
              var now:Date    = new Date();
              var time:Number = now.hours * 3600 + now.minutes * 60 + now.seconds + now.milliseconds / 1000;
              var metric:int  = time * 100/864;

              var hrs:Number  = int(metric / 100);
              var secs:Number = metric % 100;

              hour.rotation = 3.6 * hrs;
              sec.rotation  = 3.6 * secs;

              when.text = "";
              if ( hrs  < 10 ) when.text = "0";
              when.text += hrs + ":";
              if ( secs < 10 ) when.text += "0";
              when.text += int(secs);
           }
  ]]>
 < /mx:Script>
 < mx:Canvas id="myCanvas" x="0" y="0" width="200" height="200">
 < /mx:Canvas>  
 < mx:Text id="when" x="131" y="264" text="" width="52"/>
< /mx:Application>