Vectrex Aklabeth (6)

a ~13000 cycle demon!

The dungeon – Monsters

Let us first look at the monsters in the dungeon. Some of them have a huge vector count. The demon e.g. uses more than 90 vectors. I said before, that I use a special routine “draw_synced_list()”.

I again got the “base” of that routine from the vecci export option – like described in the last chapter.

Now – that is the “baisc” draw_sync_list() function – nice and small and working good – but not necessarily the fastest version.

The mentioned demon takes about ~13000 cycles to be drawn (12894 cycles to be exact). It might be that we need lots of time to draw the demon – but I’d like to reduce that a little bit nonetheless.

First the “obvious”. When you create sync lists – Vide places “sync” points each “xx” vectors, per default each 20 vectors (this can be set in vecci – but strangely enough the default “20” places sync points every 13 vectors… I think this is a bug – must look at it in the future…).

Anyways – in the generated source code of the list – I just chose to remove every second sync point and let it run on my “cranky” vectrex. If it still does not look shaky… this will be a simple reduction.

Yes – still looks ok – time taken: 11118 cycles. This is easy!

using dissi to get cycle information of “single” routines!

Next we use “our” internal knowledge of the draw_sync_list(). When it returns to “zero”, it uses a wait loop – to ensure it always reaches zero. This loop has per default some “buffer”, in case you zero from an extreme position. In this case, the demon is printed nearly in the middle of the screen, so this loop can be “nearly” non existent -> 10890 cycles, well not such a big step:-).

Ok – I will not go into details upon the next step… but next I changed the “draw_sync_list()” routine itself to be more performant. I removed subroutine calls to “moveto_d” and replaced those with “direct” moveTo macros. I took into account that due to the way the vectorlists are drawn in the dungeon, x position is always “0” (zero) and I checked the next vector pattern during the “draw” – not after the draw (something similar to the “inMove” coding).

You can inspect the resulting new draw_sync_list() in the sources – but this new function can ONLY be used with Aklabeth dungeon monsters!

Cycle count for the demon: 8267 (we saved about 4500!)

The last monster optimization I did, was to forbid the draw of multiple monsters. With the original, in a long corridor, the monsters could “line up” and were all displayed. In my version only the nearest monster is displayed.

The dungeon – Walls and features

I can’t quite recall the thought process behind my the “dungeon” optimization. But certainly it involved an inventory of all features that are possible:

  • a front wall
  • a front door
  • a front opening
  • a left/right wall
  • a left/right door
  • a left/right opening
  • a ladder
  • an opening in the floor (a pit)
  • an opening in the ceiling

These “features” are drawn within the calculated rectangle of their “slice”. Within their slice all of these objects are always drawn at the same position and in “relation” with the same size.

After a while I realized, that the Vectrex is MADE for this kind of display! With the Vectrex you do not have to do ANY calculation to draw the dungeon. All is needed is a “tile-set”, that contains every feature a slice can have. If each tile within the set is “positioned” correctly, than you can draw a complete “slice” by just displaying the apropriate tiles.

The positioning can even be done “inside” the tile vectorlist – no need to calculate positions.

The level of the slice (the depth within the current display) is than just the scale factor you need to draw (and position) everything with!
Provided each depth increase is exactly half the scale of the previous depth – than the dungeon displays itself automatically!

Ok, the perspective is not 100% the same – but man, this trick allows us to save over 20000 cycles. The above is a “depth” of 3. With this “trick” we can display every dungeon within 30000 cycles! The further away the features are, the faster they are drawn (since this is the SCALE in which it is drawn).

(ah, well, for good measure – and because I had it lying around anyways, I also used an own version of Draw_VLp() – but that was not really necessary.)

a list of all dungeon tiles

The following code displays one complete slice, the scale is set from the “level – loop”, these are all “direct draws” no calculation of anything is done anymore!

#define DRAW(list) \
dp_VIA_cntl = 0xce; \
Draw_VLp_lessGap(list); \
Reset0Ref();

void _DRAWDungeon(int Left,int Centre,int Right, int Room,int Monster, int distance)
{
	// draw from outer to inner
	if (ISDRAWOPEN(Left))     DRAW(WallSideLeftOpen);
	else  DRAW(WallLeftDiagonals);
	if (ISDRAWOPEN(Right))    DRAW(WallSideRightOpen);
	else  DRAW(WallRightDiagonals);
	
	// draw inner
	if (!ISDRAWOPEN(Centre))                // Back wall ?
	{
		if ((!ISDRAWOPEN(Left)) && (!ISDRAWOPEN(Right)))
			DRAW(WallBackFull);
		else  if (!ISDRAWOPEN(Left))
			DRAW(WallBackRightOpen);
		else  if (!ISDRAWOPEN(Right))
			DRAW(WallBackLeftOpen);
		else
			DRAW(WallBackOpen);
	}
	if (Left == DT_DOOR)		DRAW(LeftDoor);
	if (Right == DT_DOOR)		DRAW(RightDoor);
	if (Centre == DT_DOOR)		DRAW(CenterDoor);
	
	if (Room == DT_LADDERUP)	DRAW(LadderUp);
	else if (Room == DT_LADDERDN)	DRAW(LadderDown);
	else if (Room == DT_PIT)		DRAW(Pit);
	else if (Room == DT_GOLD)                // Draw the gold (as a mimic)
	{
		DRAWMonster(0,0,MN_MIMIC,distance);
		Reset0Ref();
	}
	if (Monster > 0)                    // Monster here ?
	{
		DRAWMonster(0,0,Monster,distance);
		Reset0Ref();
	}
}

