Skip to content

Latest commit

 

History

History
1057 lines (854 loc) · 30 KB

c64-cc65.org

File metadata and controls

1057 lines (854 loc) · 30 KB

Org

Constants

Author

Kyle W T Sherman

Copyright

(format-time-string "%Y" nil t)
(if (string= "<<year()>>" start-year)
    start-year
  (concat start-year "-<<year()>>"))
Copyright © <<year-range()>> <<author>>

Header

<<copyright>>
MIT License

License Header

The MIT License (MIT)

<<copyright>>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Shared

Common

Common H
// Source: https://8bitworkshop.com/

#ifndef _COMMON_H
#define _COMMON_H

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <peekpoke.h>
#include <string.h>
#include <c64.h>
#include <joystick.h>

typedef uint8_t byte;                   // 8-bit unsigned
typedef int8_t sbyte;                   // 8-bit signed
typedef uint16_t word;                  // 16-bit unsigned
typedef enum { false, true } bool;      // boolean

#define COLS 40                         // total # of columns
#define ROWS 25                         // total # of rows

///// MACROS /////

// lookup screen address macro
#define SCRNADR(base, col, row) ((base) + (col) + (row) * 40)

// default screen base address on startup
#define DEFAULT_SCREEN ((void*) 0x400)

// wait until next frame, same as waitvsync()
#define wait_vblank waitvsync
// is raster line > 255?
#define RASTER_HIBIT  (VIC.ctrl1 & 0x80)

// set VIC Bank (given the start address)
#define SET_VIC_BANK(_addr) \
  CIA2.pra = (CIA2.pra & ~3) | (((((_addr) >> 8) & 0xc0) >> 6) ^ 3);

// set VIC character memory (given the start address)
#define SET_VIC_BITMAP(_addr) \
  VIC.addr = (VIC.addr & 0b11110001) | ((((_addr) >> 8) & 0x38) >> 2);

// set VIC screen memory (given the start address)
#define SET_VIC_SCREEN(_addr) \
  VIC.addr = (VIC.addr & 0b00001111) | ((((_addr) >> 8) & 0x3c) << 2);

// set scrolling registers
#define SET_SCROLL_Y(_y) \
  VIC.ctrl1 = (VIC.ctrl1 & 0xf8) | (_y);

#define SET_SCROLL_X(_x) \
  VIC.ctrl2 = (VIC.ctrl2 & 0xf8) | (_x);

// enable RAM from 0xa000-0xffff, disable interrupts
#define ENABLE_HIMEM() \
  asm("php"); \
  asm("sei"); \
  POKE(1, PEEK(1) & ~0b111);

// enable ROM and interrupts
#define DISABLE_HIMEM() \
  POKE(1, PEEK(1) | 0b111); \
  asm("plp");

///// FUNCTIONS /////

// wait until specific raster line
void raster_wait(byte line);

// get current VIC bank start address
char *get_vic_bank_start();

// get current screen memory address
char *get_screen_memory();

// return key in buffer, or 0 if none (BIOS call)
char __fastcall__ poll_keyboard();

#endif
Common C
// Source: https://8bitworkshop.com/

#include "common.h"

void raster_wait(byte line) {
  while (VIC.rasterline < line) ;
}

void wait_vblank(void) {
  raster_wait(255);
}

static byte VIC_BANK_PAGE[4] = {
  0xc0, 0x80, 0x40, 0x00
};

char *get_vic_bank_start() {
  return (char *)(VIC_BANK_PAGE[CIA2.pra & 3] << 8);
}

char *get_screen_memory() {
  return ((VIC.addr & 0xf0) << 6) + get_vic_bank_start();
}

char __fastcall__ poll_keyboard() {
  asm("jmp $f142");
  return __A__;
}

Multi-Color Bitmap

Multi-Color Bitmap H
// Source: https://8bitworkshop.com/

#include "common.h"

#define MCB_COLORS 0xc000
#define MCB_BITMAP 0xe000

