C on the Spectrum (z88dk)

The place for codemasters or beginners to talk about programming any language for the Spectrum.
dom
Drutt
Posts: 11
Joined: Sun Feb 10, 2019 11:08 am

Re: C on the Spectrum (z88dk)

Post by dom »

FFoulkes wrote: Thu Feb 07, 2019 4:15 pm Well, it comes with scripts "build.sh" and "config.sh". These have to be executable ("chmod +x ./build.sh", "chmod +x ./config.sh"). It compiles ok then, when you run "./build.sh" (OpenSuSE 13.1), no severe errors. Installation goes into "/usr/local/share/z88dk" then. Sometimes it wants something in "/usr/local/lib/z88dk" instead.
That's not intended - can you let me know which ones?
FFoulkes wrote: Thu Feb 07, 2019 4:15 pm The build-directory with the compiler-libraries was 171 Mb in size. Quite big for a compiler targeting a simple 8 bit-platform, I would say.
Well...each target (machine) library is roughly a megabyte, and there's about 80 of them so the disc space mounts up quickly!
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

dom wrote: Sun Feb 10, 2019 11:27 amThat's not intended - can you let me know which ones?
Ah, never mind. I think, it's ok. I was a bit confused (by the installation), when I wrote the posting.
User avatar
utz
Microbot
Posts: 114
Joined: Wed Nov 15, 2017 9:04 am
Contact:

Re: C on the Spectrum (z88dk)

Post by utz »

dom wrote: Sun Feb 10, 2019 11:15 am
utz wrote: Sat Feb 09, 2019 2:07 pmDoesn't quite work for me with latest nightly. On step 'Verify the Install':

Code: Select all

$ zcc +zx -vn test.c -o test -lndos           
warning: unknown option '-iquote.'
warning: unknown option '-isystem/home/myuser/path/to/z88dk/lib/config/../..//include'
spurious duplicate filename '/tmp/tmpXXkntuNK.i2' - vs. 'test.c' 
Usage: ucpp [options] [file]
...
I think that means there's another ucpp on your path somewhere. Setup the path so that z88dk/bin is first. There is a compilation option to prefix all the commands with something unique to avoid this problem for global installations but I'm not sure it works properly for the zsdcc component.
Confirmed, I have ucpp as part of my OS (Gentoo), where it is required to build libreoffice. I'm no expert on these matters but I think that implies that changing the path is not an option. How do I apply the prefix option for a local installation? I tried setting it in the Makefile but that didn't solve the problem.

EDIT: Upon second thought, I think I can get away with overriding the path just for the user session, as I probably won't be needing the system ucpp for anything else. At least for now it works fine. So, thanks for your help :) Might still be a good idea to have those prefixes available for local installs somehow.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

Well, I'm running into a problem now. I'd like to print a UDG at a certain place on the screen.
Printing an "A" works with "-startup=1", but this has no UDG-support, meaning "printf("%c", 144);" doesn't do anything (although the UDG is defined (at address 65368)).
"-startup=30" has a certain UDG-support (printing in general does work then). But then the "PRINT AT" doesn't work:

Code: Select all

#include <stdio.h>
int main() {
    puts("\x16\x0A\x0D" "A");
    return 0;
}

Code: Select all

zcc +zx -vn -clib=sdcc_iy -startup=30 -zorg=32768 -create-app test.c
Any ideas how to get both working (UDG plus "PRINT AT")? Seems to be quite important for games, I guess.

There was a discussion about it here, but there wasn't a solution yet. Maybe we have to wait for further compiler development ...

Oh, and there is an error "Integer out of range" then, which looks just like in Basic. If the ordinary ROM-routines are called anyway, C may not be much faster than Basic, I think.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: C on the Spectrum (z88dk)

Post by dfzx »

FFoulkes wrote: Mon Feb 11, 2019 1:16 am Well, I'm running into a problem now. I'd like to print a UDG at a certain place on the screen.
I think there's a bit of a context shift required for people coming to C on the Spectrum. UDGs are largely a concept from BASIC which don't make a lot of sense in the C world. C is much closer to assembly language, so you need to think at a lower level. OTOH, the stdio stuff is a high level concept from UNIX, and although it's a useful starting point I personally have never been convinced of its value on the Spectrum. Once you get past "hello world," unless you're doing something which requires a lot of text manipulation, you'll probably just turn it off. Mixing these disparate concepts together is, as you've found, a bit of a headache. It's possible to make it work, as per Alvin's post which you found in the z88dk forum, but it doesn't really happen naturally.

So, at the risk of telling you you're asking the wrong question then answering my own, let's take a step back. :)

As soon as you start thinking about UDGs, you're thinking about graphics, and graphics, once you step outside the world of BASIC, means you need to start thinking about how to directly change the Spectrum's display. The display is memory mapped, so you change it by POKEing (to use the BASIC terminology) values into it. If you want an 8x8 pixel graphic to appear in the middle of the display you can do it like this:

Code: Select all

#include <arch/zx.h>

unsigned char hash_udg[] = { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA };

void printc(unsigned char x, unsigned char y, unsigned char* udg)
{
   unsigned char *p;
   unsigned char i;

   p = zx_cxy2saddr(x, y);

   for (i = 0; i < 8; ++i)
   {
      *p = *udg++;
      p += 256;
   }
}

int main( void )
{
  printc(15, 12, hash_udg);
  return 0;
}
which you can compile with:

Code: Select all

zcc +zx -vn -clib=sdcc_iy -startup=31 printc.c -o printc -create-app
The zx_cxy2saddr() function is described along with all its friends here, but in essence it takes an x,y character coordinates pair and returns you the address in the Spectrum's display of the top byte of that character cell. Adding 256 to it returns the address of the byte directly below it, hence the loop POKEs the 8 UDG byte values directly into the display at the place required.

That's the simple and fast way. At the other extreme you can use the SP1 library. You're not at that level yet, and I haven't written the tutorial article which tells you how to do it, but as a peek ahead you can see the control characters of the SP1 library's very sophisticated print facility here. SP1 isn't to everyone's taste, nor the answer to all problems, but if you're after a powerful library to experiment with for graphics it's worth looking into when you're ready.

Quite where this all leaves you I'm not sure. :lol: I suppose it depends on what you're trying to learn or what problem you're trying to solve.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
dom
Drutt
Posts: 11
Joined: Sun Feb 10, 2019 11:08 am

Re: C on the Spectrum (z88dk)

Post by dom »

