Vectrex Aklabeth (2)

World map continued

Extracting all “Map” symbols and putting valid Vectrex code around it – results in another very “easy” looking function:
(the calculated scale is due to using a BLOWUP of 4 – and a slight adjustment 🙂 )

void DRAWTile(RECT *r,unsigned char Obj)
{
	int x1 = (int) (r->left-128);             /* Extract values */
	int y1 = (int) (r->top-128);
	VIA_t1_cnt_lo = 0x60; // scale
	
	Reset0Int();
	Moveto_d(y1,x1);
	VIA_t1_cnt_lo = 0x60/4 +3; // scale
	switch(Obj)                               /* Decide on the object */
	{
		case WT_MOUNTAIN:                    /* Mountain the cracked effect */
		Draw_VLp((void* const)MountainList);
		break;
		case WT_PLAYER:
		Draw_VLp((void* const)PlayerList);
		break;
		case WT_TOWN:                        // town is 5 boxes
		Draw_VLp((void* const)TownList);
		break;
		case WT_TREE:                        // Tree is just a box
		Draw_VLp((void* const)TreeList);
		break;
		case WT_DUNGEON:                     // Dungeon is a cross
		Draw_VLp((void* const)DungeonList);
		break;
		case WT_BRITISH:                     // British castle
		Draw_VLp((void* const)BritishList);
		break;
		
		default:
		break;
	}
}

So – by now we can:

  • start the game
  • build a character
  • buy some equipment
  • display a world map

Next thing will be:

  • move around
  • print status information

As “everyone” knows, printing text is “the worst thing” one can do on a Vectrex. Nonetheless we do HAVE to print something on the screen, at least two “lines”:

  1. The current player status (Gold, HitPoints, and Food)
  2. What is happening?

The “easy” way:

void HWStatus(unsigned long Food, unsigned long HP, unsigned long Gold)
{
	Reset0Int();
	_fs("Food %", ltoa(Food));
	Print_Str_d(-128+5, -128, stringBuffer40);
	Reset0Int();
	_fs("HP %", ltoa(HP));
	Print_Str_d(-128+5, -20, stringBuffer40);
	Reset0Int();
	_fs("Gold %", ltoa(Gold));
	Print_Str_d(-128+5, 80, stringBuffer40);

    	Print_Str_d(-128+5+6, -128, (void* const) messageBuffer);
        CLS;
}
Takes ~75000 cycles to display!

This “print”, when there is a worthwhile message to display, takes about 12000 cycles.

More than one third of all cycles we have available for the complete game display.

And this is already using an optimized print_Str_d, which uses only a 5 pixel high font. But for now… this must suffice – more optimization will be started, once the whole game is up and running.

Moving around… is pretty straight forward again.

We can reuse code done by Paul (or Richard) and just substitute the Vectrex’ input methods:

(The print_timed() function fills the message buffer that is displayed on the screen, later that was optimized some more.)

static void _MAINCommand(WORLDMAP *w,PLAYER *p,DUNGEONMAP *d)
{
	if (joystick_1_leftChange())
	{
	 if (p->Level == 0)print_timed("Go West."); 
         else  print_timed("Turn Left.");
	 MOVEMove('W',w,p,d,-1,0);
	 return;
	}
	if (joystick_1_rightChange())
	{
	 if (p->Level == 0)print_timed("Go East."); 
         else  print_timed("Turn Right.");
	 MOVEMove('E',w,p,d,1,0);
	 return;
	}
	if (joystick_1_upChange())
	{
 	 if (p->Level == 0)print_timed("Go North."); 
         else  print_timed("Move.");
	 MOVEMove('N',w,p,d,0,-1);
	 return;
	}
	if (joystick_1_downChange())
	{
	 if (p->Level == 0)print_timed("Go South."); 
         else  print_timed("Turn Around.");
	 MOVEMove('S',w,p,d,0,1);
	 return;
	}
}

Being a little afraid of the dungeon… I next did the “easy” things like:

  • entering town, and buying stuff
  • entering the castle and receiving quests

I will not go further into that – since it is really more of the same. Parsing input, printing text on screen and doing while (1) loops to display a Vectrex-round.

The dungeon

