Jump to content
IGNORED

cc7800, a C compiler dedicated to the Atari 7800


bsteux

Recommended Posts

Hi,

I'm pleased to announce the availability of cc7800, a C compiler (subset of C indeed) dedicated to the Atari 7800, available right now at https://github.com/steux/cc7800. It's a preliminary release (v0.1), but it's functional. I could compile a few examples and run them on my old PAL Atari 7800 (on a Concerto cart). The main limitation today is the poor support of the silly memory map of the 7800 (I still need to manage the shadowing of the zeropage and the stack in the middle of the RAM). What's nice is the "automatic" support of memory scattering and holey DMA (yet another quirk of the 7800). Another limitation is the lack of examples and support libraries (well, at the moment, you have to work your Maria display lines yourself...). I'm working on it, so stay tuned !

In the mean time, here is a sprite display example (you can move it with the joystick and it's PAL and NTSC compatilble) :

 

#include "prosystem.h"

char i;
unsigned char X, Y;
unsigned char xpos, ypos;
char *dlpnt;

holeydma scattered(16, 1) char sprite[16] = { 0x3c, 0x3c, 0x42, 0x42, 0x99, 0x99, 0xa5, 0xa5, 0x81, 0x81, 0xa5, 0xa5, 0x42, 0x42, 0x3c, 0x3c };
ramchip char dl0[64], dl1[64], dl2[64], dl3[64], dl4[64], dl5[64], dl6[64], dl7[64], dl8[64], dl9[64], dl10[64], dl11[64];
#ifdef PAL
#define DLL_ARRAY_SIZE 15 
#define YMAX (240 - 16)
ramchip char dl12[64], dl13[64], dl14[64];
const char *dls[DLL_ARRAY_SIZE] = { dl0, dl1, dl2, dl3, dl4, dl5, dl6, dl7, dl8, dl9, dl10, dl11, dl12, dl13, dl14 };
#else
#define DLL_ARRAY_SIZE 12 
#define YMAX (192 - 16)
const char *dls[DLL_ARRAY_SIZE] = { dl0, dl1, dl2, dl3, dl4, dl5, dl6, dl7, dl8, dl9, dl10, dl11 };
#endif
ramchip char dll[(DLL_ARRAY_SIZE + 4) * 3];
char dlend[DLL_ARRAY_SIZE];

void main()
{
    // Build DLL
    // 25 blank lines
    dll[X = 0] = 0x4f;  // 16 lines
    dll[++X] = 0x21; // 0x2100 = Blank DL
    dll[++X] = 0x00;
    dll[++X] = 0x48; // 9 lines
    dll[++X] = 0x21; // 0x2100 = Blank DL
    dll[++X] = 0x00;
    // 192 lines divided into 12 regions
    for (Y = 0; Y != DLL_ARRAY_SIZE; Y++) {
        dll[++X] = 0x4f; // 16 lines
        dll[++X] = dls[Y] >> 8; // High address
        dll[++X] = dls[Y]; // Low address
    }
    // 26 blank lines
    dll[++X] = 0x4f; // 16 lines
    dll[++X] = 0x21; // 0x2100 = Blank DL
    dll[++X] = 0x00;
    dll[++X] = 0x49; // 10 lines
    dll[++X] = 0x21; // 0x2100 = Blank DL
    dll[++X] = 0x00;

    // Setup Maria registers
    *DPPH = dll >> 8;
    *DPPL = dll;
    *P0C1 = 0x18; // Setup Palette 0
    *P0C2 = 0x38;
    *P0C3 = 0x58;
    *CTRL = 0x43; // Enable DMA
    *CTLSWA = 0; // Setup ports to read mode
    *CTLSWB = 0;

    xpos = 64;
    ypos = 64;

    // Main loop
    do {

        while (!(*MSTAT & 0x80)); // Wait for VBLANK

        if (!(*SWCHA & 0x80)) { // Pushed right ?
            xpos++; // Move right
        }
        if (!(*SWCHA & 0x40)) { // Pushed left ?
            xpos--;
        }
        if (!(*SWCHA & 0x20)) { // Pushed down ?
            if (ypos != YMAX) ypos++;
        }
        if (!(*SWCHA & 0x10)) { // Pushed up ?
            if (ypos != 0) ypos--;
        }

        // Reset DL ends
        for (X = DLL_ARRAY_SIZE - 1; X >= 0; X--) {
            dlend[X] = 0;
        }

        // Build DL entries
        X = ypos >> 4;
        dlpnt = dls[X];

        // Create DL entry for upper part of sprite
        Y = dlend[X];
        dlpnt[Y++] = sprite; // Low byte of data address
        dlpnt[Y++] = 0x40; // Mode 320x1
        i = ypos & 0x0f;
        dlpnt[Y++] = (sprite >> 8) | i;
        dlpnt[Y++] = 0x1f; // Palette 0, 1 byte wide  
        dlpnt[Y++] = xpos; // Horizontal position
        dlend[X] = Y;

        if (ypos & 0x0f) { // Is the sprite lying on another region ?
            X++;
            dlpnt = dls[X]; // Point to the next region
            Y = dlend[X];
            dlpnt[Y++] = sprite; // Low byte of data address
            dlpnt[Y++] = 0x40; // Mode 320x1
            dlpnt[Y++] = ((sprite - 0x1000) >> 8) | i;
            dlpnt[Y++] = 0x1f; // Palette 0, 1 byte wide  
            dlpnt[Y++] = xpos; // Horizontal position
            dlend[X] = Y;
        }

        // Add DL end entry on each DL
        for (X = DLL_ARRAY_SIZE - 1; X >= 0; X--) {
            dlpnt = dls[X];
            Y = dlend[X];
            dlpnt[++Y] = 0; 
        }

        // Wait for VBLANK to end
        while (*MSTAT & 0x80);
    } while(1);
}

 

What is interesting is the generated code. Very very close to pure assembly with little to no overhead (which was the primary goal). Well, have fun and please contribute if you find this exciting !

Regards.

  • Like 17
  • Thanks 7
Link to comment
Share on other sites

  • 2 weeks later...

Hi,

Yes, let's see if C can get a little room in between Basic and Assembler on the Atari 7800...

I've made some progress in terms of usability...

- v0.1.2 of cc7800 now correctly handles memory mapping, bankswitching, etc. It's rather complete now.

- I've added a multisprite.h header, with all the necessary functions to ease, well, multisprite display... At the moment, it's quite basic, but at least it's working... Tested on real hardware and a7800 (see the video attached). It doesn't handle scrolling, but includes memory protection and DMA time computation so that glitches can be detected and handled if necessary. It also supports double buffering. At the moment, only 160A mode is supported.

- I've made a program to read and process images in order to generate C sprite code: sprites7800. It's located in my new tools7800 repository (https://github.com/steux/tools7800), and is written in Rust (and thus can run on any platform).

- And I've managed to make an installer for cc7800 for Windows... Here it is in attachment. Please tell me if it's working. I'm only using linux...

 

Here is an example that displays 48 sprites at 50Hz (on my PAL system), with a full tiled background. Quite straightforward to understand for anyone who knows C and has a little bit of MARIA knowledge (the video attached shows the result on my PAL system) :

 

#include "prosystem.h"
#include "multisprite.h"

char i, xpos, ypos;

#define NB_SPRITES 48
ramchip short sp_xpos[NB_SPRITES], sp_ypos[NB_SPRITES];
ramchip char sp_direction[NB_SPRITES];

#ifdef PAL
#define YMAX 240
#else
#define YMAX 192
#endif

const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };

holeydma reversed scattered(16,2) char bb_char1[32] = {
	0x01, 0x00, 0x01, 0x40, 0x0a, 0x94, 0x2a, 0x90, 0x3b, 0xa0, 0xc8, 0xe5, 0xc8, 0xe4, 0xc8, 0xd0,
	0xc8, 0xe5, 0xbb, 0x84, 0x0c, 0x20, 0x2a, 0x90, 0x0e, 0x50, 0x3f, 0x94, 0x3d, 0x68, 0x5d, 0x6a
};
reversed scattered(16,2) char tiles[32] = {
	0x5a, 0x5a, 0x69, 0x69, 0x69, 0x69, 0xa5, 0xa5, 0xa5, 0xa5, 0x96, 0x96, 0x96, 0x96, 0x5a, 0x5a,
	0x5a, 0x5a, 0x69, 0x69, 0x69, 0x69, 0xa5, 0xa5, 0xa5, 0xa5, 0x96, 0x96, 0x96, 0x96, 0x5a, 0x5a
};
const char background[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

void main()
{
    multisprite_init();
    multisprite_set_charbase(tiles);
    
    for (i = 0; i != _MS_DLL_ARRAY_SIZE; i++) {
        // 20 characters (8 pixels wide) on each line, using palette 1 (pink)
        multisprite_display_chars(0, i, background, 20, 1);
    }
    multisprite_save();

    *P0C1 = 0x1c; // Setup Palette 0
    *P0C2 = 0xd5; // Green
    *P0C3 = 0x0f; // White
   
    *P1C1 = 0x65; // Dark pink
    *P1C2 = 0x6B; // Light pink

    // Initialize sprites
    for (ypos = 0, xpos = 0, i = 0, X = 0; X != NB_SPRITES; xpos++, ypos++, X++) {
        sp_xpos[X] = xpos << 8;
        sp_ypos[X] = ypos << 8;
        sp_direction[X] = i++;
        if (i == 24) i = 0;
    }

    // Main loop
    do {
        while (!(*MSTAT & 0x80)); // Wait for VBLANK
        multisprite_flip();
        for (i = 0; i != NB_SPRITES; i++) {
            X = i;
            Y = sp_direction[X];
            sp_xpos[X] += dx[Y];
            sp_ypos[X] += dy[Y];
            xpos = sp_xpos[X] >> 8;
            ypos = sp_ypos[X] >> 8;
            if ((xpos < 5 && (dx[Y] >> 8) < 0) || 
                (xpos >= 150 && (dx[Y] >> 8) >= 0)) {
                sp_direction[X] = horizontal_pingpong[Y];
            }
            if ((ypos < 5 && (dy[Y] >> 8) < 0) || 
                (ypos >= YMAX - 20 && (dy[Y] >> 8) >= 0)) {
                sp_direction[X] = vertical_pingpong[Y];
            }
            multisprite_display_sprite(xpos, ypos, bb_char1, 2, 0); 
        }
    } while(1);
}

The sprites are generated using sprites7800, with the following YAML file as input :

sprite_sheets:
  - image: Bubble Bobble.png
    sprites:
      - name: bb_char1
        top: 0
        left: 0
        width: 16
        holeydma: true
      - name: tiles
        top: 16
        left: 0
        width: 16
        holeydma: false

 

I hope this will give an idea of the possibilities C coding is offering to Atari7800 game developers...

I'll go on making new headers for vertical scrolling, horizontal scrolling, text display and split screen.

See you !

Bubble Bobble.png

cc7800-0.1.2-x86_64.msi

  • Like 9
  • Thanks 1
Link to comment
Share on other sites

Could you add a get_tv() function for finding out if the console is PAL or NTSC.

 

I like to use 224 visible lines for both targets (8 * 28 zones or 16 * 14 zones).

 

Currently get_tv() sets the global variable paldetected (1 = PAL, 0 = NTSC).

 

The reason why paldetected is nice to have is that you can just change the hue value by two steps to almost have the same colours in PAL and NTSC. For music and sound effects you can also just use 50 Hz update rate by dropping each 6th update cycle for NTSC machines.

 

This would give you a wider audience for the game.

 

PS. My personal workhorse for TIA music today is TiaTracker. It exports the asm file in many formats so it should be easy to get to work.

get_tv.s

Link to comment
Share on other sites

23 hours ago, karri said:

The reason why paldetected is nice to have is that you can just change the hue value by two steps to almost have the same colours in PAL and NTSC.

:ponder:

The below conversion typically works best - one step (outside of grayscale):

 

NTSC $0X <--> PAL $0X
NTSC $1X <--> PAL $2X
NTSC $2X <--> PAL $3X
NTSC $3X <--> PAL $4X
NTSC $4X <--> PAL $5X
NTSC $5X <--> PAL $6X
NTSC $6X <--> PAL $7X
NTSC $7X <--> PAL $8X
NTSC $8X <--> PAL $9X
NTSC $9X <--> PAL $AX
NTSC $AX <--> PAL $BX
NTSC $BX <--> PAL $CX
NTSC $CX <--> PAL $DX
NTSC $DX <--> PAL $EX
NTSC $EX <--> PAL $FX

 

NTSC $FX <--> N/A - Best nearest PAL $2X

PAL $1X <--> N/A - Best nearest NTSC $DX

 

The Atari 7800 Color Documentation page on 7800 8BitDev is an excellent resource for this topic.  There are also a slew of examples and discussion under this thread.

 

Spoiler

A conversion process where two steps takes place is in most instances of equating PAL 7800 hue ranges to PAL 2600:

 

PAL 7800 $2x = PAL 2600 $2x
PAL 7800 $3x = PAL 2600 $4x
PAL 7800 $4x = PAL 2600 $6x
PAL 7800 $5x = PAL 2600 $8x
PAL 7800 $6x = PAL 2600 $Ax
PAL 7800 $7x = PAL 2600 $Cx
PAL 7800 $8x = PAL 2600 $Dx
PAL 7800 $9x = PAL 2600 $Bx
PAL 7800 $Ax = PAL 2600 $9x
PAL 7800 $Bx = PAL 2600 $7x
PAL 7800 $Cx = PAL 2600 $5x
PAL 7800 $Dx = PAL 2600 $3x

 

PAL 7800 $0x = PAL 2600 $0x
PAL 7800 $0x = PAL 2600 $1x
PAL 7800 $0x = PAL 2600 $Ex
PAL 7800 $0x = PAL 2600 $Fx

 

There is no need to convert hue ranges for NTSC 7800 to NTSC 2600 as it is the same for the two consoles, though only every other luminance value is available under the 2600.

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

On 4/20/2023 at 3:14 PM, karri said:

Could you add a get_tv() function for finding out if the console is PAL or NTSC.

 

I like to use 224 visible lines for both targets (8 * 28 zones or 16 * 14 zones).

 

Currently get_tv() sets the global variable paldetected (1 = PAL, 0 = NTSC).

 

The reason why paldetected is nice to have is that you can just change the hue value by two steps to almost have the same colours in PAL and NTSC. For music and sound effects you can also just use 50 Hz update rate by dropping each 6th update cycle for PAL machines.

 

This would give you a wider audience for the game.

 

PS. My personal workhorse for TIA music today is TiaTracker. It exports the asm file in many formats so it should be easy to get to work.

get_tv.s 1.05 kB · 1 download

 

This is an excellent suggestion! I was wondering how to cope with PAL and NTSC versions. Yes, using a 16x14 zones to have a "standard" 224 lines picture seems a good way to go, with an added autodetection of TV model.

I've implemented your get_tv function and included it in the header :

 

void multisprite_get_tv()
{
    while (!(*MSTAT & 0x80)); // Wait for VBLANK
    while (*MSTAT & 0x80); // Wait for end of VBLANK

    X = 0;
    do {
        strobe(WSYNC);
        strobe(WSYNC);
        X++;
    } while (!(*MSTAT & 0x80));

    if (X >= 135) _ms_pal_detected = 0xff; 
}

 

This is called by multisprite_init() now, and it seems to work perfectly (tested on a7800)

I've added a macro to process the colors (adding +1 phase shift for NTSC to PAL conversion) :

 

// Macro to convert NTSC colors to PAL colors
// To be used outside of grayscale ($0x) and NTSC $Fx
#define multisprite_color(color) (color + (_ms_pal_detected & 0x10))

 

which doesn't cost much CPU...

The difference in pixel ratio is partially compensated by the availability of wide screen displays. If we use a 4:3 PAL display and a 16:9 NTSC display, the result is looking similar...

Adopted. Thank you !

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Hi,

I've updated cc7800 to v0.2. It now directly produces .a78 files with the included header., making things way simpler for users. It's now running DASM internally as a second stage compiler (this requires DASM to be present in the current directory or found in the system's path). This release also includes a bug fix which prevented cc7800 to run correctly under Windows (due to CRLF issues). Here is the new installer for windows. I'm working now on a conio.h header to add console-like facilities...

cc7800-0.2.0-x86_64.msi

  • Like 6
Link to comment
Share on other sites

21 hours ago, bsteux said:

I'm working now on a conio.h header to add console-like facilities...

This is really nice! For curiosity I created a database parser for turning scottkit adventures into 7800 C-code. The resulting C-code is pretty simple and plain conio support might be enough for compiling the file with your cc7800. It already works on cc65. And for simplicity it creates all the code in one C-file.

 

The "adventure" code mainly uses gotoxy() and cputc(). For clearing the memory or scrolling I prefer memcpy() or memset().

 

Edit: I added a small scottkit adventure. The mini.sck is the source file and the mini.c is what my python script generates of it.

 

Scottkit is a ruby tool that you can get in ruby by "gem install scottkit". You can play text adventures in ruby by "scottkit -p mini.sck". Or dump them to be played by scottfree interpreters by "scottkit -c mini.sck > mini.sco". It is also possible to convert existing adventures back into scottkit source by "scottkit -d piratecove.dat > piratecove.sck".

mini.sck mini.c mini.a78

  • Like 2
Link to comment
Share on other sites

 Create a game of "War", where each player is dealt half of a shuffled deck of cards, and they take turns drawing a card from their deck and comparing it to their opponent's card. The player with the higher card wins that round and takes both cards. The game ends when one player has all the cards.

Here is an example implementation:

```
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define DECK_SIZE 52
#define HAND_SIZE 26

typedef enum {CLUB, DIAMOND, HEART, SPADE} Suit;

typedef struct {
    Suit suit;
    int rank;
} Card;

typedef struct {
    Card cards[HAND_SIZE];
    int size;
} Hand;

int main() {
    // Initialize the deck of cards
    Card deck[DECK_SIZE];
    for (int i = 0; i < DECK_SIZE; i++) {
        deck[i].rank = i % 13 + 1;
        if (i < 13) {
            deck[i].suit = CLUB;
        } else if (i < 26) {
            deck[i].suit = DIAMOND;
        } else if (i < 39) {
            deck[i].suit = HEART;
        } else {
            deck[i].suit = SPADE;
        }
    }

    // Shuffle the deck of cards
    srand(time(NULL));
    for (int i = 0; i < DECK_SIZE; i++) {
        int j = rand() % DECK_SIZE;
        Card temp = deck[i];
        deck[i] = deck[j];
        deck[j] = temp;
    }

    // Deal the cards to the players
    Hand player1, player2;
    player1.size = player2.size = HAND_SIZE;
    for (int i = 0; i < HAND_SIZE; i++) {
        player1.cards[i] = deck[i];
        player2.cards[i] = deck[i + HAND_SIZE];
    }

    // Play the game
    while (player1.size > 0 && player2.size > 0) {
        // Draw a card from each player's hand
        Card card1 = player1.cards[--player1.size];
        Card card2 = player2.cards[--player2.size];

        // Compare the cards
        int result = card1.rank - card2.rank;
        if (result > 0) {
            printf("Player 1 wins the round!\n");
            player1.cards[player1.size++] = card1;
            player1.cards[player1.size++] = card2;
        } else if (result < 0) {
            printf("Player 2 wins the round!\n");
            player2.cards[player2.size++] = card1;
            player2.cards[player2.size++] = card2;
        } else {
            printf("Tie!\n");
            player1.cards[player1.size++] = card1;
            player2.cards[player2.size++] = card2;
        }
    }

    // Declare the winner
    if (player1.size > player2.size) {
        printf("Player 1 wins the game!\n");
    } else {
        printf("Player 2 wins the game!\n");
    }

    return 0;
}
```

Link to comment
Share on other sites

  • 3 weeks later...

Hi,

I've made some progress on the Multisprite library for cc7800. I've added vertical scrolling.

Here is a working example :

#include "prosystem.h"
#include "gfx.h"
#define VERTICAL_SCROLLING
#define _MS_DL_SIZE 64
#define _MS_DL_MALLOC(y) ((y == 6 || y == 7 || y == 8)?_MS_DL_SIZE * 2:_MS_DL_SIZE)
#include "multisprite.h"

char i, counter, xpos, ypos;
char *ptr;
char xchest;

#define NB_SPRITES 32 
ramchip short sp_xpos[NB_SPRITES], sp_ypos[NB_SPRITES];
ramchip char sp_direction[NB_SPRITES];

const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };

const char background[22] = { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 };
#define LTR(x) (((x) - ' ') * 2)
const char hello_world[] = { LTR('H'), LTR('E'), LTR('L'), LTR('L'), LTR('O'), LTR(' '), LTR('W'), LTR('O'), LTR('R'), LTR('L'), LTR('D') };

void main()
{
    counter = 0;

    multisprite_init();
    multisprite_set_charbase(tiles);
   
    // Set up a full background 
    for (counter = 0; counter < _MS_DLL_ARRAY_SIZE; counter++) {
        if (counter & 2) {
            ptr = background + 2;
        } else {
            ptr = background;
        }
        multisprite_display_tiles(0, _MS_DLL_ARRAY_SIZE - 1 - counter, ptr, 20, 1);
    }
    multisprite_save();

    *P0C1 = multisprite_color(0x1c); // Setup Palette 0: Yellow
    *P0C2 = multisprite_color(0xc5); // Green
    *P0C3 = 0x0f; // White
   
    *P1C1 = multisprite_color(0x55); // Dark pink
    *P1C2 = multisprite_color(0x5B); // Light pink

    *P2C1 = multisprite_color(0x32);
    *P2C2 = multisprite_color(0x3D);
    *P2C3 = multisprite_color(0x37);

    *P3C1 = multisprite_color(0x92);
    *P3C2 = multisprite_color(0x97);
    *P3C3 = multisprite_color(0x9D);

    // Initialize sprites
    for (ypos = 0, xpos = 0, i = 0, X = 0; X != NB_SPRITES; xpos++, ypos++, X++) {
        sp_xpos[X] = xpos << 8;
        sp_ypos[X] = ypos << 8;
        sp_direction[X] = i++;
        if (i == 24) i = 0;
    }

    // Main loop
    do {
        // Prepare scrolling data
        if (multisprite_vscroll_buffer_empty()) {
            if (counter & 2) {
                ptr = background + 2;
            } else {
                ptr = background;
            }
            multisprite_vscroll_buffer_tiles(0, ptr, 20, 1);
            multisprite_vscroll_buffer_sprite(xchest, chest, 2, 3);
            xchest += 45;
            counter++;
        }

        multisprite_flip();
        multisprite_vertical_scrolling(1);
        multisprite_reserve_dma(104, sizeof(hello_world), 2);

        for (i = 0; i != NB_SPRITES; i++) {
            X = i;
            Y = sp_direction[X];
            sp_xpos[X] += dx[Y];
            sp_ypos[X] += dy[Y];
            xpos = sp_xpos[X] >> 8;
            ypos = sp_ypos[X] >> 8;
            if ((xpos < 5 && (dx[Y] >> 8) < 0) || 
                (xpos >= 150 && (dx[Y] >> 8) >= 0)) {
                sp_direction[X] = horizontal_pingpong[Y];
            }
            if ((ypos < 5 && (dy[Y] >> 8) < 0) || 
                (ypos >= MS_YMAX - 20 && (dy[Y] >> 8) >= 0)) {
                sp_direction[X] = vertical_pingpong[Y];
            }
            multisprite_display_sprite(xpos, ypos, bb_char1, 2, 0); 
        }
        for (xpos = 40, i = 0; i != sizeof(hello_world); xpos += 8, i++) {
            ptr = chars0 + hello_world[X = i];
            multisprite_display_sprite_fast(xpos, 104, ptr, 2, 2);
        }
    } while(1);
}

 

The result ? See the video. I think it's getting close to what is needed to implement a shm'up.

I'm still working on conio.h for console output. It's starting to work, but it is still not usable in a real program (at the moment it generates a display list for every print).

The next steps are adding horizontal scrolling, support for joystick and sound (TIA and hopefully POKEY), writing some docs, and cc7800 will be ready for making good Atari 7800 games that eat the NES alive.

 

 

  • Like 11
Link to comment
Share on other sites

Along with the example source code, here are :

- The bitmap file for the graphics (including a 3 colors bitmap font)

- The YAML file for the graphics description (so that sprites7800 tool can generate the C code)

- A new windows installer for cc7800 v0.2.2

Bubble Bobble.png

cc7800-0.2.2-x86_64.msi Bubble Bobble.yaml

  • Like 6
Link to comment
Share on other sites

The cc7800 project is going on...

Let's now introduce conio.h, a console I/O (well, only O) to display text. It's not complete, but it supports putch, puts, gotoxy, textcolor, clrscr and delline. The included font is the lowercase C64 font. 128 characters are defined, so there is room for another 128 custom characters. Technically, my design of conio favors 3 goals : KISS (Keep it stupid simple), minimizing the CPU load (i.e. minimizing the number of Display List Headers - 0 when nothing is displayed), and maximizing the possibility to use colors. It's possible to use 8 colors on a line, with some limitations (the algorithm tries to use one DL header per color, and due to Maria design, this results in color priorities (the last color used has display priority over the first used)).

Here is an example of use (a test indeed). The result is the picture in attachment. The "hearth" character is a custom character that is appended automatically by cc7800 to the C64 font (using the scattered keyword, indicating this is a MARIA graphics). Note the use of assert from assert.h, which turns background to red if the assertion is not correct (in my example, I check the the length of Display Lists) :

#include "conio.h"
#include "assert.h"

char i;

reversed scattered(8,1) char special_char[8] = {
    0x66, 0xff, 0xff, 0xff, 0x7e, 0x3c, 0x18, 0x00  
};

void main()
{
    clrscr();
    // Draw a square
    gotoxy(0, 0);
    textcolor(7);
    putch(CONIO_TL_CORNER);
    for (i = 0; i != 20; i++) {
        putch(CONIO_HBAR);
    }
    putch(CONIO_TR_CORNER);
    for (i = 0; i != 8; i++) {
        gotoxy(0, i + 1);
        putch(CONIO_VBAR);
        gotoxy(21, i + 1);
        putch(CONIO_VBAR);
    }
    gotoxy(0, 9);
    putch(CONIO_BL_CORNER);
    for (i = 0; i != 20; i++) {
        putch(CONIO_HBAR);
    }
    putch(CONIO_BR_CORNER);
    // Write some text
    for (i = 0; i != 8; i++) {
        textcolor(i);
        gotoxy(i + 1, i + 1);
        cputs("Hello World!");
    }
    // Long text test
    gotoxy(0, 10);
    cputs("This is a long text that fits in a line.");
    
    gotoxy(10, 11);
    cputs("World!");
    gotoxy(4, 11);
    cputs("Hello");
    
    gotoxy(10, 12);
    cputs("World!");
    gotoxy(4, 12);
    textcolor(4);
    cputs("Hello");

    gotoxy(0, 13);
    for (i = 0; i != 8; i++) {
        textcolor(i);
        putch('!');
    }

    gotoxy(0, 14);
    for (i = 0; i != 8; i++) {
        textcolor(7 - i);
        putch(128); // Special character
    }

    // Unit tests (Display lines length check) :
    assert(_ms_dlend[X = 0] == 5);
    assert(_ms_dlend[X = 1] == 10);
    assert(_ms_dlend[X = 7] == 10);
    assert(_ms_dlend[X = 8] == 5);
    assert(_ms_dlend[X = 9] == 5);
    assert(_ms_dlend[X = 10] == 10);
    assert(_ms_dlend[X = 11] == 5);
    assert(_ms_dlend[X = 12] == 10);
    assert(_ms_dlend[X = 13] == 40);
    assert(_ms_dlend[X = 14] == 40);
    while(1);
}

 

test_conio.png

cc7800-0.2.3-x86_64.msi

  • Like 7
Link to comment
Share on other sites

The next step. Support for DLIs (Display List Interrupts). This allows to execute a (short) function on the fly while Maria is displaying the screen. In the following example, I just change the background color to red on Display List 7 (out of 14), i.e. in the middle of the screen. Note that using DLI (which uses 6502 NMI : Non Maskable Iterrupt), it's necessary to declare an "interrupt" function. This is a new keyword that was added to cc7800 in the new v0.2.4. An example of use (this is examples/test_nmi.c from the cc7800 github repository) :

#include "prosystem.h"
#include "multisprite.h"

char i, xpos, ypos;

#define NB_SPRITES 64 
ramchip short sp_xpos[NB_SPRITES], sp_ypos[NB_SPRITES];
ramchip char sp_direction[NB_SPRITES];

const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };

holeydma reversed scattered(16,2) char bb_char1[32] = {
	0x01, 0x00, 0x01, 0x40, 0x0a, 0x94, 0x2a, 0x90, 0x3b, 0xa0, 0xc8, 0xe5, 0xc8, 0xe4, 0xc8, 0xd0,
	0xc8, 0xe5, 0xbb, 0x84, 0x0c, 0x20, 0x2a, 0x90, 0x0e, 0x50, 0x3f, 0x94, 0x3d, 0x68, 0x5d, 0x6a
};

void interrupt dli()
{
    *BACKGRND = 0x32; 
}

void main()
{
    multisprite_init();

    multisprite_enable_dli(7);
    
    *P0C1 = multisprite_color(0x1c); // Setup Palette 0
    *P0C2 = multisprite_color(0xc5); // Green
    *P0C3 = multisprite_color(0x0f); // White

    // Initialize sprites
    for (ypos = 0, xpos = 0, i = 0, X = 0; X != NB_SPRITES; xpos++, ypos++, X++) {
        sp_xpos[X] = xpos << 8;
        sp_ypos[X] = ypos << 8;
        sp_direction[X] = i++;
        if (i == 24) i = 0;
    }

    // Main loop
    do {
        multisprite_flip();
        *BACKGRND = 0; // Back to black background
        for (i = 0; i != NB_SPRITES; i++) {
            Y = sp_direction[X = i];
            sp_xpos[X] += dx[Y];
            sp_ypos[X] += dy[Y];
            xpos = sp_xpos[X] >> 8;
            ypos = sp_ypos[X] >> 8;
            if ((xpos < 5 && (dx[Y] >> 8) < 0) || 
                (xpos >= 150 && (dx[Y] >> 8) >= 0)) {
                sp_direction[X] = horizontal_pingpong[Y];
            }
            if ((ypos < 5 && (dy[Y] >> 8) < 0) || 
                (ypos >= MS_YMAX - 20 && (dy[Y] >> 8) >= 0)) {
                sp_direction[X] = vertical_pingpong[Y];
            }
            multisprite_display_sprite(xpos, ypos, bb_char1, 2, 0); 
        }
    } while(1);
}

To compile the example, just type : (Make sure DASM is in the current path)

cc7800 -Iheaders examples/test_nmi.c

In attachment, a picture of the result, and the new version of cc7800 installer for Windows.

nmi.png

cc7800-0.2.4-x86_64.msi

  • Like 5
Link to comment
Share on other sites

  • 2 weeks later...

The next step again. I've added 16 bits numbers comparison to cc6502 (the Rust code used as the base for cc7800 and cc2600) and `itoa` function to the cc7800 library. This enables to display a score on top of the scrolling fiel ! I've also added a define (_MS_TOP_SCROLLING_ZONE) which defines, well, the top of the scrolling zone... Here is a new demo : example_score.c, batteries included and still without a single line of assembly code. Attached is a video of the result on my old PAL Atari 7800.

 

#include "stdlib.h"
#include "string.h"
#include "prosystem.h"
#define VERTICAL_SCROLLING
#define _MS_TOP_SCROLLING_ZONE 1
#include "multisprite.h"

char i, counter, xpos, ypos;
char *ptr;
char xchest;

#define NB_SPRITES 32 
ramchip short sp_xpos[NB_SPRITES], sp_ypos[NB_SPRITES];
ramchip char sp_direction[NB_SPRITES];

const signed short dx[24] = {300, 289, 259, 212, 149, 77, 0, -77, -150, -212, -259, -289, -300, -289, -259, -212, -149, -77, 0, 77, 149, 212, 259, 289};
const signed short dy[24] = {0, 124, 240, 339, 415, 463, 480, 463, 415, 339, 240, 124, 0, -124, -239, -339, -415, -463, -480, -463, -415, -339, -240, -124};
const char horizontal_pingpong[24] = { 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13 };
const char vertical_pingpong[24] = { 0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };

const char background[22] = { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 };

holeydma reversed scattered(16,2) char bb_char1[32] = {
	0x01, 0x00, 0x01, 0x40, 0x0a, 0x94, 0x2a, 0x90, 0x3b, 0xa0, 0xc8, 0xe5, 0xc8, 0xe4, 0xc8, 0xd0,
	0xc8, 0xe5, 0xbb, 0x84, 0x0c, 0x20, 0x2a, 0x90, 0x0e, 0x50, 0x3f, 0x94, 0x3d, 0x68, 0x5d, 0x6a
};
reversed scattered(16,4) char tiles[64] = {
	0x5a, 0x5a, 0x95, 0x95, 0x69, 0x69, 0x65, 0x65, 0x69, 0x69, 0x95, 0x95, 0xa5, 0xa5, 0x65, 0x65,
	0xa5, 0xa5, 0xa9, 0xa9, 0x96, 0x96, 0xa6, 0xa6, 0x96, 0x96, 0xa9, 0xa9, 0x5a, 0x5a, 0xa6, 0xa6,
	0x5a, 0x5a, 0x95, 0x95, 0x69, 0x69, 0x65, 0x65, 0x69, 0x69, 0x95, 0x95, 0xa5, 0xa5, 0x65, 0x65,
	0xa5, 0xa5, 0xa9, 0xa9, 0x96, 0x96, 0xa6, 0xa6, 0x96, 0x96, 0xa9, 0xa9, 0x5a, 0x5a, 0xa6, 0xa6
};
#define CHEST_INDEX 4
reversed scattered(16,2) char chest[32] = {
	0x00, 0x00, 0x05, 0x50, 0x2b, 0xf4, 0x96, 0xfc, 0x69, 0xbd, 0xbe, 0x6a, 0x55, 0x55, 0xaa, 0xbf,
	0xd5, 0xd5, 0xd9, 0xb7, 0xef, 0x9d, 0xef, 0x9d, 0xef, 0x9d, 0xdd, 0x9d, 0xd5, 0xf7, 0xff, 0x95
};
#define DIGITS_INDEX 6
reversed scattered(16,20) char digits[320] = {
	0x15, 0x40, 0x05, 0x00, 0x15, 0x40, 0x15, 0x40, 0x01, 0x40, 0x55, 0x50, 0x15, 0x40, 0x55, 0x50, 0x15, 0x40, 0x15, 0x40,
	0x15, 0x40, 0x05, 0x00, 0x15, 0x40, 0x15, 0x40, 0x01, 0x40, 0x55, 0x50, 0x15, 0x40, 0x55, 0x50, 0x15, 0x40, 0x15, 0x40,
	0x5a, 0x70, 0x0d, 0x00, 0x7a, 0xd0, 0x7a, 0x50, 0x07, 0x40, 0xda, 0xa0, 0x5a, 0xd0, 0x7a, 0xd0, 0x7a, 0x50, 0xda, 0x70,
	0x70, 0xd0, 0x05, 0x00, 0x50, 0x50, 0x50, 0xd0, 0x05, 0x40, 0x50, 0x00, 0xd0, 0x70, 0x50, 0x50, 0x50, 0xd0, 0x70, 0x50,
	0x51, 0x70, 0x1d, 0x00, 0xa0, 0xd0, 0xa0, 0xd0, 0x37, 0x40, 0xd7, 0x40, 0xd0, 0xa0, 0xa1, 0xe0, 0x70, 0xd0, 0x70, 0x50,
	0xd3, 0x50, 0x37, 0x00, 0x00, 0x70, 0x00, 0x70, 0x1d, 0xc0, 0x75, 0xc0, 0x70, 0x00, 0x03, 0x40, 0xd0, 0x70, 0x50, 0xd0,
	0x76, 0xd0, 0x27, 0x00, 0x03, 0x60, 0x07, 0x60, 0x6b, 0x40, 0xaa, 0x70, 0x77, 0x40, 0x07, 0x80, 0xb7, 0x60, 0xb7, 0x70,
	0xdc, 0x70, 0x07, 0x00, 0x03, 0x40, 0x07, 0x40, 0x43, 0x40, 0x00, 0x70, 0x77, 0x40, 0x07, 0x00, 0x37, 0x40, 0x37, 0x70,
	0xd8, 0xf0, 0x0d, 0x00, 0x36, 0x80, 0x0a, 0x70, 0xf7, 0xf0, 0x00, 0x70, 0x7a, 0xf0, 0x07, 0x00, 0x7a, 0x70, 0x2a, 0xf0,
	0xf0, 0x70, 0x0f, 0x00, 0x1c, 0x00, 0x00, 0xd0, 0x7d, 0xd0, 0x00, 0xf0, 0xd0, 0x70, 0x0f, 0x00, 0xf0, 0xd0, 0x00, 0xd0,
	0xf0, 0xd0, 0x0f, 0x00, 0xf8, 0x00, 0xf0, 0xf0, 0xab, 0xe0, 0xf0, 0xf0, 0xf0, 0x70, 0x0f, 0x00, 0xf0, 0xf0, 0x70, 0xf0,
	0xd0, 0xf0, 0x0f, 0x00, 0xd0, 0x00, 0xf0, 0x70, 0x03, 0xc0, 0xd0, 0x70, 0x70, 0xf0, 0x0d, 0x00, 0x70, 0x70, 0xf0, 0xd0,
	0xbd, 0xe0, 0xff, 0xf0, 0xff, 0xf0, 0xbf, 0xe0, 0x03, 0xc0, 0xbf, 0xe0, 0xbf, 0xe0, 0x0f, 0x00, 0xbf, 0xe0, 0xbf, 0xe0,
	0x3f, 0xc0, 0xff, 0xf0, 0xff, 0xf0, 0x3f, 0xc0, 0x03, 0xc0, 0x3f, 0xc0, 0x3f, 0xc0, 0x0f, 0x00, 0x3f, 0xc0, 0x3f, 0xc0,
	0x2a, 0x80, 0xaa, 0xa0, 0xaa, 0xa0, 0x2a, 0x80, 0x02, 0x80, 0x2a, 0x80, 0x2a, 0x80, 0x0a, 0x00, 0x2a, 0x80, 0x2a, 0x80,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

ramchip int score;
ramchip char display_score_str[5];
ramchip char display_score_ascii[6];

void display_score_update()
{
    itoa(score, display_score_ascii, 10);
    Y = strlen(display_score_ascii); 
    for (X = 0; X != 5 - Y; X++) {
        display_score_str[X] = DIGITS_INDEX; // '0'
    }
    X = 4;
    do {
        display_score_str[X--] = DIGITS_INDEX + ((display_score_ascii[--Y] - '0') << 1);
    } while (Y);
}

void main()
{
    score = 0;

    multisprite_init();
    multisprite_set_charbase(tiles);
   
    // Set up a full background 
    for (counter = 0; counter < _MS_DLL_ARRAY_SIZE - 1; counter++) {
        if (counter & 2) {
            ptr = background + 2;
        } else {
            ptr = background;
        }
        multisprite_display_tiles(0, _MS_DLL_ARRAY_SIZE - 1 - counter, ptr, 20, 1);
    }
    // Score display
    display_score_update();
    multisprite_display_tiles(0, 0, display_score_str, 5, 3);
    multisprite_save();

    *P0C1 = multisprite_color(0x1c); // Setup Palette 0: Yellow
    *P0C2 = multisprite_color(0xc5); // Green
    *P0C3 = 0x0f; // White
   
    *P1C1 = multisprite_color(0x55); // Dark pink
    *P1C2 = multisprite_color(0x5B); // Light pink

    *P2C1 = multisprite_color(0x32);
    *P2C2 = multisprite_color(0x3D);
    *P2C3 = multisprite_color(0x37);

    *P3C1 = multisprite_color(0x92);
    *P3C2 = multisprite_color(0x9D);
    *P3C3 = multisprite_color(0x97);

    // Initialize sprites
    for (ypos = 16, xpos = 16, i = 0, X = 0; X != NB_SPRITES; xpos++, ypos++, X++) {
        sp_xpos[X] = xpos << 8;
        sp_ypos[X] = ypos << 8;
        sp_direction[X] = i++;
        if (i == 24) i = 0;
    }

    // Main loop
    do {
        // Prepare scrolling data
        if (multisprite_vscroll_buffer_empty()) {
            if (counter & 2) {
                ptr = background + 2;
            } else {
                ptr = background;
            }
            multisprite_vscroll_buffer_tiles(0, ptr, 20, 1);
            multisprite_vscroll_buffer_sprite(xchest, chest, 2, 3);
            xchest += 45;
            counter++;
        }
        
        multisprite_flip();
        multisprite_vertical_scrolling(1);

        for (i = 0; i != NB_SPRITES; i++) {
            X = i;
            Y = sp_direction[X];
            sp_xpos[X] += dx[Y];
            sp_ypos[X] += dy[Y];
            xpos = sp_xpos[X] >> 8;
            ypos = sp_ypos[X] >> 8;
            if ((xpos < 5 && (dx[Y] >> 8) < 0) || 
                (xpos >= 150 && (dx[Y] >> 8) >= 0)) {
                sp_direction[X] = horizontal_pingpong[Y];
            }
            if ((ypos < 5 + 16 && (dy[Y] >> 8) < 0) || 
                (ypos >= MS_YMAX - 20 && (dy[Y] >> 8) >= 0)) {
                sp_direction[X] = vertical_pingpong[Y];
            }
            multisprite_display_sprite(xpos, ypos, bb_char1, 2, 0); 
        }
        score++;
        display_score_update();
    } while(1);
}

 

cc7800-0.2.5-x86_64.msi

  • Like 7
Link to comment
Share on other sites

Hi,

This week's progress on cc7800/tools7800 :

- I've added horizontal scrolling support to headers/multisprite.h. It's working nicely but is not general purpose enough at the moment (it doesn't handle tile map boundaries...)

- I've added bidirectional support to vertical scrolling, which enables along with horizontal scrolling to tile a screen (example shown below)

- I've added support for 1 and 2 button joystick (and detection of 1 button joystick in order to avoid the destruction of the precious console), in a new header: joystick.h

- And I've added support for TileEd (tile editor) in Tools7800 : I generate the C code for the tiles directly from parsing the .tmx files

Here is examples/example_tiled.c and a video (sorry for the gfx. If anyone is volunteer to make it better looking, he's welcome) :

 

#include "stdlib.h"
#include "string.h"
#include "prosystem.h"
#define BIDIR_VERTICAL_SCROLLING
#define HORIZONTAL_SCROLLING
#define _MS_TOP_SCROLLING_ZONE 1
#include "multisprite.h"
#include "joystick.h"

holeydma reversed scattered(16,2) char bb_char1[32] = {
	0x01, 0x00, 0x01, 0x40, 0x0a, 0x94, 0x2a, 0x90, 0x3b, 0xa0, 0xc8, 0xe5, 0xc8, 0xe4, 0xc8, 0xd0,
	0xc8, 0xe5, 0xbb, 0x84, 0x0c, 0x20, 0x2a, 0x90, 0x0e, 0x50, 0x3f, 0x94, 0x3d, 0x68, 0x5d, 0x6a
};
// Generated by sprites7800 based on tiles.yaml
reversed scattered(16,12) char tiles[192] = {
	0x50, 0x50, 0x00, 0x00, 0xaa, 0xbf, 0xfe, 0xfe, 0xab, 0xea, 0xcf, 0xcf, 0x50, 0x50, 0x13, 0xc4,
	0xd6, 0xff, 0xee, 0xee, 0xaf, 0xfa, 0x3c, 0x3c, 0x50, 0x50, 0x14, 0xc4, 0xff, 0xff, 0xde, 0xde,
	0xaa, 0xfa, 0xf3, 0xf3, 0x50, 0x50, 0x12, 0x30, 0x3f, 0xfc, 0xde, 0xde, 0xa6, 0xfe, 0xc3, 0xc3,
	0x05, 0x05, 0x12, 0x3c, 0x2f, 0xfc, 0xee, 0xee, 0x96, 0xbe, 0x3c, 0x3c, 0x05, 0x05, 0x12, 0x3c,
	0x1e, 0xfc, 0xfe, 0xfe, 0x97, 0xfe, 0x33, 0x33, 0x05, 0x05, 0x0c, 0x4c, 0x1e, 0xfc, 0xaa, 0xaa,
	0x98, 0x3e, 0xcc, 0xcc, 0x05, 0x05, 0x3c, 0x4c, 0x1e, 0xfc, 0xaa, 0xaa, 0x94, 0x1e, 0x3c, 0x3c,
	0x50, 0x50, 0x0c, 0x40, 0x1e, 0xfc, 0xfe, 0xfe, 0xa4, 0x2e, 0xcf, 0xcf, 0x50, 0x50, 0x21, 0x28,
	0x1e, 0xec, 0xee, 0xee, 0xa9, 0x7e, 0x3c, 0x3c, 0x50, 0x50, 0x28, 0xa0, 0x1e, 0xec, 0xde, 0xde,
	0xba, 0xbe, 0xf3, 0xf3, 0x50, 0x50, 0x28, 0x84, 0x1e, 0xec, 0xde, 0xde, 0xbe, 0xfa, 0xc3, 0xc3,
	0x05, 0x05, 0x21, 0x04, 0x1e, 0xec, 0xee, 0xee, 0xaf, 0xfa, 0x3c, 0x3c, 0x05, 0x05, 0x05, 0x30,
	0x2e, 0xec, 0xfe, 0xfe, 0xab, 0xea, 0x33, 0x33, 0x05, 0x05, 0x04, 0xf0, 0x3f, 0xfc, 0xaa, 0xaa,
	0xaa, 0xaa, 0xcc, 0xcc, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
#define DIGITS_INDEX 12 
reversed scattered(16,20) char digits[320] = {
	0x15, 0x40, 0x05, 0x00, 0x15, 0x40, 0x15, 0x40, 0x01, 0x40, 0x55, 0x50, 0x15, 0x40, 0x55, 0x50, 0x15, 0x40, 0x15, 0x40,
...
};

// Edited with tiled, and generated with tiles7800
const char tilemap[1024] = {
	4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 
	4, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 4, 
...
	4, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 4, 
	4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4 
	};

#define TILEMAP_WIDTH 32
#define TILEMAP_HEIGHT 32

char i, xoffset;
char *ptr, *ptr2;

ramchip char *top_tiles, *bottom_tiles;
ramchip int score;
ramchip char display_score_str[5];
ramchip char display_score_ascii[6];

void display_score_update()
{
    itoa(score, display_score_ascii, 10);
    Y = strlen(display_score_ascii); 
    for (X = 0; X != 5 - Y; X++) {
        display_score_str[X] = DIGITS_INDEX; // '0'
    }
    X = 4;
    do {
        display_score_str[X--] = DIGITS_INDEX + ((display_score_ascii[--Y] - '0') << 1);
    } while (Y);
}

void main()
{
    score = 0;
    xoffset = 0;

    multisprite_init();
    multisprite_set_charbase(tiles);
    joystick_init();
   
    // Set up a full background 
    for (ptr = tilemap, i = _MS_TOP_SCROLLING_ZONE; i != _MS_DLL_ARRAY_SIZE; i++) {
        multisprite_display_tiles(-8, i, ptr, 21, 1); // 21 chars (168 pixels) for horizontal scrolling, out of screen on the left
        ptr += TILEMAP_WIDTH;
    }
    bottom_tiles = ptr;
    top_tiles = tilemap - TILEMAP_WIDTH; 

    // Score display
    display_score_update();
    multisprite_display_tiles(0, 0, display_score_str, 5, 0);
    multisprite_save();

    *P0C1 = multisprite_color(0x1c); // Setup Palette 0: Yellow
    *P0C2 = multisprite_color(0xc5); // Green
    *P0C3 = multisprite_color(0xcd); // Light green
   
    *P1C1 = 0x0f; // White
    *P1C2 = multisprite_color(0x9D);
    *P1C3 = multisprite_color(0x97);

    // Main loop
    do {
        if (multisprite_vscroll_buffers_refill_status()) {
            switch (multisprite_vscroll_buffers_refill_status()) {
            case MS_SCROLL_UP:
                top_tiles -= TILEMAP_WIDTH;
                bottom_tiles -= TILEMAP_WIDTH;
                break;
            case MS_SCROLL_DOWN:
                top_tiles += TILEMAP_WIDTH;
                bottom_tiles += TILEMAP_WIDTH;
            }
            xoffset -= 8;
            multisprite_top_vscroll_buffer_tiles(xoffset, top_tiles, 21, 1);
            multisprite_bottom_vscroll_buffer_tiles(xoffset, bottom_tiles, 21, 1);
            xoffset += 8;
            multisprite_vscroll_buffers_refilled();
        }    
        
        multisprite_flip();
        
        multisprite_display_sprite(76, 100, bb_char1, 2, 0);
        
        joystick_update();
        if (joystick[0] & JOYSTICK_LEFT) {
            multisprite_horizontal_scrolling(-1);
            xoffset++;
            if (xoffset == 8) {
                top_tiles--;
                bottom_tiles--;
                xoffset = 0;
            }
        } else if (joystick[0] & JOYSTICK_RIGHT) {
            multisprite_horizontal_scrolling(1);
            xoffset--;
            if (xoffset < 0) {
                top_tiles++;
                bottom_tiles++;
                xoffset = 7;
            }
        }
        if (joystick[0] & JOYSTICK_UP) {
            multisprite_vertical_scrolling(1);
        } else if (joystick[0] & JOYSTICK_DOWN) {
            multisprite_vertical_scrolling(-1);
        }
        score++;
        display_score_update();
    } while(1);
}

 

image.png

tiles.png

cc7800-0.2.6-x86_64.msi tiled.a78 test.tiled-project test.tsx test.tmx

  • Like 9
Link to comment
Share on other sites

On 6/13/2023 at 1:19 AM, KrunchyTC said:

So does bidirectional scrolling require special hardware like Nintendo does? 

Definitely no, and indeed the Maria chip is very strong at handling bidirectionnal scrolling (in contrary to the NES VPU which requires very specific programming OR special hardware like metroid if the field is bigger than 2 screens wide). BUT unfortunately, a fully tiled background is practically only usable in 160A mode (160 pixels wide and 4 colors only) if you want to keep a little CPU time and/or DMA bandwidth to display some sprites over your field... (roughly, it takes 10 + 3 + 9 * 21 = 202 cycles / 402 available in 160A, and 10 + 3 + 9 * 41 = 382 cycles out of 402 in 160B or 320B/C).

A solution would be partially tiled background, a solution I'll try to develop a library for in the future...

  • Like 1
Link to comment
Share on other sites

12 hours ago, bsteux said:

Definitely no, and indeed the Maria chip is very strong at handling bidirectional scrolling (in contrary to the NES VPU which requires very specific programming OR special hardware like metroid if the field is bigger than 2 screens wide). BUT unfortunately, a fully tiled background is practically only usable in 160A mode (160 pixels wide and 4 colors only) if you want to keep a little CPU time and/or DMA bandwidth to display some sprites over your field... (roughly, it takes 10 + 3 + 9 * 21 = 202 cycles / 402 available in 160A, and 10 + 3 + 9 * 41 = 382 cycles out of 402 in 160B or 320B/C).

A solution would be partially tiled background, a solution I'll try to develop a library for in the future...

Interesting. So bidirectional scrolling is always going to be tough to do. This is a GPU/CPU cycle limited task, rather than RAM limited?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...