Programming Newbrain with Z88DK in C

This is a demo i did using the Z88DK to program NB in C.

Cmd line to compile this : zcc +newbrain -O3 -lndos -zorg=20000 -create-app test1.c

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <newbrain.h>
#include <stdbool.h>

// External graphics
extern char whitepiece[];
extern char blackpiece[];
extern char pacmanleft[];
extern char pacmanright[];
extern char tempspr[];


long heap; // For dynamic memory allocation

void initDynMem() {
    mallinit();
    sbrk(28000, 3000); // Allocate memory at 28000 size 1000 bytes used for malloc contains the graphics too
}

// NEWBRAIN SPECIFIC FUNCTIONS

// Opens some devices needed to operate
void setup() {
    nb_close(5); // Close stream 5 if open
    nb_open(INP, 5, 5, 5, ""); // Open device 5 for input reads a char from keyboard WAITS
    nb_close(6); // Close stream 6 if open
    nb_open(INP, 6, 6, 6, ""); // Open device 6 for input reads a char from keyboard RETURN IMMEDIATELY
}

void cleanExit() {
    nb_close(5);
    nb_close(6);
}

// Sets cursor at x, y position on screen
void scrPos(char x, char y) {
    printf("%c%c%c", 22, x, y);
}

// Clears Text Screen
void cls() {
    printf("%c", 31);
}

//waits for a char
char nbGetCharW() {
    return nb_getc(5);
}

//returns immediately char or not
char nbGetChar() {
    return nb_getc(6);
}

unsigned int textlinewidth;
unsigned int graphlinewidth;

//opens a text screen 80 or 40 cols with or without vfd
void nbSetScreen(int lines, bool is80cols, bool withvfd) {
    char params[8];
    int dv = withvfd ? 4 : 0;

    if (is80cols) {
        sprintf(params, "l%d", lines);
        textlinewidth = 128;
        graphlinewidth = 80;
    } else {
        sprintf(params, "%d", lines);
        textlinewidth = 64;
        graphlinewidth = 40;
    }

    nb_close(0);
    nb_open(OUTP, 0, dv, 0, params);
}

unsigned int tvGraphLines;
unsigned int tvGraphBufferReal;
unsigned char* ram = 0; // Pointer to ram at Ram Address 0

void setGraphParams() {
    const unsigned int tvramaddr = 0x5c;
    unsigned int tvram = ram[tvramaddr] + (ram[tvramaddr+1] << 8);
    unsigned char tvoffs = ram[tvram];
    unsigned int tvbuffer = tvram + tvoffs + 5;
    unsigned int tvtxtlinebufferlen = ram[tvram + 4] * textlinewidth;
    unsigned int tvgraphbuffer = tvbuffer + tvtxtlinebufferlen;
    tvGraphBufferReal = tvgraphbuffer + 10 * graphlinewidth;
}

//opens a graphic screen 640 or 320 px wide wide or narrow
void nbSetGraphScreen(int grlines, bool is80cols, bool withvfd, bool isnarrow) {
    int neededtextlines = 200; // Should be calculated
    char params[8];

    nbSetScreen(neededtextlines, is80cols, withvfd);

    nb_close(11);
    sprintf(params, isnarrow ? "n%d" : "%d", grlines);
    nb_open(OUTP, 11, 11, 11, params);

    tvGraphLines = grlines;
    setGraphParams();
}

// Graphics screen functions

typedef enum {
    mvLEFT,
    mvRIGHT,
    mvUP,
    mvDOWN,
    mvLAST,
    mvNONE,
} CMDMove;

struct TGraphic {
    int x;
    int y;
    int w;
    int h;
    int prex;
    int prey;
    char speed;
    bool doPaint;
    CMDMove lastCmdMove;
	char pnttimes;  //count how many times we paint an image helps to make animation
	char pntspeed;
    char imgcount;
    char curimg;
    char *graphic;
    char *sprite;
};

const char DEFSPEED = 1;
enum { MAXGRAPHICS = 5 };

struct TGraphic graphicArr[MAXGRAPHICS];
int grArrLen = 0;

