Conway's game of life for the ZX81

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
pjshumphreys
Member
Posts: 66
Joined: Sat Feb 06, 2021 2:32 pm

Conway's game of life for the ZX81

Post by pjshumphreys »

I've been working on this as a fun mini project this past weekend. I'd like it to be faster but it works ok at least. On startup it'll ask for a value to seed rand() with, then will begin. If nothing changes during rendering the next seed is used to reinitialise with. Also, if it gets stuck you can press a key to skip to the next seed. You can compile this with

Code: Select all

zcc +zx81 -create-app -ogol -O3 gol.c
Here's the code:

Code: Select all

/* Conway's Game of Life for the ZX81 */
#include <stdio.h>
#include <stdlib.h>

#define WIDTH 32
#define HEIGHT 21

#define for_y for (y = 0; y < HEIGHT; y++)
#define for_x for (x = 0; x < WIDTH; x++)

struct {
  unsigned char x;
  unsigned char y;
} param;

void myputch(void) {
  __asm
    ld a, (_param)
    jp 0x07f1
  __endasm;
}

void myplot(void) {
  __asm
    ld bc,(_param)
    call 0x08f5
    ld a, 0x80
    jp 0x07f1
  __endasm;
}

void myunplot(void) {
  __asm
    ld bc,(_param)
    call 0x08f5
    ld a, 0
    jp 0x07f1
  __endasm;
}

char univ[HEIGHT][WIDTH];
char new[HEIGHT][WIDTH];

char* q;
char* r;
char* g;
char* m;
char* p;

int seed, hasChange, a, b, c, d, e, f, x, y, newval;

int main(int c, char **v) {
  printf("seed?\n");

  scanf("%d", &seed);
  srand(seed);

  do {
    /* void init(void) */ {
      r = (void*)univ;

      __asm
        ld bc,0
        call 0x08f5
      __endasm;

      for_y {
        for_x {
          param.x = ((*r++) = (rand() < RAND_MAX / 10)) ? 0x80 : 0;
          myputch();
        }
        //param.chars.x = 0x76;
        //myputch();
      }
    }

    do {
      if(getk()) {
        while(getk()) {}
        break;
      }

      /* void evolve(void) */ {
        q = (void*)new;
        r = (void*)univ;

        for_y {
          for_x {
            e = (y + HEIGHT);
            a = (((e - 1) % HEIGHT) * WIDTH);
            f = (((e + 1) % HEIGHT) * WIDTH);
            c = (x + WIDTH);
            b = ((c - 1) % WIDTH);
            d = ((c + 1) % WIDTH);

            e = ((e % HEIGHT) * WIDTH);
            c %= WIDTH;

            g = r + a;
            m = r + e;
            p = r + f;

            switch(g[b] + g[c] + g[d] + m[b] + m[d] + p[b] + p[c] + p[d]) {
              case 2: {
                if(m[x]) {
                  case 3:
                  q[x] = 1;
                  break;
                }
              } /* fall thru */

              default: {
                q[x] = 0;
              } break;
            }
          }

          q += WIDTH;
        }

        q = (void*)new;

        hasChange = 0;

        for_y {
          for_x {
            newval = *q++;

            if(newval != (*r)) {
              hasChange = 1;

              param.x = x;
              param.y = y;

              ((*r++) = newval) ? myplot() : myunplot();
            }
            else {
              ++r;
            }
          }
        }
      }
    } while(hasChange);

    ++seed;
  } while (1);
}
pjshumphreys
Member
Posts: 66
Joined: Sat Feb 06, 2021 2:32 pm

Re: Conway's game of life for the ZX81

Post by pjshumphreys »

Doh. The srand(seed) line should've been put under the line "/* void init(void) */ {"
stefano
Well known member
Posts: 2137
Joined: Mon Jul 16, 2007 7:39 pm

Re: Conway's game of life for the ZX81

Post by stefano »

I like the way you chose to pass the parameter to the ROM routines :)
pjshumphreys
Member
Posts: 66
Joined: Sat Feb 06, 2021 2:32 pm

Re: Conway's game of life for the ZX81

Post by pjshumphreys »

It's not the most elegant method of passing the arguments I admit, but this way most of the work of getting the parameters into the proper locations is handled by the compiler and it doesn't add much overhead :) I've been trying to avoid pushing and popping stuff as much as possible to try and speed things up.
pjshumphreys
Member
Posts: 66
Joined: Sat Feb 06, 2021 2:32 pm

Re: Conway's game of life for the ZX81

Post by pjshumphreys »

Oh, I can add zip attachments here also. Nice. I've attached a zip containing the source and built .p file in it
You do not have the required permissions to view the files attached to this post.
User avatar
dom
Well known member
Posts: 2072
Joined: Sun Jul 15, 2007 10:01 pm

Re: Conway's game of life for the ZX81

Post by dom »

In general, you might want to look at arch/z80.h for calling ROM routines
pjshumphreys
Member
Posts: 66
Joined: Sat Feb 06, 2021 2:32 pm

Re: Conway's game of life for the ZX81

Post by pjshumphreys »

I know about the AsmCall function and have used it in the past, but for simple things like this it seems like overkill.
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Re: Conway's game of life for the ZX81

Post by Timmy »

I agree that using global variables as function parameters are sometimes useful, as long as you can keep track of those parameters and you don't try to call nested functions. I use this trick myself sometimes too.

But most of the times I just use naked __fastcall__ instead.

The thing is, calling a function that has many parameters is taking a chunk of precious memory space. These are optimisations but not for beginners.
pjshumphreys
Member
Posts: 66
Joined: Sat Feb 06, 2021 2:32 pm

Re: Conway's game of life for the ZX81

Post by pjshumphreys »

Yeah, originally this code didn't use global variables or call the rom directly. It used variables on the stack and <graphics.h>. The code now is the result of many rounds of analysing the generated asm code and optimising accordingly.
Post Reply