dfzx wrote: Mon Feb 11, 2019 3:52 pmI think there's a bit of a context shift required for people coming to C on the Spectrum. UDGs are largely a concept from BASIC which don't make a lot of sense in the C world. C is much closer to assembly language, so you need to think at a lower level. OTOH, the stdio stuff is a high level concept from UNIX, and although it's a useful starting point I personally have never been convinced of its value on the Spectrum. Once you get past "hello world," unless you're doing something which requires a lot of text manipulation, you'll probably just turn it off. Mixing these disparate concepts together is, as you've found, a bit of a headache. It's possible to make it work, as per Alvin's post which you found in the z88dk forum, but it doesn't really happen naturally.
I have to admit that I've never fully got to grips with the way the newlib stdio works, it's powerful but has quite a bit of overhead.

In fact, stdio one of two areas where classic and newlib really diverge: most of the rest of the library code is shared. However, if you're looking to create a text based application that works across many platforms then classic may be the answer.

The gencon is a terrible name for the VT52+ZX Code screen driver that's supported on "most" of the classic targets. It provides a consistent way to print text (and yes, UDGs) and handle colours across many platforms. It even provides a SCREEN$ equivalent for collision detection. Cross Chase uses gencon for many of the z80 builds.
Last edited by dom on Mon Feb 11, 2019 8:18 pm, edited 1 time in total.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

Thank you, dfzx. I think, this is a very good answer!
Because probably more people starting with C on the Spectrum will come to the point, where they need to think beyond Basic.
At the moment, I wasn't sure about going for C or assembly directly, so I watched a tutorial about Spectrum's assembly. Quite a good one, actually. There was some homework, so I wondered, if I could do that in C. That was, what got me there.
Next step would be to get that colour (white on blue). The control codes won't be the answer, I guess. :)
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

Consider this. "PRINT AT" again ... :) :

Code: Select all

#include <arch/zx.h>
#include <stdio.h>
#include <string.h>

void cls() {
    zx_cls(PAPER_WHITE);
}

void printCharAt(unsigned char x, unsigned char y, unsigned char c) {
   unsigned char *p;
   unsigned char *dat;
   long int temp;
   unsigned char i;
   temp = 15360 + c * 8;
   dat = (unsigned char *) temp;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = *dat;
      dat++;
      p += 256;
   }
}

void printUdgAt(unsigned char x, unsigned char y, unsigned char *udg) {
   unsigned char *p;
   unsigned char i;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = *udg;
      udg++;
      p += 256;
   }
}

unsigned char printAt(unsigned char x, unsigned char y, unsigned char *mystring) {
    unsigned char slen = strlen(mystring);
    if (x + slen > 32) {
        puts("Warning: Integer out of range. Nothing printed.");
        printf("x: %d  string: %s\n", x, mystring);
        return 1;
    }
    if (y > 21) {
        puts("Warning: Integer out of range. Nothing printed.");
        printf("y: %d\n", y);
        return 2;
    }
    unsigned char i;
    for (i=0; i < slen; i++) {
        printCharAt(x, y, mystring[i]);
        x++;
    }
    return 0;
}

int main() {
    cls();
    unsigned char a[] = "CONNECT 4";
    unsigned char udg1[] = {0, 24, 60, 126, 126, 60, 24, 0};
    printAt(12, 4, a);
    unsigned char i;
    unsigned char u;
    unsigned char fieldstart_x = 13;
    unsigned char fieldstart_y = 10;
    for (i=0; i<6; i++) {
        for (u=0; u<7; u++) {
            printUdgAt(fieldstart_x + u, fieldstart_y + i, udg1);
        } 
    }
    return 0;
}
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

Ok, I got colours now, too:

Code: Select all

#define BLACK   0
#define BLUE    1
#define RED     2
#define MAGENTA 3
#define GREEN   4
#define CYAN    5
#define YELLOW  6
#define WHITE   7

void setColour(unsigned char x, unsigned char y, unsigned char paper, unsigned char ink) {
    unsigned char *p = zx_cxy2aaddr(x, y); 
    *p = 8 * paper + ink;
}
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: C on the Spectrum (z88dk)

Post by dfzx »

FFoulkes wrote: Mon Feb 11, 2019 10:33 pm Ok, I got colours now, too:

Code: Select all

#define BLACK   0
#define BLUE    1
#define RED     2
#define MAGENTA 3
#define GREEN   4
#define CYAN    5
#define YELLOW  6
#define WHITE   7

void setColour(unsigned char x, unsigned char y, unsigned char paper, unsigned char ink) {
    unsigned char *p = zx_cxy2aaddr(x, y); 
    *p = 8 * paper + ink;
}
Yes, that's the idea! When do we get to play the final game? :)

Have a look in arch/zx.h though - all the colour macros are in there, plus other stuff you're likely to need.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

dfzx wrote: Tue Feb 12, 2019 9:46 amYes, that's the idea! When do we get to play the final game? :)
Ha ha. :) As I still can't get really comfortable with C (just as it always was), I have another idea now.
Please give me some time. I think, it will be really nice. I'll post about it, when it's ready.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

This is where I went the last days:

viewtopic.php?f=9&t=1315
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

Hi, back again. ;) I'm trying to rewrite the Connect4-example with Spectrum's Basic now. It would be fast enough to animate the falling tile.
But it's rather slow (too slow), when checking the grid, if anybody has won.

So would it be possible to write a Basic/C-hybrid program? I thought, maybe I could poke the grid to defined memory addresses with Basic, then call a (compiled) C program, also in memory. Write the result of the check by the C program to another memory address, then fall back to Basic. Read the result with Peek there. Is that something, that could be done? Or is it a bad idea?
User avatar
Einar Saukas
Bugaboo
Posts: 3070
Joined: Wed Nov 15, 2017 2:48 pm

Re: C on the Spectrum (z88dk)

Post by Einar Saukas »

FFoulkes wrote: Wed Mar 13, 2019 11:48 amHi, back again. ;) I'm trying to rewrite the Connect4-example with Spectrum's Basic now. It would be fast enough to animate the falling tile.
But it's rather slow (too slow), when checking the grid, if anybody has won.
It can be done in Sinclair BASIC at a reasonable speed. Take a look:

http://reptonix.awardspace.co.uk/sincla ... t4-pvp.htm

It's available for download here:

https://spectrumcomputing.co.uk/index.p ... 6&id=25224

Notice it should be possible to make it run even faster, since this implementation above was optimized for size instead of speed...
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

Thanks. Interesting. Don't get how you do it. So you put the grid-data in a one-dimensional array. Does this help by finding diagonal tiles? I'd say, it can't work. But obviously it does. And it's even fast. So it must be ... magic.
User avatar
Einar Saukas
Bugaboo
Posts: 3070
Joined: Wed Nov 15, 2017 2:48 pm