void setup_bitmap_multi();

byte is_pixel(byte x, byte y);

void set_pixel(byte x, byte y, byte color);

void draw_line(int x0, int y0, int x1, int y1, byte color);

byte flood_fill(byte x, byte y, byte color);
Multi-Color Bitmap C
// Source: https://8bitworkshop.com/

#include "common.h"
#include "mcbitmap.h"

void setup_bitmap_multi() {
    VIC.ctrl1 = 0x38;
    VIC.ctrl2 = 0x18;
    SET_VIC_BANK(MCB_BITMAP);
    SET_VIC_BITMAP(MCB_BITMAP);
    SET_VIC_SCREEN(MCB_COLORS);
    memset((void *)MCB_BITMAP, 0, 0x2000);
    memset((void *)MCB_COLORS, 0, 0x800);
    memset(COLOR_RAM, 0, 40*25);
}

const byte PIXMASK[4] = { ~0xc0, ~0x30, ~0x0c, ~0x03 };
const byte PIXSHIFT[4] = { 6, 4, 2, 0 };

byte is_pixel(byte x, byte y) {
    word ofs = ((x >> 2) * 8 + (y >> 3) * 320) | (y & 7) | MCB_BITMAP;
    byte pixvalue;
    ENABLE_HIMEM();
    pixvalue = PEEK(ofs);
    DISABLE_HIMEM();
    return pixvalue & ~PIXMASK[x & 3];;
}

void set_pixel(byte x, byte y, byte color) {
    word ofs, b, cram, sram;
    byte ccol, scol, used;
    byte val;

    if (x >= 160 || y >= 200) return;

    color &= 0xf;
    // equal to background color? (value 0)
    if (color == VIC.bgcolor0) {
        val = 0;
    } else {
        // calculate character (and color RAM) offset
        cram = ((x >> 2) + (y >> 3) * 40);
        sram = cram | MCB_COLORS;
        cram |= 0xd800;
        // read color ram, screen memory, and used bits
        ENABLE_HIMEM();
        ccol = PEEK(cram);
        scol = PEEK(sram);
        used = PEEK(sram | 0x400);
        DISABLE_HIMEM();
        // unused in lower nibble of screen RAM? (value 2)
        if (color == (scol & 0xf) || !(used & 0x10)) {
            val = 2;
            scol = (scol & 0xf0) | color;
            used |= 0x10;
            POKE(sram, scol);
            // unused in upper nibble of screen RAM? (value 1)
        } else if (color == (scol >> 4) || !(used & 0x20)) {
            val = 1;
            scol = (scol & 0xf) | (color << 4);
            used |= 0x20;
            POKE(sram, scol);
            // all other colors in use, use color RAM
        } else {
            val = 3;
            used |= 0x40;
            ccol = color;
            POKE(cram, ccol);
        }
        // write to unused bit
        POKE(sram | 0x400, used);
    }

    ofs = ((x >> 2) * 8 + (y >> 3) * 320) | (y & 7) | MCB_BITMAP;
    x &= 3;
    ENABLE_HIMEM();
    b = PEEK(ofs) & PIXMASK[x];
    DISABLE_HIMEM();
    if (val) {
        b |= val << PIXSHIFT[x];
    }
    POKE(ofs, b);
}

void draw_line(int x0, int y0, int x1, int y1, byte color) {
    int dx = abs(x1 - x0);
    int sx = x0 < x1 ? 1 : -1;
    int dy = abs(y1 - y0);
    int sy = y0 < y1 ? 1 : -1;
    int err = (dx > dy ? dx : -dy) >> 1;
    int e2;
    for(;;) {
        set_pixel(x0, y0, color);
        if (x0 == x1 && y0 == y1) break;
        e2 = err;
        if (e2 > -dx) { err -= dy; x0 += sx; }
        if (e2 < dy) { err += dx; y0 += sy; }
    }
}