And just to show the difference… here the original code:

int xLeft,xRight,yBottom,                    // Slanted drawing constants
yDiffLeft,yDiffRight;

//**********************************************************************
//
//                        Draw part of dungeon
//
//**********************************************************************

void DRAWDungeon(const RECT * const rOut,const RECT * const rIn, int Left,int Centre,int Right, int Room,int Monster, int distance)
{
	int x1,y1,x,y,y2;
	RECT r;
	
	//    HWColour(COL_WALL);                        // Start on the 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);
	}
	
	if (!ISDRAWOPEN(Centre))                // Back wall ?
	{
		HWLine(rIn->left,rIn->top,rIn->right,rIn->top);
		HWLine(rIn->left,rIn->bottom,rIn->right,rIn->bottom);
		
		if (!ISDRAWOPEN(Left))                // Corner if left,right closed
		HWLine(rIn->left,rIn->top,rIn->left,rIn->bottom);
		if (!ISDRAWOPEN(Right))
		HWLine(rIn->right,rIn->top,rIn->right,rIn->bottom);
	}
	
	_DRAWSetRange(rOut->left,rIn->left,        // Set up for left side
	rOut->bottom,
	rOut->bottom-rOut->top,
	rIn->bottom-rIn->top);
	_DRAWWall(Left);
	
	_DRAWSetRange(rIn->right,rOut->right,    // Set up for right side
	rIn->bottom,
	rIn->bottom-rIn->top,
	rOut->bottom-rOut->top);
	_DRAWWall(Right);                        // Set up for centre
	
	_DRAWSetRange(rIn->left,rIn->right,
	rIn->bottom,
	rIn->bottom-rIn->top,
	rIn->bottom-rIn->top);
	_DRAWWall(Centre);
	
	if (Room == DT_LADDERUP)
	{
		DRAWSetRect(&r,rOut->left,rOut->top,rOut->right,rIn->top);
		_DRAWPit(&r,1);
	}
	if (Room == DT_LADDERDN || Room == DT_PIT)
	{
		DRAWSetRect(&r,rOut->left,rIn->bottom,rOut->right,rOut->bottom);
		_DRAWPit(&r,-1);
	}
	DRAWSetRect(&r,  (rIn->left+rOut->left)/2,     (rIn->top+rOut->top)/2,     (rIn->right+rOut->right)/2, (rIn->bottom+rOut->bottom)/2);                         // Get the object area
	
	
	if (Room == DT_LADDERUP ||                // Ladder here ?
	Room == DT_LADDERDN)
	{
		y1 = r.top;
		y2 = r.bottom;
		if (distance == 1)
		x = (r.right-r.left)  / 4;
		else
		x = (r.right-r.left)  / 4 + (r.right-r.left)  / 8;
		HWLine(r.left+x,y1,r.left+x,y2);
		HWLine(r.right-x,y1,r.right-x,y2);
		
		x1 = (y1 - y2) / 4;
		y = y2 + x1/2;
		while (y < y1)
		{
			HWLine(r.left+x,y,r.right-x,y);
			y = y + x1;
		}
	}
	
	if (Monster > 0)                    // Monster here ?
	{
		DRAWMonster((r.left+r.right)/2,r.bottom,Monster,distance);
	}
	
	if (Room == DT_GOLD)                // Draw the gold (as a mimic)
	{
		DRAWMonster((r.left+r.right)/2,r.bottom,MN_MIMIC,distance);
	}
}

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

static void _DRAWConvert(int *px,int *py)
{
	long x,y,yd;                            // Longs for overflow in 16 bit
	x = (((long)xRight)-((long)xLeft));                        // Calculate width
	x = x * (*px) / 100 + xLeft;            // Work out horiz value
	yd = (((long)yDiffRight)-((long)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 a rectangle
//
// **********************************************************************

static void _DRAWRect(int x1,int y1,int x2,int y2)
{
	HWLine(x1,y1,x2,y1);HWLine(x1,y1,x1,y2);
	HWLine(x2,y2,x2,y1);HWLine(x2,y2,x1,y2);
}

// **********************************************************************
//
//                    Draw the pits/ladder hole
//
// **********************************************************************

static void _DRAWPit(RECT *r,int Dir)
{
	int x1,x2,y1,y2;
	//    HWColour(COL_HOLE);
	y1 = (r->top-r->bottom)/5;
	r->bottom += y1;
	r->top -= y1;
	
	x1 = (r->right-r->left)/5;
	r->left += x1;
	r->right -= x1;
	
	x2 = 0;
	x1 = x1 / 2;
	
	if (Dir > 0)
	{
		y1 = x1;
		x1 = x2;
		x2 = y1;
	}
	
	HWLine(r->left+x1,r->top,r->right-x1,r->top);
	HWLine(r->left+x1,r->top,r->left+x2,r->bottom);
	HWLine(r->left+x2,r->bottom,r->right-x2,r->bottom);
	HWLine(r->right-x1,r->top,r->right-x2,r->bottom);
}


// **********************************************************************
//
//                    Set the oblique drawing routine
//
// **********************************************************************

static void _DRAWSetRange(int x1,int x2,int y,int yd1,int yd2)
{
	xLeft = x1;xRight = x2;                    // Set x ranges
	yBottom = y;                            // Set lower left y value
	yDiffLeft = yd1;yDiffRight = yd2;        // Set difference for either end
}

// **********************************************************************
//
//                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);
	}
}

to be continued (tying it all together – and THE END)

Tagged on: , ,

One thought on “Vectrex Aklabeth (6)

Leave a Reply to Peer Cancel 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.