Re: C on the Spectrum (z88dk)

Post by Einar Saukas »

Actually using a one-dimensional array just helped make a more compact program.

It should be even faster to use separate code for checking each direction.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

For now, I stuck to my approach, even if it may feel a bit clumsy now.
First time, I could do something interesting in C. With a BASIC and a Python prototype, writing C becomes less difficult, it seems. In such small programs like on the Spectrum, it may even be ok, to allow oneself a few global variables. Makes it a bit easier and BASIC-like. The speed of such a program in C is quite impressive, though. Finally made the Spectrum agile.

Code: Select all

#include <arch/zx.h>
#include <input.h>
#include <stdio.h>

/*  winwithfour.c, 1.0:

    Compile with: 

    zcc +zx -vn -startup=0 -clib=sdcc_iy -zorg=32768 winwithfour.c -o winwithfour -create-app

    A little game in Python/Pygame (inspired by a very good Z80 Assembly
    tutorial on Youtube).

    Copyright 2019, Forum-name: Major Percival Ffoulkes, GNU GPL v.2,

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

typedef unsigned char byte;

#define BLACK   0
#define BLUE    1
#define RED     2
#define MAGENTA 3
#define GREEN   4
#define CYAN    5
#define YELLOW  6
#define WHITE   7

#define GRIDWIDTH 7
#define GRIDHEIGHT 6
#define MATCHES 4
#define WINCOMBINATIONS 69

struct Grid {
    byte grid[GRIDHEIGHT][GRIDWIDTH];
};

typedef struct Grid Grid;
Grid grid;

struct Combinations {
    byte c[WINCOMBINATIONS][MATCHES];
};

typedef struct Combinations Combinations;
Combinations comb;

byte tpos_x;
int tpos_y;
byte ccol;
byte moves;
byte udg[] = {0, 24, 60, 126, 126, 60, 24, 0};
byte winner;
byte grid_x;
byte grid_y;
int wincombination;
byte outoftiles;

void setColour(byte x, byte y, byte paper, byte ink) {
    byte *p = zx_cxy2aaddr(x, y); 
    *p = 8 * paper + ink;
}

void printCharAt(byte x, byte y, byte c) {
   byte *p;
   byte *dat;
   long int temp;
   byte i;
   temp = 15360 + c * 8;
   dat = (byte *) temp;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = *dat;
      dat++;
      p += 256;
   }
}

void printBlockAt(byte y, byte x, byte paper, byte ink) {
   setColour(x, y, paper, ink);
   byte i;
   byte *p;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = udg[i];
      p += 256;
   }
}

byte my_strlen(byte *mystring) {
    byte x = 0;
    while (*mystring != '\0') {
        x++;
        mystring++;
    }
    return x;
}


byte printAt(byte y, byte x, byte *mystring) {
    byte slen = my_strlen(mystring);
    if (x + slen > 32) {
        puts("Warning: Integer out of range. Nothing printed.");
        printf("x: %d  string: %s\n", x, mystring);
        return 1;
    }
    if (y > 21) {
        puts("Warning: Integer out of range. Nothing printed.");
        printf("y: %d\n", y);
        return 2;
    }
    byte i;
    for (i=0; i < slen; i++) {
        printCharAt(x, y, mystring[i]);
        x++;
    }
    return 0;
}

void initCombinations() {
    byte combination = 0;
    byte row;
    byte column;
    byte i;
    byte u;

    /* Horizontals: */
    for (row=0; row < GRIDHEIGHT; row++) {
        for (column=0; column < GRIDWIDTH + 1 - MATCHES; column++) {
            for (u=0; u < MATCHES; u++) {
                comb.c[combination][u] = (u + column) * 10 + row;
            }
            combination++;
        }
    }

    /* Verticals: */
    for (column=0; column < GRIDWIDTH; column++) {
        for (row=0; row < GRIDHEIGHT + 1 - MATCHES; row++) {
            for (u=0; u < MATCHES; u++) {
                comb.c[combination][u] = column * 10 + u + row;
            }
            combination++;
        }
    }

    /* DiagonalsTopRightToDownLeft. -2 to +3: */
    int d;
    int x;
    int y;
    byte arr[4];
    byte arrcount = 0;
    for (d = - (GRIDHEIGHT - MATCHES); d < GRIDWIDTH + 1 - MATCHES; d++) {
        for (i=0; i < GRIDWIDTH - MATCHES; i++) {
            arrcount = 0;
            for (u=0; u < MATCHES; u++) {
                x = i + u;
                y = i + u;
                if (d > 0) {
                    x += d;
                }
                if (d < 0) {
                    y -= d;
                }
                if (x >= GRIDWIDTH || y >= GRIDHEIGHT) {
                    break;
                }
                arr[arrcount] = x * 10 + y;
                arrcount++;
            }
            if (arrcount == MATCHES) {
                for (u=0; u < MATCHES; u++) {
                    comb.c[combination][u] = arr[u];
                }
                combination++;
            }
        }
    }

    /* DiagonalsTopRightToDownLeft: -3 to +2: */
    for (d = - (GRIDWIDTH - MATCHES); d < GRIDHEIGHT - MATCHES + 1; d++) {
        for (i=0; i < GRIDWIDTH - MATCHES; i++) {
            arrcount = 0;
            for (u=0; u < MATCHES; u++) {
                x = GRIDWIDTH - 1 - i - u;
                y = i + u;
                if (d < 0) {
                    x += d;
                }
                if (d > 0) {
                    y += d;
                }
                if (x < 0 || y >= GRIDHEIGHT) {
                    break;
                }
                arr[arrcount] = x * 10 + y;
                arrcount++;
            }
            if (arrcount == MATCHES) {
                for (u=0; u < MATCHES; u++) {
                    comb.c[combination][u] = arr[u];
                }
                combination++;
            }
        }
    }
}

void testCombinations() {
/* Print comb.c[][] */
    byte i;
    byte u;
    byte x = 0;
    for (i=0;i < 69;i++) {
        printf("%d: [", x);
        for (u=0;u < MATCHES;u++) {
            printf("%d", comb.c[i][u]);
            if (u < MATCHES - 1) {
                printf(", ");
            }
        }
        printf("]\n");
        x++;
    }
}

void initGrid() {
    byte i; byte u;
    for (i=0;i < GRIDHEIGHT;i++) {
        for (u=0;u < GRIDWIDTH;u++) {
            grid.grid[i][u] = 0;
        }
    }
}