// support recursion
#pragma static-locals(push,off)
byte flood_fill(byte x, byte y, byte color) {
    register byte x1 = x;
    register byte x2;
    register byte i;
    // find left edge
    while (!is_pixel(x1, y))
        --x1;
    // exit if (x,y) is on a boundary
    if (x1 == x)
        return 1;
    ++x1;
    // find right edge
    x2 = x + 1;
    while (!is_pixel(x2, y))
        ++x2;
    // fill scanline
    for (i = x1; i < x2; i++) {
        set_pixel(i, y, color);
    }
    // fill above and below scanline
    for (i = x1; i < x2;) {
        i += flood_fill(i, y - 1, color);
    }
    for (i = x1; i < x2;) {
        i += flood_fill(i, y + 1, color);
    }
    return (x2 - x1);
}
#pragma static-locals(pop)

Programs

Hello World

Makefile
.RECIPEPREFIX = >

CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O

all: helloworld

helloworld:
> $(CLX) $(CXXFLAGS) -o helloworld.prg *.c

clean:
> rm -f *.prg *.inc *.o
helloworld
/**
 * Hello World
 */

#include <cbm.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    cbm_k_bsout(CH_FONT_UPPER);
    printf("hello, world!\n");
    return EXIT_SUCCESS;
}
Build and Run
cd hello-world
make clean && make && x64sc helloworld.prg &

System Info

Makefile
.RECIPEPREFIX = >

CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O

all: systeminfo

systeminfo:
> $(CLX) $(CXXFLAGS) -o systeminfo.prg *.c

clean:
> rm -f *.prg *.inc *.o
systeminfo
/**
 * System Info
 */

#include <c64.h>
#include <cc65.h>
#include <conio.h>
#include <ctype.h>
#include <modload.h>
#include <stdio.h>
#include <stdlib.h>
#include <tgi.h>

int main(void) {
    // setup tgi
    tgi_install(tgi_static_stddrv);
    tgi_init();

    // output system info
    printf("char, int, long sizes: %d, %d, %d\n",
           sizeof((char) 0), sizeof((int) 0), sizeof((long) 0));
    printf("x res: %d, y res: %d, colors: %d\n",
           tgi_getxres(), tgi_getyres(), tgi_getcolorcount());

    // cleanup tgi
    tgi_uninstall();

    return EXIT_SUCCESS;
}
Build and Run
cd system-info
make clean && make && x64sc systeminfo.prg &

Qix Lines

Makefile
.RECIPEPREFIX = >

CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O

all: qixlines

qixlines:
> $(CLX) $(CXXFLAGS) -o qixlines.prg *.c

clean:
> rm -f *.prg *.inc *.o
qixlines
/**
 * QIX Lines
 *
 * <<header>>
 */

#include <c64.h>
#include <cc65.h>
#include <conio.h>
#include <ctype.h>
#include <modload.h>
#include <stdio.h>
#include <stdlib.h>
#include <tgi.h>

#define MAX_COLORS 16
#define COLOR_BG TGI_COLOR_BLACK
#define COLOR_FG TGI_COLOR_WHITE
#define MAX_SIN 180
#define HISTORY_SIZE 10                 // how many lines to display at once
#define STEP 8                          // line spacing
#define STEP_RANGE 6                    // spacing plus/minus range

// use all colors except black (0)
#define RANDOM_COLOR() (rand() % (MAX_COLORS - 1) + 1)

typedef unsigned char byte;
typedef unsigned short ushort;

// line
typedef struct {
    short x1;
    short y1;
    short x2;
    short y2;
} line_s;

// globals
static ushort x_size;
static ushort y_size;

void set_color(const byte color) {
    byte palette[2];
    palette[0] = COLOR_BG;
    palette[1] = color;
    tgi_setpalette(palette);
    tgi_setcolor(COLOR_FG);
}

ushort next_degree(const ushort degree) {
    // add randomly to the degree
    ushort d = degree + STEP + (int)rand() % (STEP_RANGE * 2 + 1) - STEP_RANGE;
    if (d >= MAX_SIN) d = d - MAX_SIN;
    return d;
}

