Robot Minefield C Port

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!

 

Part 1 | Part 2 | Part 3 | Part 4

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