It took a bit of experimenting and reading till I understood the drawing system of the dungeon.

In general the dungeon is drawn in slices (maximum depth 10):

Each color represents a dungeon slice. The bounds are defined by an outer rectangle (level 1: pink) and an inner rectangle (level 1: green).

Features are always drawn for one slice (like doors, ladders, monsters, traps etc). The original code draws all of these things dynamically and calculated with single “lines”.

So basically the complete dungeon is drawn in these slices (Loop variable “level”):

  • DrawCalcRect() – creates the shown “outer” rectangle
  • x, y are either 0, 1 or -1 and determine the direction you look at – so increasing the current position with those “moves”, you go one step further in your view direction (for the next level to draw)
  • front, left, right are fairly self expanatory – these are the three tiles in front of the current “view” position
void DDRAWDraw(PLAYER *p,DUNGEONMAP *d)
{
	int x,y;
	unsigned int  Level = 0;
	int Monster,Front,Left,Right;
	_DDRAWCalcRect(&rOut,0);
	Pos = p->Dungeon;                        // Get position
	x= p->DungDir.x;
	y= p->DungDir.y;
	do
	{
		Level++;                            // Next level
		_DDRAWCalcRect(&rIn,Level);
		Next.x = Pos.x +x;        // Next position
		Next.y = Pos.y +y;
		Front = d->Map[Next.x][Next.y];        // What's in front ?
		if (y == 0)
		{
			Left = d->Map[Pos.x][Pos.y-x];
			Right = d->Map[Pos.x][Pos.y+x];
		}
		else
		{
			Left = d->Map[Pos.x+y][Pos.y];
			Right = d->Map[Pos.x-y][Pos.y];
		}
		Monster = DDRAWFindMonster(d,&Pos);    // Find ID of monster here
		if (Monster >= 0)                    // Find Type if Found
		{
			Monster = d->Monster[Monster].Type;
		}
		DRAWDungeon(&rOut,&rIn, Left,Front,Right, d->Map[Pos.x][Pos.y],Monster,Level);               // Draw the dungeon
		Pos = Next;                            // Next position down
		rOut = rIn;                            // Last in is new out
	}
	while (Level < MAX_VIEW_DEPTH && ISDRAWOPEN(Front));
}

Within the DrawDungeon() – the “lines” are drawn – as an example the left and right walls:

	if (ISDRAWOPEN(Left))                    // Do we draw the left edge
	{
		HWLine(rOut->left,rIn->top,rIn->left,rIn->top);
		HWLine(rOut->left,rIn->bottom,rIn->left,rIn->bottom);
		HWLine(rOut->left,rOut->top,rOut->left,rOut->bottom);
	}
	else                                    // If closed, draw left diags
	{
		HWLine(rOut->left,rOut->top,rIn->left,rIn->top);
		HWLine(rOut->left,rOut->bottom,rIn->left,rIn->bottom);
	}
	
	if (ISDRAWOPEN(Right))                    // Do we draw the right edge
	{
		HWLine(rOut->right,rIn->top,rIn->right,rIn->top);
		HWLine(rOut->right,rIn->bottom,rIn->right,rIn->bottom);
		HWLine(rOut->right,rOut->top,rOut->right,rOut->bottom);
	}
	else                                    // If closed draw right diags
	{
		HWLine(rOut->right,rOut->top,rIn->right,rIn->top);
		HWLine(rOut->right,rOut->bottom,rIn->right,rIn->bottom);
	}

Anyways – after making sure the math is save (sign wise and double, and long wise) and building a “dummy” HWLine() function… a dungeon is drawn:

The first simple implementation of HWLine():

(after doing a 0,0 correction
– each coordinate -128)

#define HWLine(x1,y1,x2,y2) do {\
	Reset0Int(); \
	Moveto_d((y1), (x1)); \
	Draw_Line_d((y2)-(y1), (x2)-(x1));} while (0) 

Some further math functions had to be removed:

  • pow()
  • sqrt()
  • floor()
  • rand() function had to be “secured”

It always helped much to UNDERSTAND what was going on. Example:

    	Dist = pow(m->Loc.x-p->Dungeon.x,2);	/* Calculate Distance */
	Dist = Dist + pow(m->Loc.y-p->Dungeon.y,2);
	Dist = sqrt(Dist);
	if (Dist < 1.3)					/* If within range */
			Attacked = _MONSTAttack(m,p);

