Part 1 | Part 2 | Part 3 | Part 4
After typing in the BASIC version of Robot Minefield, then porting it to Assembly, I decided to round things out by doing a C version as well. The C version was the fastest to port. I learned a lot doing the assembly version, and had a blast. The assembly was the most fun, but the most frustrating to write. My assembly foo just isn't very strong at this point. Thinking in assembly still takes a while, but worse is not knowing all those pesky mnemonics!
You can find the BASIC and C version, or the Assebmly version on github.
It took me 2 hours to do the C port. That's faster then it took to type in the BASIC version! Well, honestly I don't know how long it took to type it in. Maybe the extra time I took on the BASIC version to fix a bug in the books source, and make it play a little better on the Coco made it feel longer then 2 hours.
The C version pretty much looks exactly like the assembly version, except the title.
Just for fun, let's take a look at the source from BASIC, C, and Assembly for the computers move logic. We should get a good feel for the differences between the languages. Which one looks most readable? I also include line counts of the move logic. Note that both C and assembly are compiled lanaguages so blank lines and comments are not included in the final output binary.
The table below shows the total # lines of code, and final output (binary or .BAS) file size. The C binary has about 1.5k of C libraries that get included in every C program.
Version | # Lines Code | Binary size | Time to code |
---|---|---|---|
C | 295 | 3918 | 2 hours |
Assembly | 756 | 1654 | 1 week |
BASIC | 118 | 2663 | 4 hours |
First BASIC, 23 lines. Took me a couple hours maybe to type it in, and a couple hours to fix up the code. The slowest of the bunch. I had to reduce the size of the minefield to help improve the performance.
160 REM MOVE ROBOTS
170 FOR E=1 TO 4
180 IF B(E) = -1 THEN 320 : REM THIS ROBOT IS DEAD
190 X=B(E) : Y=C(E)
200 IF B(E)<D THEN B(E)=B(E)+1
210 IF B(E)>D THEN B(E)=B(E)-1
220 IF C(E)<F THEN C(E)=C(E)+1
230 IF C(E)>F THEN C(E)=C(E)-1
240 IF B(E)<0 THEN B(E)=0
250 IF B(E)>12 THEN B(E)=12
260 IF C(E)<0 THEN C(E)=0
270 IF C(E)>12 THEN C(E)=12
280 A(X,Y)=46
290 IF A(B(E),C(E))=42 THEN TALLY=TALLY+1 : GOSUB 1030 : A(B(E),C(E))=46 : B(E) = -1 : GOTO 320 : REM ROBOT HIT MINE
300 IF A(B(E),C(E))=72 THEN A(B(E),C(E))=64 : GOSUB 400 : GOTO 900
310 A(B(E),C(E))=36
320 NEXT E
330 IF TALLY=4 THEN GOSUB 400:GOTO 970
340 RETURN
Seems simple enough. Loops through all the robots, looks at where the where the human is in the lines that look like IF B(E)<D THEN B(E)=B(E)+1 then checks if the robot landed on the human or a mine.
Next the C version, 36 lines. Took 2 hours to complete. The speed is as fast as the assembly, with one minor exception; the display code isn't quite as fast, but still totally playable, and hardly noticable.
To be fair on the line count, I am including blank lines, comments, closing braces, and method name. If you took that out, you would end up with about 22 lines. Keep in mine though, C is a compiled language. Blank lines and comments are not included in the final output binary file. Unlike BASIC you are encouraged to have as many comments and blank lines as you need to make your code clear. Blank lines break up the code nicely.
I updated the logic a little and moved the win check to a seperate routine. Same with assembly. That way the computer AI is seperate from the rest of the game.
void computerMove() {
int hoffset = humany*MINEFIELD_WIDTH+humanx;
int offset = 0;
unsigned char content = 0;
for(int i=0; i<MAX_ROBOTS; i++) {
if(robots[i].active) {
//erase from current position
offset = robots[i].y*MINEFIELD_WIDTH+robots[i].x;
minefield[offset] = EMPTY;
//figure out which way to go
if(humanx < robots[i].x)
robots[i].x--;
if(humanx > robots[i].x)
robots[i].x++;
if(humany < robots[i].y)
robots[i].y--;
if(humany > robots[i].y)
robots[i].y++;
//check what we landed on
offset = robots[i].y*MINEFIELD_WIDTH+robots[i].x;
content = minefield[offset];
if(HUMAN == content) {
humanKilledBy = ROBOT_ALIVE;
minefield[offset] = HUMAN_DEAD_FROM_ROBOT;
} else if(MINE == content) {
score++;
robots[i].active = FALSE;
minefield[offset] = ROBOT_DEAD;
} else {
minefield[offset] = ROBOT_ALIVE;
}
}
}
}
For each active robot, the logic checks to see which way the human is, and moves that way:
//figure out which way to go
if(humanx < robots[i].x)
robots[i].x--;
Then checks to see if the robot landed on the human, a mine, or the spot is empty:
//check what we landed on
offset = robots[i].y*MINEFIELD_WIDTH+robots[i].x;
content = minefield[offset];
if(HUMAN == content) {
humanKilledBy = ROBOT_ALIVE;
minefield[offset] = HUMAN_DEAD_FROM_ROBOT;
} else if(MINE == content) {
score++;
robots[i].active = FALSE;
minefield[offset] = ROBOT_DEAD;
} else {
minefield[offset] = ROBOT_ALIVE;
}
Assembly version is last, but not least. It's the logest at 92 lines of code including comments and blank lines. That's almost 3 times what it took in C. Took me about a week with interruptions.
****************
* Computer move
*
* X points to robot coord array
* U points to the minefield
* A,B,D used for looking at coordinate and what in in the minefield
****************
computermove
clrb ;start with first robot
stb robot_index
robotloop
ldx #robotxy ;robot coord array
ldb robot_index
aslb ;array is 2 bytes per coord
leax b,x ;point to location in array
ldd ,x ;grab robot x&y
cmpa #$ff ;is this robot inactive?
beq nextrobot ;nope, skip this one
std xpos ;xpos&ypos now contain robots location
std oldx ;remember old location
;Remove robot from current position in the minefield
jsr calcfieldpos ;find position in minefield
lda #empty
sta ,u ;erase robot from minefield
;check which way human is, and move towards him
;
;assumptions:
; 1 human will never be out of bounds, so we can safely move towards them no matter what
; 2 we first check if we are lined up with human, otherwise #1 is out the window
checkhorz lda xpos ;load the robots position again
cmpa humanx ;first check where human is on horz line
beq checkvert ;we are on the same line, check the vert now
bgt towest ;human is to the left
toeast
inc xpos ;move bot to the right
bra checkvert
towest
dec xpos ;move bot to the left
checkvert lda ypos
cmpa humany ;check where human is on vert line
beq donehumanchecks ;we are on the same line
bgt tonorth ;human is to north
tosouth
inc ypos ;move bot south
jmp donehumanchecks
tonorth
dec ypos ;move bot north
;check if bot has landed on a mine or the human
;a mine will deactivate the robot and it is removed from the mienfield
;a human will kill the player and end the round. game should restart
donehumanchecks ;check if we are on empty space, and if so just move to the next robot
ldd xpos ;grab the new positon and ...
std ,x ;... put new position into robot array
jsr calcfieldpos ;find new location on minefield
lda ,u ;get whats in that new position
ldb #robot ;robot
stb ,u ;replace what was there with robot
cmpa #empty ;was space empty before we put the robot there?
beq nextrobot ;yes, move onto the next robot
cmpa #mine ;did we hit a mine?
beq hitmine ;yes, terminate this robot
cmpa #human ;did we hit a human?
beq hithuman ;yes, kill human
jmp nextrobot
hitmine ;robot is no longer active and should be removed from minefield
ldd ,x ;get position of robot xy
jsr calcfieldpos ;pos in field
lda #robotdead
sta ,u ;robot shows as dead on field
ldd #$FFFF ;disable the robot
std ,x
inc kills+1 ;increase the player score
jmp nextrobot
hithuman
ldb #humanDeadRobot ;human was killed by robot character
stb ,u ;put on the minefield
lda #robot ;what killed player...
sta killedby ;..store what killed player
nextrobot
ldb robot_index ;current index
incb ;next index
stb robot_index
cmpb #robots_max ;at the end?
lbne robotloop ;nope, continue loop. B reg used in top of loop
rts
Copyright © 2025, Lee Patterson