int createGraphic(int w, int h, bool domemalloc, char imgcnt) {
    if (grArrLen >= MAXGRAPHICS) {
        return -1; // Array full
    }
    unsigned int grSize = (w/8) * h;

    graphicArr[grArrLen].w = w;
    graphicArr[grArrLen].h = h;
    graphicArr[grArrLen].speed = DEFSPEED;
    graphicArr[grArrLen].doPaint = true;
    graphicArr[grArrLen].lastCmdMove = mvNONE; 
	graphicArr[grArrLen].pnttimes = 0;
	graphicArr[grArrLen].pntspeed = 4;
    graphicArr[grArrLen].imgcount = imgcnt;
    graphicArr[grArrLen].curimg = 0;
    if (domemalloc) {
        graphicArr[grArrLen].graphic = (char*)malloc(grSize * imgcnt * sizeof(char));
        if (!graphicArr[grArrLen].graphic) {
            return -2; // Memory allocation failed
        }
    } else {
        graphicArr[grArrLen].graphic = NULL;
    }
    //buffer used for scrolling the graphic in bytes
    graphicArr[grArrLen].sprite = (char*)malloc((grSize+1*h)   * sizeof(char));

    return grArrLen++;
}

// Function to reverse the bits of an 8-bit integer
extern unsigned char reverse_bits(unsigned char n) __z88dk_fastcall;

#asm
; Z80 assembly code to reverse the bits of an 8-bit integer
; Inputs:
;   A - 8-bit integer to reverse bits
; Outputs:
;   A - Reversed 8-bit integer

._reverse_bits

 ; reverse bits in A
; 17 bytes / 66 cycles
  ;param on l due to fastcall so copy to a

  ld a,l    ; a = 76543210  
  rlca
  rlca      ; a = 54321076
  xor l
  and 0xAA
  xor l     ; a = 56341270
  ld l,a
  rlca
  rlca
  rlca      ; a = 41270563
  rrc l     ; l = 05634127
  xor l
  and 0x66
  xor l     ; a = 01234567
  ld l,a
    ret                  ; Return with reversed byte in A
#endasm

#asm
 defm "paintToSprite"
#endasm

void paintToSprite(struct TGraphic *mygr){
    int posx = mygr->x / 8;
    int posxrem = mygr->x % 8;
    int posy = mygr->y;
    int ibt = 0;
   // int btaddr = tvGraphBufferReal + posy * graphlinewidth + posx;
    int btwidth = (mygr->w ) / 8;
	int imglen = btwidth * mygr->h;
    unsigned char *tmgraph=mygr->graphic+(mygr->curimg*imglen);

    int ispr = 0;
    for (unsigned char y = 0; y < mygr->h; y++) {
        unsigned char btx , btxnext = 0;
        for (unsigned char x = 0; x < btwidth; x++) {
            unsigned char btrd = tmgraph[ibt++];
            btx = (btrd >> posxrem) | btxnext;
            btxnext = btrd << (8 - posxrem);
            mygr->sprite[ispr++]= reverse_bits(btx);
            //ram[btaddr++] = reverse_bits(btx);
        }
        mygr->sprite[ispr++]= reverse_bits(btxnext);
        //ram[btaddr] = reverse_bits(btxnext);
       // btaddr += (graphlinewidth - btwidth );
    }
    mygr->doPaint = false;
	if (++mygr->pnttimes>mygr->pntspeed) { //every x frames change image
		mygr->pnttimes=0;
		mygr->curimg =  (mygr->curimg+1 < mygr->imgcount) ? mygr->curimg+1 : 0;
	}


}

#asm
 defm "moveGraphic"
#endasm


void moveGraphic(struct TGraphic *mygr, CMDMove cmdNewMv) {
    if (cmdNewMv == mvLAST) cmdNewMv = mygr->lastCmdMove; 
    else mygr->lastCmdMove = cmdNewMv;
    mygr->prex = mygr-> x;
    mygr->prey = mygr-> y;
    switch (cmdNewMv) {
        case mvLEFT:
		    if (mygr->x-mygr->speed>0) {
              mygr->x -= mygr->speed;
              mygr->doPaint = true;
			}
            break;
        case mvRIGHT:
		    if (mygr->x+mygr->w+mygr->speed<graphlinewidth*8) { //graphlinewidth is in bytes
              mygr->x += mygr->speed;
              mygr->doPaint = true;
		    }
            break;
        case mvUP:
		    if (mygr->y-mygr->speed>0) {
              mygr->y -= mygr->speed;
              mygr->doPaint = true;
			}
            break;
        case mvDOWN:
		    if (mygr->y+mygr->h+mygr->speed<tvGraphLines) {
              mygr->y += mygr->speed;
              mygr->doPaint = true;
			}
            break;
        case mvNONE:
            break;
        case mvLAST:
            // Do nothing
              mygr->doPaint = true;
            break;
    }
   // if (mygr->doPaint)
        paintToSprite(mygr);
}