void initGame() {
    winner = 0;
    tpos_x = 3;
    tpos_y = -1;
    ccol = RED;
    moves = 0;
    grid_x = 12;
    grid_y = 6;
    outoftiles = 0;
    wincombination = -1;
    initGrid();
    /* Show Grid: */
    zx_cls(PAPER_WHITE);
    zx_border(INK_RED);
    printAt(grid_y - 3, grid_x - 3, "Win with Four");
    byte y;
    byte x;
    for (y=0; y < GRIDHEIGHT; y++) {
        for (x=0; x < GRIDWIDTH; x++) {
            printBlockAt(grid_y + y, grid_x + x, BLUE, WHITE);
        } 
    }

}

byte getKeyPress() {
    byte c;
    in_wait_key();
    c = in_inkey();
    in_wait_nokey();
    return c;
}

void checkGameWon() {
    byte rc;
    byte yc;
    byte i;
    byte u;
    byte x;
    byte y;
    for (i=0;i< WINCOMBINATIONS;i++) {
        rc = 0;
        yc = 0;
        for (u=0;u< MATCHES;u++) {
            if (comb.c[i][u] >= 10) {
                x = comb.c[i][u] / 10;
            } else {
                x = 0;
            }
            y = comb.c[i][u] - x * 10;
            if (grid.grid[y][x] == RED) {
                rc++;
            }
            if (grid.grid[y][x] == YELLOW) {
                yc++;
            }
            if (rc == MATCHES) {
                winner = RED;
                wincombination = i;
                return;
            }
            if (yc == MATCHES) {
                winner = YELLOW;
                wincombination = i;
                return;
            }
        }
    }
}

void dropTile() {
    if (grid.grid[0][tpos_x] != 0) {
        return;
    }
    byte y;
    int i;
    int count = 0;
    tpos_y = 0;
    for (y=0; y < GRIDHEIGHT; y++) {
        if (grid.grid[y][tpos_x] == 0) {
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, BLUE, WHITE);
            tpos_y = y;
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, BLUE, ccol);
            for (i=0; i<3000;i++){count++;}
        }
    }
    grid.grid[tpos_y][tpos_x] = ccol;
    moves++;
    if (moves >= GRIDWIDTH * GRIDHEIGHT) {
        outoftiles = 1;
        return;
    } 
    checkGameWon();
    if (winner > 0) {
        return;
    }
    tpos_y = -1;
    if (ccol == RED) {
        ccol = YELLOW;
        return;
    }
    if (ccol == YELLOW) {
        ccol = RED;
        return;
    }
}


void showWinner() {
    if (winner == RED) {
        printAt(grid_y + 9, grid_x - 2, "Red has won!");
    }
    if (winner == YELLOW) {
        printAt(grid_y + 9, grid_x - 3, "Yellow has won!");
    }
    byte u;
    byte x;
    byte y;
    for (u=0; u < MATCHES;u++) {
        x = comb.c[wincombination][u] / 10;
        y = comb.c[wincombination][u] - x * 10;
        printBlockAt(grid_y + y, grid_x + x, BLUE, MAGENTA);
    }
}


int main() {
    initCombinations();
    initGame();
    byte key;
    byte running = 1;
    while (running) {
        if (! winner && ! outoftiles) {
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, WHITE, ccol);
        }
        key = getKeyPress();
        if (! winner && ! outoftiles) {
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, WHITE, WHITE);
            if (key == 'w' && tpos_x < 6) {
                tpos_x++;
            }
            if (key == 'q' && tpos_x > 0) {
                tpos_x--;
            }
            if (key == ' ') {
                dropTile();
            }
        }
        if (key == 'r') {
            initGame();
        }
        if (winner) {
            showWinner();
        }
        if (outoftiles) {
            printAt(grid_y + 9, grid_x - 9, "The Game ended in a Draw!");
        }
        if (winner || outoftiles) {
            printAt(grid_y + 11, grid_x - 8, "Press 'r' to play again.");
        }
        if (key == 'e' || key == 'a') {
            zx_cls(PAPER_WHITE);
            zx_border(INK_WHITE);
            running = 0;
        }
    }
    return 0;
}
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

