C on the Spectrum (z88dk)

The place for codemasters or beginners to talk about programming any language for the Spectrum.
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.
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 1:17 pm Fair enough. Any chance the memory for globals ends up in a contended bank?
No. His compile line ORGs the program at 0x8000, so the program starts there, then the DATA section goes directly above it, then the BSS section goes directly above that. Nothing will go in the lower contended bank. C doesn't know anything about switched bank memory on the 128K machines, and although there's library support for it you very much need to work to make it happen. It can't happen by chance. :)
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 »

AndyC wrote: Sat Mar 16, 2019 3:07 pm 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.
Yes indeed. :)

z88dk allows you to choose between 2 C compilers. The more modern one is a modified SDCC, and that has relatively sophisticated static analysis. It works hard to allocate registers and static locations efficiently over large code blocks. Even so, the most efficient approach is to keep code blocks tight so they only use as many variables as the compiler can arrange in the Z80's registers.
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.
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: C on the Spectrum (z88dk)

Post by Alcoholics Anonymous »

AndyC wrote: Sat Mar 16, 2019 3:07 pm 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.
The z80 is a special case though - it so happens that it is often faster to load and store from absolute memory addresses instead of loading and storing to local variables on the stack. Using the stack for local vars only saves time if the compiler is able to hold those values in registers as long as possible and then have the option not to write to memory if not needed. For reasoning about static variables, zsdcc does distinguish between volatile and non-volatile memory but I'm not sure how well that is obeyed.

zsdcc will also try to allocate to registers to avoid writing values to memory if it can. It's not terrible at it but it can be befuddled by sub-optimal C code and results can vary with C coding style.
ZxSpence wrote: Sat Mar 16, 2019 1:17 pm Fair enough. Any chance the memory for globals ends up in a contended bank?
dfzx wrote: Sat Mar 16, 2019 4:27 pm C doesn't know anything about switched bank memory on the 128K machines, and although there's library support for it you very much need to work to make it happen. It can't happen by chance. :)
The humans programming do though :)

The compiler gives you (the C or asm programmer) total control over where things are placed in memory. The toolchain is a modern one and it works by defining a memory map composed of sections into which the linker distributes code and data that the programmer has placed into specific sections.

For the 128k spectrum, the toolchain is fully aware of banked memory and all you have to do to put stuff into banks is assign your c code or asm code into a bank. You can also define your own sections with their own org and put stuff into those. The appmake generator in z88dk understands these sections and can generate an SNA automatically using them or you can go to things like raw binaries for the banks. As a byproduct of adding the zx next support, the zx type can also generate dot commands for divmmc interfaces which has a separate memory space in divmmc memory.

The default memory map for the spectrum target collects everything not placed into a BANK section into the "main binary" which is assumed to be destined for the usual start-up spectrum memory map, that is banks 5,2,0. The main binary is composed of the CODE,DATA,BSS sections ORGed at the user defined CODE origin (32768 by default).
Last edited by Alcoholics Anonymous on Mon Apr 22, 2019 6:16 am, edited 3 times in total.
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: C on the Spectrum (z88dk)

Post by Alcoholics Anonymous »

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.
As a 2d array the program must still perform a multiplication and addition to index to the chosen entry. If the numbers are nice, the multiplication may be turned into a bunch of shifts and adds so the penalty is not too bad. A manual 2d array where the programmer indexes into a 1d array with an actual multiply explicit in the code may not be optimized as often; I think the fact the compiler knows it's a 2d array means it will look for a nice shift-add solution for the entire index operation.

But another faster 2d array type is the jagged array which is an array of arrays. The main 1d array is an array of pointers so arr[ i ] returns another array. Then the second index is a 1d offset so arr[ i ][j] turns into a fast operation since arr[ i ] is reading a 16-bit value from an array of 16-bit values which means the offset is i*2 which is fast.
ZxSpence
Dizzy
Posts: 58
Joined: Sat Mar 16, 2019 7:29 am

Re: C on the Spectrum (z88dk)

Post by ZxSpence »

You don't need to multiply.
Post Reply