void next_line(line_s *line, line_s *line_delta, line_s *line_degree) {
    // randomly add to the degrees
    line_degree->x1 = next_degree(line_degree->x1);
    line_degree->y1 = next_degree(line_degree->y1);
    line_degree->x2 = next_degree(line_degree->x2);
    line_degree->y2 = next_degree(line_degree->y2);

    // add using sin modified by a delta for each coordinate dimension
    line->x1 += (ushort)(((long)line_delta->x1 * _sin(line_degree->x1)) / 256);
    line->y1 += (ushort)(((long)line_delta->y1 * _sin(line_degree->y1)) / 256);
    line->x2 += (ushort)(((long)line_delta->x2 * _sin(line_degree->x2)) / 256);
    line->y2 += (ushort)(((long)line_delta->y2 * _sin(line_degree->y2)) / 256);

    // if any coordinates are out of range, reverse their direction and change color
    if (line->x1 < 0) {
        line->x1 = 0 - line->x1;
        line_delta->x1 = -line_delta->x1;
        set_color(RANDOM_COLOR());
    }
    if (line->x1 >= x_size) {
        line->x1 = x_size - (line->x1 - x_size);
        line_delta->x1 = -line_delta->x1;
        set_color(RANDOM_COLOR());
    }
    if (line->y1 < 0) {
        line->y1 = 0 - line->y1;
        line_delta->y1 = -line_delta->y1;
        set_color(RANDOM_COLOR());
    }
    if (line->y1 >= y_size) {
        line->y1 = y_size - (line->y1 - y_size);
        line_delta->y1 = -line_delta->y1;
        set_color(RANDOM_COLOR());
    }
    if (line->x2 < 0) {
        line->x2 = 0 - line->x2;
        line_delta->x2 = -line_delta->x2;
        set_color(RANDOM_COLOR());
    }
    if (line->x2 >= x_size) {
        line->x2 = x_size - (line->x2 - x_size);
        line_delta->x2 = -line_delta->x2;
        set_color(RANDOM_COLOR());
    }
    if (line->y2 < 0) {
        line->y2 = 0 - line->y2;
        line_delta->y2 = -line_delta->y2;
        set_color(RANDOM_COLOR());
    }
    if (line->y2 >= y_size) {
        line->y2 = y_size - (line->y2 - y_size);
        line_delta->y2 = -line_delta->y2;
        set_color(RANDOM_COLOR());
    }
}

// draw lines until a key is pressed
void draw_lines() {
    line_s line, line_delta, line_degree, line_history[HISTORY_SIZE];
    ushort history_index;

    // set random color
    tgi_setcolor(RANDOM_COLOR());

    // randomize starting values
    line.x1 = rand() % x_size;
    line.y1 = rand() % y_size;
    line.x2 = rand() % x_size;
    line.y2 = rand() % y_size;

    line_delta.x1 = STEP;
    line_delta.y1 = STEP;
    line_delta.x2 = STEP;
    line_delta.y2 = STEP;

    line_degree.x1 = rand() % MAX_SIN;
    line_degree.y1 = rand() % MAX_SIN;
    line_degree.x2 = rand() % MAX_SIN;
    line_degree.y2 = rand() % MAX_SIN;
    history_index = 0;

    // loop until key-press
    while (!kbhit()) {
        // get next line
        next_line(&line, &line_delta, &line_degree);

        // draw line
        tgi_setcolor(COLOR_FG);
        tgi_line(line.x1, line.y1, line.x2, line.y2);

        // undraw oldest line
        tgi_setcolor(COLOR_BG);
        tgi_line(line_history[history_index].x1, line_history[history_index].y1,
                 line_history[history_index].x2, line_history[history_index].y2);

        // add to history
        line_history[history_index++] = line;
        if (history_index >= HISTORY_SIZE) history_index = 0;
    }

    // consume key-press
    cgetc();
}

