Kyle W T Sherman
(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.
// 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
// 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__;
}
// 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);
// 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)
.RECIPEPREFIX = >
CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O
all: helloworld
helloworld:
> $(CLX) $(CXXFLAGS) -o helloworld.prg *.c
clean:
> rm -f *.prg *.inc *.o
/**
* 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;
}
cd hello-world
make clean && make && x64sc helloworld.prg &
.RECIPEPREFIX = >
CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O
all: systeminfo
systeminfo:
> $(CLX) $(CXXFLAGS) -o systeminfo.prg *.c
clean:
> rm -f *.prg *.inc *.o
/**
* 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;
}
cd system-info
make clean && make && x64sc systeminfo.prg &
.RECIPEPREFIX = >
CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O
all: qixlines
qixlines:
> $(CLX) $(CXXFLAGS) -o qixlines.prg *.c
clean:
> rm -f *.prg *.inc *.o
/**
* 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;
}
cd qix-lines
make clean && make && x64sc qixlines.prg &
.RECIPEPREFIX = >
CXX = cc65
CLX = cl65
CXXFLAGS = -t c64 -O
all: qixlinesmc
qixlinesmc:
> $(CLX) $(CXXFLAGS) -o qixlinesmc.prg *.c
clean:
> rm -f *.prg *.inc *.o
/**
* 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;
}
cd qix-lines-multi-color
make clean && make && x64sc qixlinesmc.prg &
* 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-header>>