#asm
 defm "showGraphic"
#endasm

void showGraphic(struct TGraphic *mygr) {
	//if (!mygr->doPaint) return;
    int posx = mygr->x / 8;
    int posxrem = mygr->x % 8;
    int posy = mygr->y;
    int ibt = 0;
    int btaddr = tvGraphBufferReal + posy * graphlinewidth + posx;
    int btwidth = (mygr->w ) / 8;
	int imglen = btwidth * mygr->h;

    for (unsigned char y = 0; y < mygr->h; y++) {
        unsigned char btx = 0, btxnext = 0;
        for (unsigned char x = 0; x < btwidth; x++) {
            unsigned char btrd = (mygr->graphic+(mygr->curimg*imglen))[ibt++];
            btx = (btrd >> posxrem) | btxnext;
            btxnext = btrd << (8 - posxrem);
            ram[btaddr++] = reverse_bits(btx);
        }
        ram[btaddr] = reverse_bits(btxnext);
        btaddr += (graphlinewidth - btwidth );
    }
    mygr->doPaint = false;
	if (++mygr->pnttimes>mygr->pntspeed) { //every x frames change image
		mygr->pnttimes=0;
		mygr->curimg =  (mygr->curimg+1 < mygr->imgcount) ? mygr->curimg+1 : 0;
	}
}

#asm
 defm "showSprite"
#endasm

void showSprite(struct TGraphic *mygr) {
    int posx = mygr->x / 8;
    //int posxrem = mygr->x % 8;
    int posy = mygr->y;
    int ibt = 0;
    int btaddr = tvGraphBufferReal + posy * graphlinewidth + posx;
    int btwidth = (mygr->w ) / 8;
	//int imglen = btwidth * mygr->h;

    for (unsigned char y = 0; y < mygr->h; y++) {
        unsigned char btx = 0, btxnext = 0;
        for (unsigned char x = 0; x <= btwidth; x++) {
            unsigned char btrd = mygr->sprite[ibt++];
            ram[btaddr++] = btrd;
        }
        btaddr += (graphlinewidth - btwidth -1 );
    }
    mygr->doPaint = false;

}


void clearSprite(struct TGraphic *mygr) {
    int posx = mygr->prex / 8;
    int posy = mygr->prey;
    int btaddr = tvGraphBufferReal + posy * graphlinewidth + posx;
    int btwidth = (mygr->w ) / 8;
    for (unsigned char y = 0; y < mygr->h; y++) {
        for (unsigned char x = 0; x <= btwidth; x++) {
            ram[btaddr++] = 0;
        }
        btaddr += (graphlinewidth - btwidth -1 );
    }
}



int spridx=0;
bool painted = false;

void paintSprites() {
#asm
    di
#endasm
        for (unsigned char j = 0; j < grArrLen;j++)
           clearSprite( graphicArr[j] );

        for (unsigned char j = 0; j < grArrLen;j++)
           showSprite( graphicArr[j] );

           // showSprite(graphicArr[spridx++]);
       // showGraphic(graphicArr[spridx++]);
		//if (spridx>grArrLen) spridx=0;
#asm
    ei
#endasm
        painted = true;
}

int random_number(int min_num, int max_num) {
    int result = 0, low_num = 0, hi_num = 0;

    if (min_num < max_num) {
        low_num = min_num;
        hi_num = max_num + 1; // Include max_num in output
    } else {
        low_num = max_num + 1; // Include max_num in output
        hi_num = min_num;
    }

    result = (rand() % (hi_num - low_num)) + low_num;
    return result;
}