Fixed a few bugs, flashing winning tiles and better keyboard I/O (couldn't edit previous posting any more):

Code: Select all

#include <arch/zx.h>
#include <input.h>

/*  winwithfour.c, 1.0:

    Compile with: 

    zcc +zx -vn -startup=0 -clib=sdcc_iy -zorg=32768 winwithfour.c -o winwithfour -create-app

    A little game inspired by a very good Z80 Assembly tutorial on Youtube.

    Copyright 2019, Forum-name: Major Percival Ffoulkes, GNU GPL v.2,

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

typedef unsigned char byte;

#define BLACK   0
#define BLUE    1
#define RED     2
#define MAGENTA 3
#define GREEN   4
#define CYAN    5
#define YELLOW  6
#define WHITE   7

#define GRIDWIDTH 7
#define GRIDHEIGHT 6
#define MATCHES 4
#define WINCOMBINATIONS 69
#define FLASHDELAY 400

#define KEY_q     0
#define KEY_w     1
#define KEY_space 2
#define KEY_r     3
#define KEY_e     4
#define KEY_a     5
#define USEDKEYS  6

struct Grid {
    byte grid[GRIDHEIGHT][GRIDWIDTH];
};

typedef struct Grid Grid;
Grid grid;

struct Combinations {
    byte c[WINCOMBINATIONS][MATCHES];
};

typedef struct Combinations Combinations;
Combinations comb;

byte tpos_x;
int tpos_y;
byte ccol;
byte moves;
byte udg[] = {0, 24, 60, 126, 126, 60, 24, 0};
byte winner;
byte grid_x;
byte grid_y;
int wincombination;
byte outoftiles;
int keys_now[USEDKEYS];
int keys_before[USEDKEYS];
int keys_timeout[USEDKEYS];

void setColour(byte x, byte y, byte paper, byte ink) {
    byte *p = zx_cxy2aaddr(x, y); 
    *p = 8 * paper + ink;
}

void printCharAt(byte x, byte y, byte c) {
   byte *p;
   byte *dat;
   long int temp;
   byte i;
   temp = 15360 + c * 8;
   dat = (byte *) temp;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = *dat;
      dat++;
      p += 256;
   }
}

void printBlockAt(byte y, byte x, byte paper, byte ink) {
   setColour(x, y, paper, ink);
   byte i;
   byte *p;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = udg[i];
      p += 256;
   }
}

byte my_strlen(byte *mystring) {
    byte x = 0;
    while (*mystring != '\0') {
        x++;
        mystring++;
    }
    return x;
}


byte printAt(byte y, byte x, byte *mystring) {
    byte slen = my_strlen(mystring);
    byte i;
    for (i=0; i < slen; i++) {
        printCharAt(x, y, mystring[i]);
        x++;
    }
    return 0;
}

void initCombinations() {
    byte combination = 0;
    byte row;
    byte column;
    byte i;
    byte u;

    /* Horizontals: */
    for (row=0; row < GRIDHEIGHT; row++) {
        for (column=0; column < GRIDWIDTH + 1 - MATCHES; column++) {
            for (u=0; u < MATCHES; u++) {
                comb.c[combination][u] = (u + column) * 10 + row;
            }
            combination++;
        }
    }

    /* Verticals: */
    for (column=0; column < GRIDWIDTH; column++) {
        for (row=0; row < GRIDHEIGHT + 1 - MATCHES; row++) {
            for (u=0; u < MATCHES; u++) {
                comb.c[combination][u] = column * 10 + u + row;
            }
            combination++;
        }
    }

    /* DiagonalsTopRightToDownLeft. -2 to +3: */
    int d;
    int x;
    int y;
    byte arr[4];
    byte arrcount = 0;
    for (d = - (GRIDHEIGHT - MATCHES); d < GRIDWIDTH + 1 - MATCHES; d++) {
        for (i=0; i < GRIDWIDTH - MATCHES; i++) {
            arrcount = 0;
            for (u=0; u < MATCHES; u++) {
                x = i + u;
                y = i + u;
                if (d > 0) {
                    x += d;
                }
                if (d < 0) {
                    y -= d;
                }
                if (x >= GRIDWIDTH || y >= GRIDHEIGHT) {
                    break;
                }
                arr[arrcount] = x * 10 + y;
                arrcount++;
            }
            if (arrcount == MATCHES) {
                for (u=0; u < MATCHES; u++) {
                    comb.c[combination][u] = arr[u];
                }
                combination++;
            }
        }
    }

    /* DiagonalsTopRightToDownLeft: -3 to +2: */
    for (d = - (GRIDWIDTH - MATCHES); d < GRIDHEIGHT - MATCHES + 1; d++) {
        for (i=0; i < GRIDWIDTH - MATCHES; i++) {
            arrcount = 0;
            for (u=0; u < MATCHES; u++) {
                x = GRIDWIDTH - 1 - i - u;
                y = i + u;
                if (d < 0) {
                    x += d;
                }
                if (d > 0) {
                    y += d;
                }
                if (x < 0 || y >= GRIDHEIGHT) {
                    break;
                }
                arr[arrcount] = x * 10 + y;
                arrcount++;
            }
            if (arrcount == MATCHES) {
                for (u=0; u < MATCHES; u++) {
                    comb.c[combination][u] = arr[u];
                }
                combination++;
            }
        }
    }
}

void testCombinations() {
/* Print comb.c[][] 
    byte i;
    byte u;
    byte x = 0;
    for (i=0;i < 69;i++) {
        printf("%d: [", x);
        for (u=0;u < MATCHES;u++) {
            printf("%d", comb.c[i][u]); 
            if (u < MATCHES - 1) {
                printf(", ");
            }
        }
        printf("]\n");
        x++;
    }
*/
}

void initGrid() {
    byte i; byte u;
    for (i=0;i < GRIDHEIGHT;i++) {
        for (u=0;u < GRIDWIDTH;u++) {
            grid.grid[i][u] = 0;
        }
    }
}

void initGame() {
    byte i;
    winner = 0;
    tpos_x = 3;
    tpos_y = -1;
    ccol = RED;
    moves = 0;
    grid_x = 12;
    grid_y = 6;
    outoftiles = 0;
    wincombination = -1;
    initGrid();
    for (i=0; i < USEDKEYS;i++) {
        keys_timeout[i] = 0;
    }
    /* Show Grid: */
    zx_cls(PAPER_WHITE);
    zx_border(INK_RED);
    printAt(grid_y - 3, grid_x - 3, "Win with Four");
    byte y;
    byte x;
    for (y=0; y < GRIDHEIGHT; y++) {
        for (x=0; x < GRIDWIDTH; x++) {
            printBlockAt(grid_y + y, grid_x + x, BLUE, WHITE);
        } 
    }

}

void checkGameWon() {
    byte rc;
    byte yc;
    byte i;
    byte u;
    byte x;
    byte y;
    for (i=0;i< WINCOMBINATIONS;i++) {
        rc = 0;
        yc = 0;
        for (u=0;u< MATCHES;u++) {
            if (comb.c[i][u] >= 10) {
                x = comb.c[i][u] / 10;
            } else {
                x = 0;
            }
            y = comb.c[i][u] - x * 10;
            if (grid.grid[y][x] == RED) {
                rc++;
            }
            if (grid.grid[y][x] == YELLOW) {
                yc++;
            }
            if (rc == MATCHES) {
                winner = RED;
                wincombination = i;
                return;
            }
            if (yc == MATCHES) {
                winner = YELLOW;
                wincombination = i;
                return;
            }
        }
    }
}

void dropTile() {
    if (grid.grid[0][tpos_x] != 0) {
        return;
    }
    byte y;
    int i;
    int count = 0;
    tpos_y = 0;
    for (y=0; y < GRIDHEIGHT; y++) {
        if (grid.grid[y][tpos_x] == 0) {
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, BLUE, WHITE);
            tpos_y = y;
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, BLUE, ccol);
            for (i=0; i<3000;i++){count++;}
        }
    }
    grid.grid[tpos_y][tpos_x] = ccol;
    moves++;
    if (moves >= GRIDWIDTH * GRIDHEIGHT) {
        outoftiles = 1;
        return;
    } 
    checkGameWon();
    if (winner > 0) {
        return;
    }
    tpos_y = -1;
    if (ccol == RED) {
        ccol = YELLOW;
        return;
    }
    if (ccol == YELLOW) {
        ccol = RED;
        return;
    }
}

void showWinner(byte winner_printed, int flashvar) {
    if (winner_printed == 0) {
        if (winner == RED) {
            printAt(grid_y + 9, grid_x - 2, "Red has won!");
        }
        if (winner == YELLOW) {
            printAt(grid_y + 9, grid_x - 3, "Yellow has won!");
        }
    }
    byte u;
    byte x;
    byte y;
    for (u=0; u < MATCHES;u++) {
        x = comb.c[wincombination][u] / 10;
        y = comb.c[wincombination][u] - x * 10;
        if (winner_printed == 0) {
            printBlockAt(grid_y + y, grid_x + x, BLUE, MAGENTA);
        }
        if (flashvar == 0) {
            setColour(grid_x + x, grid_y + y, MAGENTA, BLUE);
        }
        if (flashvar == FLASHDELAY / 2) {
            setColour(grid_x + x, grid_y + y, BLUE, MAGENTA);
        }
    }
}