int main(void) {
    byte border_color;

    // setup tgi
    tgi_install(tgi_static_stddrv);
    tgi_init();
    tgi_clear();

    // set globals
    x_size = tgi_getxres();
    y_size = tgi_getyres();

    // persist border color
    border_color = bordercolor(COLOR_BG);

    // main loop
    draw_lines();

    // restore border color
    bordercolor(border_color);

    // cleanup tgi
    tgi_uninstall();
    clrscr();

    return EXIT_SUCCESS;
}
Build and Run
cd qix-lines
make clean && make && x64sc qixlines.prg &

Qix Lines (Multi-Color)

Makefile
.RECIPEPREFIX = >

CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O

all: qixlinesmc

qixlinesmc:
> $(CLX) $(CXXFLAGS) -o qixlinesmc.prg *.c

clean:
> rm -f *.prg *.inc *.o
common
<<common_h>>
<<common_c>>
mcbitmap
<<mcbitmap_h>>
<<mcbitmap_c>>
qixlinesmc
/**
 * QIX Lines (Multi-Color)
 *
 * <<header>>
 */

#include <c64.h>
#include <cc65.h>
#include <conio.h>
#include <ctype.h>
#include <modload.h>
#include <stdio.h>
#include <stdlib.h>
#include <tgi.h>

#include "mcbitmap.h"

#define X_SIZE 160
#define Y_SIZE 192
#define MAX_COLORS 16
#define COLOR_BG TGI_COLOR_BLACK
#define MAX_SIN 180
#define HISTORY_SIZE 10                 // how many lines to display at once
#define STEP 10                         // line spacing
#define STEP_RANGE 9                    // spacing plus/minus range
#define QIX_COUNT 3                     // number of qixs to display

// use all colors except black (0)
#define RANDOM_COLOR() (rand() % (MAX_COLORS - 1) + 1)

// line
typedef struct {
    int x1;
    int y1;
    int x2;
    int y2;
    byte color;
} line_s;

int next_degree(int degree) {
    // add randomly to the degree
    int d = degree + STEP + (int)rand() % (STEP_RANGE * 2 + 1) - STEP_RANGE;
    if (d >= MAX_SIN) d = d - MAX_SIN;
    return d;
}

void next_line(line_s *line, line_s *line_delta, line_s *line_degree) {
    // randomly add to the degrees
    line_degree->x1 = next_degree(line_degree->x1);
    line_degree->y1 = next_degree(line_degree->y1);
    line_degree->x2 = next_degree(line_degree->x2);
    line_degree->y2 = next_degree(line_degree->y2);

    // add using sin modified by a delta for each coordinate dimension
    line->x1 += (int)(((long) line_delta->x1 * _sin(line_degree->x1)) / 256);
    line->y1 += (int)(((long) line_delta->y1 * _sin(line_degree->y1)) / 256);
    line->x2 += (int)(((long) line_delta->x2 * _sin(line_degree->x2)) / 256);
    line->y2 += (int)(((long) line_delta->y2 * _sin(line_degree->y2)) / 256);

    // if any coordinates are out of range, reverse their direction and change color
    if (line->x1 < 0) {
        line->x1 = 0 - line->x1;
        line_delta->x1 = -line_delta->x1;
        line->color = RANDOM_COLOR();
    }
    if (line->x1 >= X_SIZE) {
        line->x1 = X_SIZE - (line->x1 - X_SIZE);
        line_delta->x1 = -line_delta->x1;
        line->color = RANDOM_COLOR();
    }
    if (line->y1 < 0) {
        line->y1 = 0 - line->y1;
        line_delta->y1 = -line_delta->y1;
        line->color = RANDOM_COLOR();
    }
    if (line->y1 >= Y_SIZE) {
        line->y1 = Y_SIZE - (line->y1 - Y_SIZE);
        line_delta->y1 = -line_delta->y1;
        line->color = RANDOM_COLOR();
    }
    if (line->x2 < 0) {
        line->x2 = 0 - line->x2;
        line_delta->x2 = -line_delta->x2;
        line->color = RANDOM_COLOR();
    }
    if (line->x2 >= X_SIZE) {
        line->x2 = X_SIZE - (line->x2 - X_SIZE);
        line_delta->x2 = -line_delta->x2;
        line->color = RANDOM_COLOR();
    }
    if (line->y2 < 0) {
        line->y2 = 0 - line->y2;
        line_delta->y2 = -line_delta->y2;
        line->color = RANDOM_COLOR();
    }
    if (line->y2 >= Y_SIZE) {
        line->y2 = Y_SIZE - (line->y2 - Y_SIZE);
        line_delta->y2 = -line_delta->y2;
        line->color = RANDOM_COLOR();
    }
}