// Text screen functions
void testtextscreen() {
    nbSetScreen(15, true, true); // Set new 15 lines text screen 80cols with VFD screen

    cls(); // Clear screen

    printf("%c\nHello from C TEST PROGRAM\r", 31);
    printf("Hello from C TEST PROGRAM\r");
    scrPos(1, 10); // Set cursor position to 1st column 10th line
    printf("Position in line 10\r");

    char c = nbGetCharW();
    printf("You pressed: %c as int= %d \r", c, c);

    nbGetCharW();
    nbSetScreen(25, false, true); // Set default screen 24 lines
}

enum { nbleft = 8 , nbright = 26, nbup = 11, nbdown = 10 };

void doMovePlayer(char c){
	char spr1=0;
        switch (c) {
            case nbleft: 
                    graphicArr[spr1].graphic = pacmanleft;
                    moveGraphic(&graphicArr[spr1], mvLEFT);                    
                    break;
            case nbright: 
                    graphicArr[spr1].graphic = pacmanright;
                    moveGraphic(&graphicArr[spr1], mvRIGHT);                     
                    break;
            case nbup: 
                    moveGraphic(&graphicArr[spr1], mvUP); 
                    break;
            case nbdown: 
                    moveGraphic(&graphicArr[spr1], mvDOWN); 
                    break;
            case ' ': 
                    moveGraphic(&graphicArr[spr1], mvNONE); 
                    break;
            default: {
                moveGraphic(&graphicArr[spr1], mvLAST);
            }
        }
}

void doMoveEnemy(){
	char c=0;
	char spr2=1;
 int r = random_number(0, 100);
        if (r > 96) {
            int r2 = random_number(0, 100);
            if (r2 < 25) c = 'z';
            else if (r2 < 50) c = 'x';
            else if (r2 < 75) c = 'q';    
            else c = 'a';
        }

        switch (c) {
            case 'z': moveGraphic(&graphicArr[spr2], mvLEFT); break;
            case 'x': moveGraphic(&graphicArr[spr2], mvRIGHT); break;
            case 'q': moveGraphic(&graphicArr[spr2], mvUP); break;
            case 'a': moveGraphic(&graphicArr[spr2], mvDOWN); break;
            default: {
                moveGraphic(&graphicArr[spr2], mvLAST);
            }
        }
}

char pr = 0;

bool tmelapsed(){
  
    unsigned char c3, c2, c1;
    char ne = 0;

    c3 = ram[105];
    c2 = ram[106];
    c1 = ram[107];    
//
    ne = c1 & 4;

    if (pr!=ne){
        pr = ne;
        return true;
    }

    return false;

}

int main() {
    initDynMem(); // Initializing dynamic memory allocation
    setup(); // Setup needed devices

    //create a graphic screen 160 lines, low res, with vfd and wide
    nbSetGraphScreen(160, false, true, false);
    cls();

    int spr1 = createGraphic(16, 16, false, 3);
    printf("Graphic created at pos %d\r", spr1);
    graphicArr[spr1].x = 10;
    graphicArr[spr1].y = 20;
	graphicArr[spr1].pntspeed = 2;
	graphicArr[spr1].speed = 2;
    graphicArr[spr1].graphic = pacmanleft;
    //graphicArr[spr1].sprite = tempspr;

    int spr2 = createGraphic(16, 16, false, 2);
    printf("Graphic created at pos %d\r", spr2);
    graphicArr[spr2].x = 80;
    graphicArr[spr2].y = 60;
    graphicArr[spr2].graphic = whitepiece;

    int c, lastkey = 0;
    do {
        
        c = nbGetChar();

		if (c=='d'){
			printf("x=%d, y=%d, curimg=%d %d %d\r",graphicArr[spr1].x,graphicArr[spr1].y,graphicArr[spr1].curimg,graphicArr[spr1].pnttimes,graphicArr[spr1].imgcount);
		}
        else
        if (c=='t'){
            printf("Timer: %d %d\r",  ram[105],pr);
        }
        else
        if (c=='-'){
            graphicArr[spr1].speed--;
            printf("Speed: %d \r",  graphicArr[spr1].speed);
        }
        else
        if (c=='+'){
            graphicArr[spr1].speed++;
            printf("Speed: %d \r",  graphicArr[spr1].speed);
        }
        else
        if (c!=0)
           lastkey = c;


        if (painted) {
            doMovePlayer(lastkey);
            doMoveEnemy();
            painted = false;
        }


        if (tmelapsed()){
          if (!painted)  
             paintSprites(); 
        }
    } while (c != 27);

    cleanExit(); // Close devices
    nb_close(1); // Close tape the loader leaves it open
}