void checkKeyboard() {
    byte i;
    for (i=0; i < USEDKEYS;i++) {
        if (keys_before[i] == -1 && keys_now[i] == -1) {
            keys_timeout[i]++;
        }
        if (keys_before[i] == -1 && keys_now[i] == 0) {
            keys_timeout[i] = 0;
        }
        keys_before[i] = keys_now[i];
    };
    keys_now[0] = in_key_pressed(IN_KEY_SCANCODE_q);
    keys_now[1] = in_key_pressed(IN_KEY_SCANCODE_w);
    keys_now[2] = in_key_pressed(IN_KEY_SCANCODE_SPACE);
    keys_now[3] = in_key_pressed(IN_KEY_SCANCODE_r);
    keys_now[4] = in_key_pressed(IN_KEY_SCANCODE_e);
    keys_now[5] = in_key_pressed(IN_KEY_SCANCODE_a);

}

byte checkKey(byte key) {
    if (keys_now[key] == -1 && keys_before[key] == 0) {
        return 1;
    }
    byte i;
    if (keys_now[key] == -1 && keys_timeout[key] > 250) {
        for (i=0;i < USEDKEYS; i++) {
            keys_timeout[i] = 0;
        }
        return 1;
    }
    return 0;
}

void clearTile() {
    printBlockAt(grid_y + tpos_y, grid_x + tpos_x, WHITE, WHITE);
}

int main() {
    initCombinations();
    initGame();
    byte running = 1;
    byte winner_printed = 0;
    int flashvar = 0;
    byte draw_printed = 0;
    while (running) {
        checkKeyboard();
        if (! winner && ! outoftiles) {
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, WHITE, ccol);
            if (checkKey(KEY_q) && tpos_x > 0) {
                clearTile();
                tpos_x--;
            }
            if (checkKey(KEY_w) && tpos_x < 6) {
                clearTile();
                tpos_x++;
            }
            if (checkKey(KEY_space)) {
                clearTile();
                dropTile();
            }
        }
        if (winner) {
            showWinner(winner_printed, flashvar);
            winner_printed = 1;
            flashvar++;
            if (flashvar == FLASHDELAY) {
                flashvar = 0;
            }
        }
        if (outoftiles && draw_printed == 0) {
            printAt(grid_y + 9, grid_x - 9, "The Game ended in a Draw!");
            draw_printed = 1;
        }
        if (winner || outoftiles) {
            if (winner_printed == 0 || draw_printed == 0) {
                printAt(grid_y + 11, grid_x - 8, "Press 'r' to play again.");
                draw_printed = 1;
            }
        }
        if (checkKey(KEY_r)) {
            initGame();
        }
        if (checkKey(KEY_e) || checkKey(KEY_a)) {
            zx_cls(PAPER_WHITE);
            zx_border(INK_WHITE);
            running = 0;
        }
    }
    return 0;
}
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: C on the Spectrum (z88dk)

Post by dfzx »

FFoulkes wrote: Fri Mar 15, 2019 5:45 pm Fixed a few bugs, flashing winning tiles and better keyboard I/O (couldn't edit previous posting any more):
Yep, that seems to work. :) Unless you play it a second time in which case the winner message doesn't appear. :P

Good to see someone persevering with C. There aren't enough of us!
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
FFoulkes
Microbot
Posts: 161
Joined: Thu Feb 07, 2019 2:42 pm

Re: C on the Spectrum (z88dk)

Post by FFoulkes »

dfzx wrote: Fri Mar 15, 2019 6:05 pmYep, that seems to work. :) Unless you play it a second time in which case the winner message doesn't appear. :P
Thanks for the feedback! Seems, I didn't expect anyone to play it a second time. :lol:
As I can't edit the posting above (again), here just for the record once more the fixed version:

Code: Select all

#include <arch/zx.h>
#include <input.h>

/*  winwithfour.c, 1.0:

    Compile with: 

    zcc +zx -vn -startup=0 -clib=sdcc_iy -zorg=32768 winwithfour.c -o winwithfour -create-app

    A little game inspired by a very good Z80 Assembly tutorial on Youtube).

    Copyright 2019, Forum-name: Major Percival Ffoulkes, GNU GPL v.2,

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

typedef unsigned char byte;

enum colours { BLACK, BLUE, RED, MAGENTA, GREEN, CYAN, YELLOW, WHITE };
enum keys { KEY_q, KEY_w, KEY_space, KEY_r, KEY_e, KEY_a };

#define GRIDWIDTH 7
#define GRIDHEIGHT 6
#define MATCHES 4
#define WINCOMBINATIONS 69
#define FLASHDELAY 400
#define USEDKEYS  6

struct Grid {
    byte grid[GRIDHEIGHT][GRIDWIDTH];
};

typedef struct Grid Grid;
Grid grid;

struct Combinations {
    byte c[WINCOMBINATIONS][MATCHES];
};

typedef struct Combinations Combinations;
Combinations comb;

byte tpos_x;
int tpos_y;
byte ccol;
byte moves;
byte udg[] = {0, 24, 60, 126, 126, 60, 24, 0};
byte winner;
byte grid_x;
byte grid_y;
int wincombination;
byte outoftiles;
byte winner_printed;
byte draw_printed;
int keys_now[USEDKEYS];
int keys_before[USEDKEYS];
int keys_timeout[USEDKEYS];

void setColour(byte x, byte y, byte paper, byte ink) {
    byte *p = zx_cxy2aaddr(x, y); 
    *p = 8 * paper + ink;
}

void printCharAt(byte x, byte y, byte c) {
   byte *p;
   byte *dat;
   long int temp;
   byte i;
   temp = 15360 + c * 8;
   dat = (byte *) temp;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = *dat;
      dat++;
      p += 256;
   }
}

void printBlockAt(byte y, byte x, byte paper, byte ink) {
   setColour(x, y, paper, ink);
   byte i;
   byte *p;
   p = zx_cxy2saddr(x, y);
   for (i = 0; i < 8; ++i) {
      *p = udg[i];
      p += 256;
   }
}

byte my_strlen(byte *mystring) {
    byte x = 0;
    while (*mystring != '\0') {
        x++;
        mystring++;
    }
    return x;
}


byte printAt(byte y, byte x, byte *mystring) {
    byte slen = my_strlen(mystring);
    byte i;
    for (i=0; i < slen; i++) {
        printCharAt(x, y, mystring[i]);
        x++;
    }
    return 0;
}

void initCombinations() {
    byte combination = 0;
    byte row;
    byte column;
    byte i;
    byte u;

    /* Horizontals: */
    for (row=0; row < GRIDHEIGHT; row++) {
        for (column=0; column < GRIDWIDTH + 1 - MATCHES; column++) {
            for (u=0; u < MATCHES; u++) {
                comb.c[combination][u] = (u + column) * 10 + row;
            }
            combination++;
        }
    }

    /* Verticals: */
    for (column=0; column < GRIDWIDTH; column++) {
        for (row=0; row < GRIDHEIGHT + 1 - MATCHES; row++) {
            for (u=0; u < MATCHES; u++) {
                comb.c[combination][u] = column * 10 + u + row;
            }
            combination++;
        }
    }

    /* DiagonalsTopRightToDownLeft. -2 to +3: */
    int d;
    int x;
    int y;
    byte arr[4];
    byte arrcount = 0;
    for (d = - (GRIDHEIGHT - MATCHES); d < GRIDWIDTH + 1 - MATCHES; d++) {
        for (i=0; i < GRIDWIDTH - MATCHES; i++) {
            arrcount = 0;
            for (u=0; u < MATCHES; u++) {
                x = i + u;
                y = i + u;
                if (d > 0) {
                    x += d;
                }
                if (d < 0) {
                    y -= d;
                }
                if (x >= GRIDWIDTH || y >= GRIDHEIGHT) {
                    break;
                }
                arr[arrcount] = x * 10 + y;
                arrcount++;
            }
            if (arrcount == MATCHES) {
                for (u=0; u < MATCHES; u++) {
                    comb.c[combination][u] = arr[u];
                }
                combination++;
            }
        }
    }

    /* DiagonalsTopRightToDownLeft: -3 to +2: */
    for (d = - (GRIDWIDTH - MATCHES); d < GRIDHEIGHT - MATCHES + 1; d++) {
        for (i=0; i < GRIDWIDTH - MATCHES; i++) {
            arrcount = 0;
            for (u=0; u < MATCHES; u++) {
                x = GRIDWIDTH - 1 - i - u;
                y = i + u;
                if (d < 0) {
                    x += d;
                }
                if (d > 0) {
                    y += d;
                }
                if (x < 0 || y >= GRIDHEIGHT) {
                    break;
                }
                arr[arrcount] = x * 10 + y;
                arrcount++;
            }
            if (arrcount == MATCHES) {
                for (u=0; u < MATCHES; u++) {
                    comb.c[combination][u] = arr[u];
                }
                combination++;
            }
        }
    }
}