// draw lines until a key is pressed
void draw_lines() {
    line_s line, line_delta, line_degree, line_history[HISTORY_SIZE];
    int history_index;

    // randomize starting values
    line.x1 = rand() % X_SIZE;
    line.y1 = rand() % Y_SIZE;
    line.x2 = rand() % X_SIZE;
    line.y2 = rand() % Y_SIZE;
    line.color = RANDOM_COLOR();

    line_delta.x1 = STEP;
    line_delta.y1 = STEP;
    line_delta.x2 = STEP;
    line_delta.y2 = STEP;

    line_degree.x1 = rand() % MAX_SIN;
    line_degree.y1 = rand() % MAX_SIN;
    line_degree.x2 = rand() % MAX_SIN;
    line_degree.y2 = rand() % MAX_SIN;
    history_index = 0;

    // loop until key-press
    while (!kbhit()) {
        // get next line
        next_line(&line, &line_delta, &line_degree);

        // draw line
        draw_line(line.x1, line.y1, line.x2, line.y2, line.color);

        // remove from history
        draw_line(line_history[history_index].x1, line_history[history_index].y1,
                  line_history[history_index].x2, line_history[history_index].y2,
                  COLOR_BG);

        // add to history
        line_history[history_index++] = line;
        if (history_index >= HISTORY_SIZE) history_index = 0;
    }

    // consume key-press
    cgetc();
}

int main(void) {
    unsigned char bg_color, border_color;

    // setup multi-color bitmap
    setup_bitmap_multi();

    // persist background and border color
    bg_color = bgcolor(COLOR_BG);
    border_color = bordercolor(COLOR_BG);

    // clear screen
    clrscr();

    // main loop
    draw_lines();

    // restore background and border color
    bgcolor(bg_color);
    bordercolor(border_color);

    return EXIT_SUCCESS;
}
Build and Run
cd qix-lines-multi-color
make clean && make && x64sc qixlinesmc.prg &

README.org

* cc65 Programs for the C64

  See the "Build and Run" sections of [[file:c64-cc65.org][c64-cc65.org]] to
  see how to build, clean, and run these applications using the VICE emulator.
  In general you follow these steps:

  Build with =make=.

  Clean with =make clean=.

  Run with =x64sc NAME.prg=.

  All files are generated from [[file:c64-cc65.org][c64-cc65.org]] using
  Emacs' org-mode literate programming system to "tangle" them.

  The example code at [[https://8bitworkshop.com/][8bitworkshop]] was
  instrumental in creating these programs.

  <<copyright>>

  License: [[file:LICENSE][MIT License]]

*** [[hello-world][Hello World]]

    Just prints "HELLO, WORLD!".

*** [[system-info][System Info]]

    Displays some basic system information.

*** [[qix-lines][Qix Lines]]

    Draws lines on the screen like the game Qix.

    #+NAME: Qix Lines Video
    [[file:qix-lines/qixlines.mkv][file:qix-lines/qixlines.gif]]

*** [[qix-lines-multi-color][Qix Lines (Multi Color)]]

    Attempt at a multi-colored version of Qix Lines, but it is both slow and
    not working ATM.

LICENSE

<<license-header>>