The above code checks, whether a monster can attack, it can only attack if it is adjacent to the player and not diagonal. This can be reduced to:

         signed int d1 = ABS(m->Loc.x-p->Dungeon.x);
         signed int d2 = ABS(m->Loc.y-p->Dungeon.y);

         Dist = d1+d2;     // must be 90° to player and only one position away!
	if (Dist == 1) 
              Attacked = _MONSTAttack(m,p); // If within range

Since the above version is basically Pythagoras, and checks if the hypotenuse is smaller than 2 (sqrt(2) < 1.3) – it is the same as checking that “no distance” is greater than abs(1) AND that “one distance” is zero.

And thus I got rid of two functions, that drove the “C” code crazy! POW() and SQRT().

Doors

Doors are actually drawn “more” complicated to place them in the middle and get the diagonals right:

/************************************************************************/
/*                                                                        */
/*            Convert coordinates from oblique to logical                    */
/*                                                                        */
/************************************************************************/

static void _DRAWConvert(int *px,int *py)
{
	long x,y,yd;                            /* Longs for overflow in 16 bit */
	x = (xRight-xLeft);                    /* Calculate width */
	x = x * (*px) / 100 + xLeft;            /* Work out horiz value */
	yd = (yDiffRight-yDiffLeft);            /* Work out height of vert for x */
	yd = yd * (*px) / 100;
	y = yBottom + yd/2 - (yd+yDiffLeft) * (*py) / 100; /* Half of the distance  + Scaled total size */
	*px = (int)x;                            /* Write back, casting to int */
	*py = (int)y;
}

/************************************************************************/
/*                                                                        */
/*                Draw wall object using current setting                    */
/*                                                                        */
/************************************************************************/

static void _DRAWWall(int n)
{
	int x1,y1,x2,y2;
	if (n == DT_DOOR)
	{
		HWColour(COL_DOOR);
		x1 = 35;y1 = 0;x2 = 35;y2 = 60;
		_DRAWConvert(&x1,&y1);
		_DRAWConvert(&x2,&y2);
		HWLine(x1,y1,x2,y2);
		x1 = 65;y1 = 60;_DRAWConvert(&x1,&y1);
		HWLine(x1,y1,x2,y2);
		x2 = 65;y2 = 0;_DRAWConvert(&x2,&y2);
		HWLine(x1,y1,x2,y2);
	}
}

But since the math worked ok and I am not optimizing yet… I left that alone as well.
(Actually I had to debug that for quite some time, since here a sign/unsign difference sneaked in using the 6809 gcc).

The above dungeon screen as shown refreshed in about 44000 cycles (12000 from the text output – but nonetheless over 30000 cycles for the few dungeon walls (depth levels: 3)).

Monsters

In principle monsters are drawn like everything else.
But not in principle – monsters are different:

A monster is drawn with a “plot” function – seemingly a specialty of Apple II BASIC.
Plot is a function that draws a series of lines using dynamic parameters. e.g.:

static void _DRAWSkeleton(PARAMS)
{
_HPlot(y-23/d,x,y-15/d,x,y-15/d,x-15/d,y-8/d,x-30/d,y+8/d,x-30/d,y+15/d,x-15/d,y+15/d,x,y+23/d,x,END,END);
_HPlot(y,x-26/d,y,x-65/d,END,END);
_HPlot(y-2/d+.5,x-38/d,y+2/d+.5,x-38/d,END,END);
_HPlot(y-3/d+.5,x-45/d,y+3/d+.5,x-45/d,END,END);
_HPlot(y-5/d+.5,x-53/d,y+5/d+.5,x-53/d,END,END);
_HPlot(y-23/d,x-56/d,y-30/d,x-53/d,y-23/d,x-45/d,y-23/d,x-53/d,y-8/d,x-38/d,END,END);
_HPlot(y-15/d,x-45/d,y-8/d,x-60/d,y+8/d,x-60/d,y+15/d,x-45/d,END,END);
_HPlot(y+15/d,x-42/d,y+15/d,x-57/d,END,END);
_HPlot(y+12/d,x-45/d,y+20/d,x-45/d,END,END);
_HPlot(y,x-75/d,y-5/d+.5,x-80/d,y-8/d,x-75/d,y-5/d+.5,x-65/d,y+5/d+.5,x-65/d,y+5/d+.5,x-68/d,y-5/d+.5,x-68/d,y-5/d+.5,x-65/d,END,END);
_HPlot(y+5/d+.5,x-65/d,y+8/d,x-75/d,y+5/d+.5,x-80/d,y-5/d+.5,x-80/d,END,END);
_HPlot(y-5/d+.5,x-72/d,END,END);
_HPlot(y+5/d+.5,x-72/d,END,END);
}