void testCombinations() {
/* Print comb.c[][] 
    byte i;
    byte u;
    byte x = 0;
    for (i=0;i < 69;i++) {
        printf("%d: [", x);
        for (u=0;u < MATCHES;u++) {
            printf("%d", comb.c[i][u]); 
            if (u < MATCHES - 1) {
                printf(", ");
            }
        }
        printf("]\n");
        x++;
    }
*/
}

void initGrid() {
    byte i; byte u;
    for (i=0;i < GRIDHEIGHT;i++) {
        for (u=0;u < GRIDWIDTH;u++) {
            grid.grid[i][u] = 0;
        }
    }
}

void initGame() {
    byte i;
    winner = 0;
    tpos_x = 3;
    tpos_y = -1;
    ccol = RED;
    moves = 0;
    grid_x = 12;
    grid_y = 6;
    outoftiles = 0;
    winner_printed = 0;
    draw_printed = 0;
    wincombination = -1;
    initGrid();
    for (i=0; i < USEDKEYS;i++) {
        keys_timeout[i] = 0;
    }
    /* Show Grid: */
    zx_cls(PAPER_WHITE);
    zx_border(INK_RED);
    printAt(grid_y - 3, grid_x - 3, "Win with Four");
    byte y;
    byte x;
    for (y=0; y < GRIDHEIGHT; y++) {
        for (x=0; x < GRIDWIDTH; x++) {
            printBlockAt(grid_y + y, grid_x + x, BLUE, WHITE);
        } 
    }

}

void checkGameWon() {
    byte rc;
    byte yc;
    byte i;
    byte u;
    byte x;
    byte y;
    for (i=0;i< WINCOMBINATIONS;i++) {
        rc = 0;
        yc = 0;
        for (u=0;u< MATCHES;u++) {
            if (comb.c[i][u] >= 10) {
                x = comb.c[i][u] / 10;
            } else {
                x = 0;
            }
            y = comb.c[i][u] - x * 10;
            if (grid.grid[y][x] == RED) {
                rc++;
            }
            if (grid.grid[y][x] == YELLOW) {
                yc++;
            }
            if (rc == MATCHES) {
                winner = RED;
                wincombination = i;
                return;
            }
            if (yc == MATCHES) {
                winner = YELLOW;
                wincombination = i;
                return;
            }
        }
    }
}

void dropTile() {
    if (grid.grid[0][tpos_x] != 0) {
        return;
    }
    byte y;
    int i;
    int count = 0;
    tpos_y = 0;
    for (y=0; y < GRIDHEIGHT; y++) {
        if (grid.grid[y][tpos_x] == 0) {
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, BLUE, WHITE);
            tpos_y = y;
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, BLUE, ccol);
            for (i=0; i<3000;i++){count++;}
        }
    }
    grid.grid[tpos_y][tpos_x] = ccol;
    moves++;
    if (moves >= GRIDWIDTH * GRIDHEIGHT) {
        outoftiles = 1;
        return;
    } 
    checkGameWon();
    if (winner > 0) {
        return;
    }
    tpos_y = -1;
    if (ccol == RED) {
        ccol = YELLOW;
        return;
    }
    if (ccol == YELLOW) {
        ccol = RED;
        return;
    }
}

void showWinner() {
    if (winner == RED) {
        printAt(grid_y + 9, grid_x - 2, "Red has won!");
    }
    if (winner == YELLOW) {
        printAt(grid_y + 9, grid_x - 3, "Yellow has won!");
    }
}

void showWinnerTiles(int flashvar) {
    byte u;
    byte x;
    byte y;
    for (u=0; u < MATCHES;u++) {
        x = comb.c[wincombination][u] / 10;
        y = comb.c[wincombination][u] - x * 10;
        if (winner_printed == 0) {
            printBlockAt(grid_y + y, grid_x + x, BLUE, MAGENTA);
        }
        if (flashvar == 0) {
            setColour(grid_x + x, grid_y + y, MAGENTA, BLUE);
        }
        if (flashvar == FLASHDELAY / 2) {
            setColour(grid_x + x, grid_y + y, BLUE, MAGENTA);
        }
    }
}