#asm

._whitepiece
 defb @00000000, @00000000
 defb @00000000, @00000000
 defb @00000011, @11000000
 defb @00001100, @00110000
 defb @00010000, @11001000
 defb @00010000, @00101000
 defb @00100000, @00010100
 defb @00100000, @00010100
 defb @00100000, @00000100
 defb @00100000, @00000100
 defb @00010000, @00001000
 defb @00010000, @00001000
 defb @00001100, @00110000
 defb @00000011, @11000000
 defb @00000000, @00000000
 defb @00000000, @00000000

._blackpiece
 defb @00000000, @00000000
 defb @00000000, @00000000
 defb @00000011, @11000000
 defb @00001111, @11110000
 defb @00011111, @00111000
 defb @00011111, @11011000
 defb @00111111, @11101100
 defb @00111111, @11101100
 defb @00111111, @11111100
 defb @00111111, @11111100
 defb @00011111, @11111000
 defb @00011111, @11111000
 defb @00001111, @11110000
 defb @00000011, @11000000
 defb @00000000, @00000000
 defb @00000000, @00000000

//defm "pacman"
 ._pacmanright
defb @00000000, @00000000
defb @00000000, @00000000
defb @00000011, @11000000
defb @00001111, @11110000
defb @00011111, @11100000
defb @00111111, @10000000
defb @00111110, @00000000
defb @00111100, @00000000
defb @00111100, @00000000
defb @00111110, @00000000
defb @00111111, @10000000
defb @00011111, @11100000
defb @00001111, @11110000
defb @00000011, @11000000
defb @00000000, @00000000
defb @00000000, @00000000

defb @00000000, @00000000
defb @00000000, @00000000
defb @00000011, @11000000
defb @00001111, @11110000
defb @00011111, @11111000
defb @00111111, @11111100
defb @00111111, @11000000
defb @00111100, @00000000
defb @00111100, @00000000
defb @00111111, @11000000
defb @00111111, @11111100
defb @00011111, @11111000
defb @00001111, @11110000
defb @00000011, @11000000
defb @00000000, @00000000
defb @00000000, @00000000

defb @00000000, @00000000
defb @00000000, @00000000
defb @00000011, @11000000
defb @00001111, @11110000
defb @00011111, @11111000
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00011111, @11111000
defb @00001111, @11110000
defb @00000011, @11000000
defb @00000000, @00000000
defb @00000000, @00000000

._pacmanleft
defb @00000000 , @00000000 
defb @00000000 , @00000000 
defb @00000011 , @11000000 
defb @00001111 , @11110000 
defb @00011111 , @11111000 
defb @00111111 , @11111100 
defb @00000011 , @11111100 
defb @00000000 , @00111100 
defb @00000000 , @00111100 
defb @00000011 , @11111100 
defb @00111111 , @11111100 
defb @00011111 , @11111000 
defb @00001111 , @11110000 
defb @00000011 , @11000000 
defb @00000000 , @00000000 
defb @00000000 , @00000000 

defb @00000000 , @00000000 
defb @00000000 , @00000000 
defb @00000011 , @11000000 
defb @00001111 , @11110000 
defb @00000111 , @11111000 
defb @00000001 , @11111100 
defb @00000000 , @01111100 
defb @00000000 , @00111100 
defb @00000000 , @00111100 
defb @00000000 , @01111100 
defb @00000001 , @11111100 
defb @00000111 , @11111000 
defb @00001111 , @11110000 
defb @00000011 , @11000000 
defb @00000000 , @00000000 
defb @00000000 , @00000000 

defb @00000000, @00000000
defb @00000000, @00000000
defb @00000011, @11000000
defb @00001111, @11110000
defb @00011111, @11111000
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00111111, @11111100
defb @00011111, @11111000
defb @00001111, @11110000
defb @00000011, @11000000
defb @00000000, @00000000
defb @00000000, @00000000


//temp use we malloc the necessary bytes
defm "TEMPBUFFER"
._tempspr

  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0
  defb 0,0,0

#endasm

Leave a Reply