Regardless of mighty Vide – this I could not convert directly with Vecci. This needed some manual labour:

  • all signs must be “spaced”
  • all fractions removed
  • multiple plot entries must be seperated and absolut coordinates (the last plotted coordinates) added
  • coordinates without offsets replaced with “0” (zero)

These are only a couple of replaces, easily done in a minute in any editor. For ten monsters perhaps 15-20 minutes “work”, easier than to program a parser, especially since we probably will never need it again.

This resulted in a “weird” list like:

100
_HPlot(y -23/d , 0, y -15/d , 0,
y -15/d , 0, y -15/d , x -15/d ,
y -15/d , x -15/d , y -8/d , x -30/d ,
y -8/d , x -30/d , y +8/d , x -30/d ,
y +8/d , x -30/d , y +15/d , x -15/d ,
y +15/d , x -15/d , y +15/d , 0,
y +15/d , 0, y +23/d ,0
_HPlot(0 , x -26/d , 0 , x -65/d , END , END);
_HPlot(y -2/d+ , x -38/d , y +2/d+ , x -38/d , END , END);
_HPlot(y -3/d+ , x -45/d , y +3/d+ , x -45/d , END , END);
_HPlot(y -5/d+ , x -53/d , y +5/d+ , x -53/d , END , END);
_HPlot(y -23/d , x -56/d , y -30/d , x -53/d ,
y -30/d , x -53/d , y -23/d , x -45/d
y -23/d , x -45/d , y -23/d , x -53/d ,
y -23/d , x -53/d , y -8/d , x -38/d , END , END);
_HPlot(y -15/d , x -45/d , y -8/d , x -60/d ,
y -8/d , x -60/d , y +8/d , x -60/d ,
y +8/d , x -60/d , y +15/d , x -45/d , END , END);
_HPlot(y +15/d , x -42/d , y +15/d , x -57/d , END , END);
_HPlot(y +12/d , x -45/d , y +20/d , x -45/d , END , END);
_HPlot(0 , x -75/d , y -5/d+ , x -80/d ,
y -5/d+ , x -80/d , y -8/d , x -75/d ,
y -8/d , x -75/d , y -5/d+ , x -65/d ,
y -5/d+ , x -65/d , y +5/d+ , x -65/d ,
y +5/d+ , x -65/d , y +5/d+ , x -68/d ,
y +5/d+ , x -68/d ,y -5/d+ , x -68/d ,
y -5/d+ , x -68/d , y -5/d+ , x -65/d , END , END);
_HPlot(y +5/d+ , x -65/d , y +8/d , x -75/d ,
y +8/d , x -75/d , y +5/d+ , x -80/d ,
y +5/d+ , x -80/d , y -5/d+ , x -80/d , END , END);
_HPlot(y -5/d+ , x -72/d , END , END);
_HPlot(y +5/d+ , x -72/d , END , END);
}

But Vecci can parse this!

The “sprites” must be rotated though…

Here a view of all monsters:

As you can see – some of the monsters have an exceptionly high vector count. The demon is drawn with over 90 vectors.

Every vectrex struggles with this many vectors drawn in one go – so I decided to forgo the “original” list variants of the BIOS and use a list type I “invented” – a synced list.

Synced lists have “synchronization” points during the draw, where the vectorbeam resets to zero and than moves to the last known location withing the “sprite” and than continues. The objects usually are drawn quite a lot cleaner and less shaky – but a little bit slower. But this was a sacrifice I had to make at this stage.

To be continued…

Tagged on:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.