void checkKeyboard() {
    byte i;
    for (i=0; i < USEDKEYS;i++) {
        if (keys_before[i] == -1 && keys_now[i] == -1) {
            keys_timeout[i]++;
        }
        if (keys_before[i] == -1 && keys_now[i] == 0) {
            keys_timeout[i] = 0;
        }
        keys_before[i] = keys_now[i];
    };
    keys_now[0] = in_key_pressed(IN_KEY_SCANCODE_q);
    keys_now[1] = in_key_pressed(IN_KEY_SCANCODE_w);
    keys_now[2] = in_key_pressed(IN_KEY_SCANCODE_SPACE);
    keys_now[3] = in_key_pressed(IN_KEY_SCANCODE_r);
    keys_now[4] = in_key_pressed(IN_KEY_SCANCODE_e);
    keys_now[5] = in_key_pressed(IN_KEY_SCANCODE_a);

}

byte checkKey(byte key) {
    if (keys_now[key] == -1 && keys_before[key] == 0) {
        return 1;
    }
    byte i;
    if (keys_now[key] == -1 && keys_timeout[key] > 250) {
        for (i=0;i < USEDKEYS; i++) {
            keys_timeout[i] = 0;
        }
        return 1;
    }
    return 0;
}

void clearTile() {
    printBlockAt(grid_y + tpos_y, grid_x + tpos_x, WHITE, WHITE);
}

int main() {
    initCombinations();
    initGame();
    byte running = 1;
    int flashvar = 0;
    while (running) {
        checkKeyboard();
        if (! winner && ! outoftiles) {
            printBlockAt(grid_y + tpos_y, grid_x + tpos_x, WHITE, ccol);
            if (checkKey(KEY_q) && tpos_x > 0) {
                clearTile();
                tpos_x--;
            }
            if (checkKey(KEY_w) && tpos_x < 6) {
                clearTile();
                tpos_x++;
            }
            if (checkKey(KEY_space)) {
                clearTile();
                dropTile();
            }
        }
        if (winner && winner_printed == 0) {
            showWinner();
            printAt(grid_y + 11, grid_x - 8, "Press 'r' to play again.");
            winner_printed = 1;
        }

        if (winner) {
            showWinnerTiles(flashvar);
            flashvar++;
            if (flashvar == FLASHDELAY) {
                flashvar = 0;
            }
        }
        if (outoftiles && draw_printed == 0) {
            printAt(grid_y + 9, grid_x - 9, "The Game ended in a Draw!");
            printAt(grid_y + 11, grid_x - 8, "Press 'r' to play again.");
            draw_printed = 1;
        }

        if (checkKey(KEY_r)) {
            initGame();
        }
        if (checkKey(KEY_e) || checkKey(KEY_a)) {
            zx_cls(PAPER_WHITE);
            zx_border(INK_WHITE);
            running = 0;
        }
    }
    return 0;
}
When I tried to change the structs, that just hold two-dimensional arrays to, well, global two-dimensional arrays, the program still worked, but was rather slow. Are structs stored in different memory areas than global arrays?
ZxSpence
Dizzy
Posts: 58
Joined: Sat Mar 16, 2019 7:29 am

Re: C on the Spectrum (z88dk)

Post by ZxSpence »

It's worth pointing out that a two dimensional array is just a one dimensional array. You just index the row by X columns. However as connect 4 pieces only ever go down it's better to flip the model 90 degrees so that the columns are modelled but the rows are not. As the calculation should occur for 4 in a row once all gravity effects have been applied the checks for proximity are best modelled as you would with chess move checks, to whit recursive allowing an early exit.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: C on the Spectrum (z88dk)

Post by dfzx »

FFoulkes wrote: Fri Mar 15, 2019 9:27 pm When I tried to change the structs, that just hold two-dimensional arrays to, well, global two-dimensional arrays, the program still worked, but was rather slow. Are structs stored in different memory areas than global arrays?
No, C is much simpler than BASIC or any other high level language. There's no concept of faster or slower memory areas (other than the Spectrum's concept of contended memory as imposed by hardware, irrelevant in this example because you're only using 0x8000 upwards which is outside the contended range).

In C when your code stores a byte in memory the compiler arranges the Z80 instructions to put that value into a memory location; when your code reads it back again the compiler arranges the Z80 instructions to do so. This is done very efficiently so there's only likely to be 2 or 3 such Z80 instructions per memory access in the compiled program. It won't be quite a efficient as a hand crafted assembly language program where the programmer can think about the exact best approach, but it'll be close.

If you change the data structures in the C you can guide the compiler to producing better code, at least to some extent. Likewise, if you don't yet understand exactly how the compiler works, you can also end up "guiding" the compiler to produce less efficient code. One of the fun things about C on the Z80 is that the compiler's Z80 output is quite readable, so you can have a look at what it's doing, spot the bottlenecks it might be introducing and learn how to guide it to improve things. All a bit down the road for you yet though. :)

Mind you, I'd be surprised if you managed to change your little game's source code to influence the compiler such that it made it noticeably slower. What did you do? If you can reproduce it try adding:

Code: Select all

-SO3 --max-allocs-per-node200000
into your compile line. Those flags turn on the optimiser, which is slow but quite efficient. See if that makes a difference.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: C on the Spectrum (z88dk)

Post by dfzx »

ZxSpence wrote: Sat Mar 16, 2019 8:30 am It's worth pointing out that a two dimensional array is just a one dimensional array. You just index the row by X columns. However as connect 4 pieces only ever go down it's better to flip the model 90 degrees so that the columns are modelled but the rows are not. As the calculation should occur for 4 in a row once all gravity effects have been applied the checks for proximity are best modelled as you would with chess move checks, to whit recursive allowing an early exit.
I think there might be a bit of room for optimisation in his implementation. :) The code isn't easy to read but as far as I can see he's pre-populating an array with the 69 winning patterns then doing a comparison of the current board against that data set each move. So, perhaps not as advanced as what's in your head, but a decent exercise for a C beginner.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
ZxSpence
Dizzy
Posts: 58
Joined: Sat Mar 16, 2019 7:29 am

Re: C on the Spectrum (z88dk)

Post by ZxSpence »

Fair enough. Any chance the memory for globals ends up in a contended bank?
AndyC
Dynamite Dan
Posts: 1388
Joined: Mon Nov 13, 2017 5:12 am

Re: C on the Spectrum (z88dk)

Post by AndyC »

I can't speak to z88dk specifically, but as a general rule of thumb it's harder for compilers to optimize global objects as it requires reasoning over a much larger block of code to ensure the output remains valid.
Post Reply