1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-11-25 20:32:25 +00:00

Added new_30_years_low_resolution.

This commit is contained in:
jespergravgaard 2021-01-04 01:02:00 +01:00
parent a6554c3d37
commit 1fd5cc001c
16 changed files with 1810 additions and 0 deletions

View File

@ -0,0 +1,18 @@
// ByteBoozer decruncher
// https://github.com/p-a/kickass-cruncher-plugins
// Decrunch crunched data using ByteBoozer
// - crunched: Pointer to the start of the crunched data
void byteboozer_decrunch(char* crunched) {
asm {
ldy crunched
ldx crunched+1
jsr b2.Decrunch
}
}
// The byteboozer decruncher
export char BYTEBOOZER[] = kickasm(resource "byteboozer_decrunch.asm") {{
.const B2_ZP_BASE = $fc
#import "byteboozer_decrunch.asm"
}};

View File

@ -0,0 +1,6 @@
// ByteBoozer decruncher
// https://github.com/p-a/kickass-cruncher-plugins
// Decrunch crunched data using ByteBoozer
// - crunched: Pointer to the start of the crunched data
void byteboozer_decrunch(char* crunched);

View File

@ -0,0 +1,182 @@
// ByteBoozer Decruncher /HCL May.2003
// B2 Decruncher December 2014
.importonce
.filenamespace b2
// You must set .const B2_ZP_BASE prior the import of this file
.if (B2_ZP_BASE > $ff) {
.error "B2_ZP_BASE must be in zeropage. Was $" + toHexString(B2_ZP_BASE,4)
}
.label zp_base = B2_ZP_BASE
.label bits = zp_base
.label put = zp_base + 2
.macro @B2_DECRUNCH(addr) {
ldy #<addr
ldx #>addr
jsr b2.Decrunch
}
.macro GetNextBit() {
asl bits
bne DgEnd
jsr GetNewBits
DgEnd:
}
.macro GetLen() {
lda #1
GlLoop:
:GetNextBit()
bcc GlEnd
:GetNextBit()
rol
bpl GlLoop
GlEnd:
}
Decrunch:
sty Get1+1
sty Get2+1
sty Get3+1
stx Get1+2
stx Get2+2
stx Get3+2
ldx #0
jsr GetNewBits
sty put-1,x
cpx #2
bcc *-7
lda #$80
sta bits
DLoop:
:GetNextBit()
bcs Match
Literal:
// Literal run.. get length.
:GetLen()
sta LLen+1
ldy #0
LLoop:
Get3:
lda $feed,x
inx
bne *+5
jsr GnbInc
L1: sta (put),y
iny
LLen:
cpy #0
bne LLoop
clc
tya
adc put
sta put
bcc *+4
inc put+1
iny
beq DLoop
// Has to continue with a match..
Match:
// Match.. get length.
:GetLen()
sta MLen+1
// Length 255 -> EOF
cmp #$ff
beq End
// Get num bits
cmp #2
lda #0
rol
:GetNextBit()
rol
:GetNextBit()
rol
tay
lda Tab,y
beq M8
// Get bits < 8
M_1:
:GetNextBit()
rol
bcs M_1
bmi MShort
M8:
// Get byte
eor #$ff
tay
Get2:
lda $feed,x
inx
bne *+5
jsr GnbInc
jmp Mdone
MShort:
ldy #$ff
Mdone:
//clc
adc put
sta MLda+1
tya
adc put+1
sta MLda+2
ldy #$ff
MLoop:
iny
MLda:
lda $beef,y
sta (put),y
MLen:
cpy #0
bne MLoop
//sec
tya
adc put
sta put
bcc *+4
inc put+1
jmp DLoop
End:
rts
GetNewBits:
Get1:
ldy $feed,x
sty bits
rol bits
inx
bne GnbEnd
GnbInc:
inc Get1+2
inc Get2+2
inc Get3+2
GnbEnd:
rts
Tab:
// Short offsets
.byte %11011111 // 3
.byte %11111011 // 6
.byte %00000000 // 8
.byte %10000000 // 10
// Long offsets
.byte %11101111 // 4
.byte %11111101 // 7
.byte %10000000 // 10
.byte %11110000 // 13

View File

@ -0,0 +1,13 @@
// Demo Flow Engine
// Start the demo IRQ. Can be called multiple times!
// Setting IRQ to the "demo" IRQ running outside the parts and
// Setting memory to IO + RAM (no kernal/basic)
void demo_start();
// Work to be performed every frame while the demo runs
// Assumes that I/O is enabled
void demo_work();
// Counts total demo frames
extern volatile unsigned int demo_frame_count;

View File

@ -0,0 +1,19 @@
// Commodore 64 PRG executable file
.plugin "se.triad.kickass.CruncherPlugins"
.file [name="%O", type="prg", segments="Program"]
.segmentdef Program [segments="Basic, Common, Part2, Part1, InitDemo"]
.segmentdef Basic [start=$0801]
.segmentdef Common [segments="Code, Data"]
.segmentdef Code [start=%P]
.segmentdef Data [startAfter="Code"]
.segmentdef Part2 [segments="CodePart2, DataPart2, InitPart2"]
.segmentdef CodePart2 [startAfter="Data"]
.segmentdef DataPart2 [startAfter="CodePart2"]
.segmentdef InitPart2 [startAfter="DataPart2"]
.segmentdef Part1 [segments="CodePart1, DataPart1, InitPart1"]
.segmentdef CodePart1 [startAfter="InitPart2"]
.segmentdef DataPart1 [startAfter="CodePart1"]
.segmentdef InitPart1 [startAfter="DataPart1"]
.segmentdef InitDemo [startAfter="InitPart1"]
.segment Basic
:BasicUpstart(%E)

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,168 @@
// KickAssembler functions for converting a Picture to a multi-color bitmap
// Based on Code by Cruzer/CML, April 2011 (https://codebase64.org/doku.php?id=base:double_screen_horizontal_scroller&s[]=koala)
// Based on a routine from Codebase64 by Martin Piper
//---------------------------------------------------------------------------------------------------------------------
// Picture - a result from LoadPicture()
// - The upper line is used for defining the c64 palette, with multicolor sized pixels
// - The pixel to the right of the palette defines the background color
// Usage:
// #import "mcbitmap.asm"
// .var mcBmmData = getMcBitmapData(LoadPicture("bitmap.png"))
// *=$0c00 "SCREEN";
// .for (var y=0; y<25; y++)
// .for (var x=0; x<40; x++)
// .byte getMcScreenData(x, y, mcBmmData)
// *=$1c00 "COLORS"
// colorRam:
// .for (var y=0; y<25; y++)
// .for (var x=0; x<40; x++)
// .byte getMcColorData(x, y, mcBmmData)
// *=$2000 "BITMAP";
// .for (var y=0; y<25; y++)
// .for (var x=0; x<40; x++)
// .fill 8, getMcPixelData(x, y, i, mcBmmData)
#importonce
.struct McBitmapPalette {
// Hashtable mapping RGB to C64 color
// rgb2c64.get(rgb) returns a C64 color (0-15)
rgb2c64,
// The (C64) background color of the image
bgColor
}
// Bitmap data for a single (4x8 MC pixel) block
.struct McBitmapBlock {
// List() with 8 pixel bytes
pixels,
// C64 color for %01 pixels
color1,
// C64 color for %10 pixels
color2,
// C64 color for %11 pixels
color3,
// Byte for screen (color1*$10 + color2)
screendata,
// Byte for color (color3)
colordata
}
// Bitmap data for an entire picture
.struct McBitmapData {
// The McBitmapPalette
palette,
// The number of X-blocks (40 for a normal full screen bitmap)
width,
// The number of Y-blocks (25 for a normal full screen bitmap)
height,
// List with width*height McBitmapBlock ordered row by row.
// blocks.get(y*width+x) is the block at (x,y)
blocks
}
// Get multicolor-bitmap data for a picture. The first row of the picture contains the palette and is discarded. (pixel #0-#15 as colors representing C64 color black, white, ... pixel #16 representing the background color)
.function getMcBitmapData(pic) {
.var palette = findMcPalette(pic)
.var width = floor(pic.width/8)
.var height = floor((pic.height-1)/8) // -1 to account for the first line containing the palette
.var blocks = List()
.for (var y=0; y<height; y++)
.for (var x=0; x<width; x++)
.eval blocks.add(createMcBlock(x, y, pic, palette))
.return McBitmapData(palette, width, height, blocks)
}
// Get the screen data for a specific bitmap block (1 char: 4x8 MC pixels). bmData is created by getMcBitmapData()
.function getMcScreenData(x, y, bmData) {
.return bmData.blocks.get(y*bmData.width + x).screendata
}
// Get the color data for a specific bitmap block (1 char: 4x8 MC pixels). bmData is created by getMcBitmapData()
.function getMcColorData(x, y, bmData) {
.return bmData.blocks.get(y*bmData.width + x).colordata
}
// Get pixel data for a specific bitmap block (1 char: 4x8 MC pixels). i is the pixel row (0-7). bmData is created by getMcBitmapData()
.function getMcPixelData(x, y, i, bmData) {
.return bmData.blocks.get(y*bmData.width + x).pixels.get(i)
}
// Find the C64 palette RGB values to use.
// Looks at the first 16 pixels in the first line (0,0)..(0,15) of an image to find the palette.
// Looks at pixel number 17 in the first line (0,16) to identify the background color to use.
// Returns McBitmapPalette
.function findMcPalette(pic) {
.var rgb2c64 = Hashtable()
.for (var i=0; i<16; i++) {
.var rgb = pic.getPixel(i*2,0)
.eval rgb2c64.put(rgb, i)
}
.var bgRgb = pic.getPixel(16*2,0)
.var bgColor = rgb2c64.get(bgRgb)
.return McBitmapPalette(rgb2c64, bgColor)
}
// Get the C64 color for a specific pixel (0-15)
// Assumes multicolor with 2-pixel wide
// Uses the passed palette to identify the C64 color
.function getMcC64Color(xPos, yPos, pic, palette) {
.var rgb = pic.getPixel(xPos*2, yPos+1)
.var c64Color = palette.rgb2c64.get(rgb)
.return c64Color
}
// Get the C64 colors to use for a bitmap block (1 char: 4x8 MC pixels).
// Returns a list with 16 values (one per C64 color).
// The values in the list is 0 for unused (or bgColor) and 1, 2, 3 for each used color.
// cols.get(c64Color) will return %01, %10, %11 when passed the C64 color matching each multi-color. This is exactly the pixel value to use for the color.
.function findMcBlockColors(chrX, chrY, pic, palette) {
.var colorCounts = List()
.for (var i=0; i<16; i++) .eval colorCounts.add(0)
.for (var pixY=0; pixY<8; pixY++) {
.for (var pixX=0; pixX<4; pixX++) {
.var c64Color = getMcC64Color(chrX*4 + pixX, chrY*8 + pixY, pic, palette)
.eval colorCounts.set(c64Color, colorCounts.get(c64Color) + 1)
}
}
.eval colorCounts.set(palette.bgColor,0)
.for (var i=0; i<16; i++) .eval colorCounts.set(i, [colorCounts.get(i) << 4] | i)
.eval colorCounts.sort()
.eval colorCounts.reverse()
.var blockColors = List()
.for (var i=0; i<16; i++) .eval blockColors.add(0)
.for (var i=0; i<3; i++) {
.var c64Color = colorCounts.get(i) & $0f
.eval blockColors.set(c64Color, i+1)
}
.return blockColors
}
// Get the bitmap bytes for a a bitmap block (1 char: 4x8 MC pixels).
// Returns BitmapBlock containing pixel data and color data for the bitmap block
.function createMcBlock(chrX, chrY, pic, palette) {
.var blockColors = findMcBlockColors(chrX, chrY, pic, palette)
.var pixelData = List()
.for (var pixY=0; pixY<8; pixY++) {
.var bitmapByte = 0
.for (var pixX=0; pixX<4; pixX++) {
.var c64Color = getMcC64Color(chrX*4 + pixX, chrY*8 + pixY, pic, palette)
.var multiColor = blockColors.get(c64Color)
.eval bitmapByte = bitmapByte | [multiColor << [6 - pixX*2]]
}
.eval pixelData.add(bitmapByte)
}
.var bitmapBlock = McBitmapBlock()
.eval bitmapBlock.pixels = pixelData
// Find the three C64 multi-colors
.for (var i=1; i<16; i++) {
.if (blockColors.get(i) == %01) .eval bitmapBlock.color1 = i
.if (blockColors.get(i) == %10) .eval bitmapBlock.color2 = i
.if (blockColors.get(i) == %11) .eval bitmapBlock.color3 = i
}
// Convert multi-colors to screen data and color data
.eval bitmapBlock.screendata = (bitmapBlock.color1 << 4) | (bitmapBlock.color2 & $f)
.eval bitmapBlock.colordata = bitmapBlock.color3
.return bitmapBlock
}

View File

@ -0,0 +1,195 @@
// A bucket-sorting sprite multiplexer
// A multiplexer frame contains a fixed number of multiplexer buckets.
// Each multiplexer bucket represents a single raster line (typically an IRQ) where sprites are positioned.
// Each multiplexer bucket has the capability of moving 0-8 sprites.
// The bucket sorting multiplexer is used for cyclic sprite Y-movements.
// (eg. 32 sprites in a single large 256 byte sine results in a 8 frame cycle after which the sprites can swap places and repeat the same 8 frames again to give one continous movement).
// ## Pre-calculation
// It works by pre-calculating frames and buckets into arrays of struct BucketSprite - and then using these buckets when rendering the actual multiplexer.
// Only the Y-positions (in PLEX_YPOS) are used for pre-calculation.
// Set-up is done by
// 1. Defining PLEX_COUNT as the number of sprites in the multiplexer
// 2. Defining BUCKET_COUNT as the number of buckets per frame
// 3. Setting BUCKET_YPOS to the Y-positions of each bucket raster line
// Pre-calculation is done by
// 1. Call plexBucketInit()
// 2. For each frame:
// a. Set up Y-positions in PLEX_YPOS
// b. Call plexBucketSort(struct BucketSprite* frame) where frame is a BUCKET_COUNT*BUCKET_SIZE array that will receive sorted sprite-data for rendering the frame.
// Rendering is done by the following for each frame:
// 1. Set up X-positions (in PLEX_XPOS and PLEX_XPOS_MSB) and pointers (in PLEX_PTR). If they are unchanged then you do not need to change them.
// 2. Call plexBucketFrameInit()
// 3. For each bucket.
// a. Wait for the raster line in BUCKET_YPOS
// b. Call plexBucketShow(bucket) where bucket is the frame array prepared in the pre-calculation.
// c. Move to the next bucket in the frame by doing
// bucket += BUCKET_SIZE;
// 4. After rendering all buckets of the frame, update the plex_id's of all the sprites in all the buckets.
// This is done to prepare for the next cycle where all sprites swap places to give a continous movement.
// Typically you want to do
// sprite->plex_id=(sprite->plex_id-1)&(FRAME_COUNT-1)
#include "multiplex-bucket.h"
#include <c64.h>
#ifdef DEBUG_PLEX
#include <stdio.h>
#endif
#ifndef PLEX_SPRITE_PTRS
#define PLEX_SPRITE_PTRS DEFAULT_SCREEN+OFFSET_SPRITE_PTRS
#endif
// The screen sprite pointers to update
char * const SCREEN_SPRITE_PTRS = PLEX_SPRITE_PTRS;
// The Y-position (IRQ raster line) starting each bucket
char BUCKET_YPOS[BUCKET_COUNT] = { 0x10, 0x48, 0x58, 0x72, 0x8e, 0xaa, 0xc0, 0xd0, 0xde };
// The y-positions of the multiplexer sprites. (These are converted to multiplexer buckets)
char PLEX_YPOS[PLEX_COUNT];
// The low byte of the x-positions of the multiplexer sprites
char PLEX_XPOS[PLEX_COUNT];
// The MSB of the x-positions of the multiplexer sprites (0/1)
char PLEX_XPOS_MSB[PLEX_COUNT];
// The sprite pointers for the multiplexed sprites
char PLEX_PTR[PLEX_COUNT];
// The sprite color for the multiplexed sprites
//char PLEX_COL[PLEX_COUNT];
// Indexes of the plex-sprites sorted by sprite y-position. Each call to plexSort() will fix the sorting if changes to the Y-positions have ruined it.
char PLEX_SORTED_IDX[PLEX_COUNT];
// Initialize data structures for the multiplexer
void plexPrepareInit() {
// Initial sorting is trivial
for(char i=0; i<PLEX_COUNT;i++)
PLEX_SORTED_IDX[i] = i;
}
// Performs run-time bucket sort of the sprites in the PLEX_ arrays into struct BucketSprite[]
// Starts by performing a true sorting of the sprites based on Y-position (unsing insertion sort)
void plexPrepareFrame(struct BucketSprite* frame) {
// Sort the sprite indices in PLEX_SORTED_IDX based on the Y-position (using insertion sort)
// Assumes that the positions are nearly sorted already (as each sprite just moves a bit)
// 1. Moves a marker (m) from the start to end of the array. Every time the marker moves forward all elements before the marker are sorted correctly.
// 2a. If the next element after the marker is larger that the current element
// the marker can be moved forwards (as the sorting is correct).
// 2b. If the next element after the marker is smaller than the current element:
// elements before the marker are shifted right one at a time until encountering one smaller than the current one.
// It is then inserted at the spot. Now the marker can move forward.
for(char m=0;m<PLEX_COUNT-1;m++) {
char nxt_idx = PLEX_SORTED_IDX[m+1];
char nxt_y = PLEX_YPOS[nxt_idx];
if(nxt_y<PLEX_YPOS[PLEX_SORTED_IDX[m]]) {
// Shift values until we encounter a value smaller than nxt_y
char s = m;
do {
PLEX_SORTED_IDX[s+1] = PLEX_SORTED_IDX[s];
s--;
} while((s!=0xff) && (nxt_y<PLEX_YPOS[PLEX_SORTED_IDX[s]]));
// store the mark at the found position
s++;
PLEX_SORTED_IDX[s] = nxt_idx;
}
}
// Y-position where each real sprite is free (used for selecting the best bucket)
char real_sprite_free_ypos[8];
// Initialize real sprite free to the first bucket Y-position
for(char i=0;i<8;i++) real_sprite_free_ypos[i] = BUCKET_YPOS[0];
// The current real sprite idx
char real_sprite_id = 0;
// The current bucket idx
char bucket_id = 0;
// The current bucket start y-position
char bucket_ypos = BUCKET_YPOS[bucket_id];
// The current bucket & sprite
struct BucketSprite *bucket = frame, *sprite = frame;
for(char i=0;i<PLEX_COUNT; i++) {
char plex_id = PLEX_SORTED_IDX[i];
unsigned char ypos = PLEX_YPOS[plex_id];
if(real_sprite_free_ypos[real_sprite_id] > bucket_ypos) {
// The real sprite is not free in the current bucket - move to the next bucket!
#ifdef DEBUG_PLEX
if(bucket_id>=BUCKET_COUNT-1) printf("plex#%hhx ypos:%hhx not free in last bucket#%hhx ypos:%hhx. real sprite#%hhx free at ypos %hhx.\n", plex_id, ypos, bucket_id, bucket_ypos, real_sprite_id, real_sprite_free_ypos[real_sprite_id]);
#endif
// Move to the next bucket
bucket_id++;
bucket_ypos = BUCKET_YPOS[bucket_id];
bucket += BUCKET_SIZE;
// Zero-fill the next sprite in the bucket (if not full)
if(sprite!=bucket) sprite->ypos=0;
// Set current sprite to start of next bucket
sprite = bucket;
}
// Identify problems filling buckets
#ifdef DEBUG_PLEX
if(ypos<=bucket_ypos) printf("plex#%hhx ypos:%hhx <= bucket#%hhx ypos:%hhx. lower bucket ypos or introduce new with lower ypos.\n", plex_id, ypos, bucket_id, bucket_ypos);
#endif
// Put the sprite into the bucket
sprite->ypos = ypos;
sprite->plex_id = plex_id;
// Increase bucket ypos to account for time spent placing the sprite
bucket_ypos += 1;
// Update next free ypos for the real sprite
real_sprite_free_ypos[real_sprite_id] = ypos+22;
// Move to the next real sprite
real_sprite_id = (real_sprite_id+1)&7;
// Move to the next sprite in the bucket
sprite++;
}
// Zero-fill the next sprite in the bucket (if not full)
bucket += BUCKET_SIZE;
if(sprite!=bucket) sprite->ypos=0;
}
// The next "real" sprite being used by the multiplexer
volatile char plex_real_sprite_idx = 0;
// Start a new frame (initializing data structures)
void plexFrameStart() {
plex_real_sprite_idx = 0;
}
// Show the sprites in a specific bucket
// - bucket: The bucket to show
void plexBucketShow(struct BucketSprite* bucket) {
// Masks used for MSB
char MSB_SET_MASK_BY_ID[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
char MSB_CLEAR_MASK_BY_ID[8] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f };
// Use a char* to optimize the code!
char* bucket_ptr = (char*)bucket;
char real_idx = plex_real_sprite_idx*2;
char i=0;
while(bucket_ptr[i]) {
SPRITES_YPOS[real_idx] = bucket_ptr[i++];
char plex_id = bucket_ptr[i];
SPRITES_XPOS[real_idx] = PLEX_XPOS[plex_id];
real_idx /= 2;
if(PLEX_XPOS_MSB[plex_id]) {
*SPRITES_XMSB |= MSB_SET_MASK_BY_ID[real_idx];
} else {
*SPRITES_XMSB &= MSB_CLEAR_MASK_BY_ID[real_idx];
}
SCREEN_SPRITE_PTRS[real_idx] = PLEX_PTR[plex_id];
//SPRITES_COLOR[real_idx] = PLEX_COL[plex_id];
real_idx = (real_idx+1)&7;
real_idx *= 2;
i++;
if(i==BUCKET_SIZE*sizeof(struct BucketSprite)) break;
}
plex_real_sprite_idx = real_idx/2;
}
// Updates the PLEX_ID's preparing for the next cycle of the multiplexer.
// Performs: sprite->plex_id=(sprite->plex_id-1)&(FRAME_COUNT-1)
void plexFrameEnd(struct BucketSprite* frame) {
char* sprite_plex_ids = &frame->plex_id;
for(char i=0;i<BUCKET_COUNT*BUCKET_SIZE*2;i+=2) {
sprite_plex_ids[i] = (sprite_plex_ids[i]-1)&(PLEX_COUNT-1);
}
}

View File

@ -0,0 +1,81 @@
// A bucket-sorting sprite multiplexer
// A multiplexer frame contains a fixed number of multiplexer buckets.
// Each multiplexer bucket represents a single raster line (typically an IRQ) where sprites are positioned.
// Each multiplexer bucket has the capability of moving 0-8 sprites.
// The bucket sorting multiplexer is used for cyclic sprite Y-movements.
// (eg. 32 sprites in a single large 256 byte sine results in a 8 frame cycle after which the sprites can swap places and repeat the same 8 frames again to give one continous movement).
// ## Pre-calculation
// It works by pre-calculating frames and buckets into arrays of struct BucketSprite - and then using these buckets when rendering the actual multiplexer.
// Only the Y-positions (in PLEX_YPOS) are used for pre-calculation.
// Set-up is done by
// 1. Defining PLEX_COUNT as the number of sprites in the multiplexer
// 2. Defining BUCKET_COUNT as the number of buckets per frame
// 3. Setting BUCKET_YPOS to the Y-positions of each bucket raster line
// Pre-calculation is done by
// 1. Call plexBucketInit()
// 2. For each frame:
// a. Set up Y-positions in PLEX_YPOS
// b. Call plexBucketSort(struct BucketSprite* frame) where frame is a BUCKET_COUNT*BUCKET_SIZE array that will receive sorted sprite-data for rendering the frame.
// Rendering is done by the following for each frame:
// 1. Set up X-positions (in PLEX_XPOS and PLEX_XPOS_MSB) and pointers (in PLEX_PTR). If they are unchanged then you do not need to change them.
// 2. Call plexBucketFrameInit()
// 3. For each bucket.
// a. Wait for the raster line in BUCKET_YPOS
// b. Call plexBucketShow(bucket) where bucket is the frame array prepared in the pre-calculation.
// c. Move to the next bucket in the frame by doing
// bucket += BUCKET_SIZE;
// 4. After rendering all buckets of the frame, update the plex_id's of all the sprites in all the buckets.
// This is done to prepare for the next cycle where all sprites swap places to give a continous movement.
// Typically you want to do
// sprite->plex_id=(sprite->plex_id-1)&(FRAME_COUNT-1)
// The number of sprites in the multiplexer
#define PLEX_COUNT 32
// The number of sprites per multiplexer bucket
#define BUCKET_SIZE 8
// The number of multiplexer buckets per frame
#define BUCKET_COUNT 9
// The number of multiplexer frames
#define FRAME_COUNT 8
// The Y-position (IRQ raster line) starting each bucket
extern char BUCKET_YPOS[BUCKET_COUNT];
// The y-positions of the multiplexer sprites. (These are converted to multiplexer buckets)
extern char PLEX_YPOS[PLEX_COUNT];
// The low byte of the x-positions of the multiplexer sprites
extern char PLEX_XPOS[PLEX_COUNT];
// The MSB of the x-positions of the multiplexer sprites (0/1)
extern char PLEX_XPOS_MSB[PLEX_COUNT];
// The sprite pointers for the multiplexed sprites
extern char PLEX_PTR[PLEX_COUNT];
// The sprite color for the multiplexed sprites
//extern char PLEX_COL[PLEX_COUNT];
// A single sprite in a multiplexer bucket
struct BucketSprite {
char ypos;
char plex_id;
};
// Initialize data structures for the multiplexer
void plexPrepareInit();
// Performs run-time bucket sort of the sprites in the PLEX_ arrays into struct BucketSprite[]
// Starts by performing a true sorting of the sprites based on Y-position (using insertion sort)
void plexPrepareFrame(struct BucketSprite* frame);
// Start a new frame (initializing data structures)
void plexFrameStart();
// Show the sprites in a specific bucket
// - bucketSprites: The bucket to show
void plexBucketShow(struct BucketSprite* bucket);
// Updates the PLEX_ID's preparing for the next cycle of the multiplexer.
// Performs: sprite->plex_id=(sprite->plex_id-1)&(FRAME_COUNT-1)
void plexFrameEnd(struct BucketSprite* frame);

View File

@ -0,0 +1,142 @@
// The Demo collects the parts and handles overall control
#pragma target(c64)
#pragma link("demo.ld")
#pragma zp_reserve(0xfa..0xfb) // Reserved for music player
#pragma interrupt(hardware_clobber)
#pragma emulator("C64Debugger")
#include <c64.h>
#include <6502.h>
#include <string.h>
#include "demo.h"
#include "byteboozer.h"
#include "part1-happynewyear.c"
#include "part2-swingplex.c"
#pragma code_seg(Code)
#pragma data_seg(Data)
char* const DEMO_MUSIC = 0xAC00;
// Pointer to the music init routine
void()* const musicInit = (void()*) DEMO_MUSIC;
// Pointer to the music play routine
void()* const musicPlay = (void()*) DEMO_MUSIC+3;
#pragma data_seg(InitDemo)
// SID tune
char DEMO_MUSIC_CRUNCHED[] = kickasm(resource "do-it-again-$AC00-$FA-8580.sid", uses DEMO_MUSIC) {{
.modify B2() {
.pc = DEMO_MUSIC "MUSIC"
.const music = LoadSid("do-it-again-$AC00-$FA-8580.sid")
.fill music.size, music.getData(i)
}
}};
#pragma data_seg(Data)
void main() {
// Initialize the demo - start the IRQ
demo_init();
// Decrunch music
byteboozer_decrunch(DEMO_MUSIC_CRUNCHED);
// Init music
asm { lda #0 }
(*musicInit)();
// Initialize the demo - start the IRQ
demo_start();
// Initialize Part 1 (Revealing "Happy New Year" logo)
part1_init();
// Start part 1 at 0:04,5
while(demo_frame_count<5*50) ;
// Run Part 1 (Revealing "Happy New Year" logo)
part1_run();
// Initialize part 2
part2_init();
// Wait for the right place to start part 2
while(demo_frame_count<16*50) ;
// Disable sparkler
sparkler_active = 0;
// Run part 2
part2_run();
for(;;) ;
}
// Initialize demo code.
// Can be called multiple times!
// Setting IRQ to the "demo" IRQ running outside the parts and
// Setting memory to IO + RAM (no kernal/basic)
void demo_init() {
SEI();
// Disable kernal & basic
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
// Disable CIA 1 Timer IRQ
CIA1->INTERRUPT = CIA_INTERRUPT_CLEAR;
// Acknowledge any timer IRQ
asm { lda CIA1_INTERRUPT }
// Acknowledge any VIC IRQ
*IRQ_STATUS = 0x0f;
}
// Start the demo IRQ. Can be called multiple times!
// Setting IRQ to the "demo" IRQ running outside the parts and
// Setting memory to IO + RAM (no kernal/basic)
void demo_start() {
demo_init();
// Set raster line to 0x00
*VICII_CONTROL &= 0x7f;
*RASTER = 0;
// Set the IRQ routine
*HARDWARE_IRQ = &irq_demo;
// Enable Raster Interrupt
*IRQ_ENABLE = IRQ_RASTER;
CLI();
}
// Counts total demo frames
volatile unsigned int demo_frame_count = 0;
// Work to be performed every frame while the demo runs
// Assumes that I/O is enabled
void demo_work() {
// Increase frame count
demo_frame_count++;
// Play music
(*musicPlay)();
// Animate the sparkler
if(sparkler_active)
sparkler_anim();
}
// Is the sparkler active
volatile char sparkler_active = 0;
// The sparkler sprite idx
volatile char sparkler_idx = 0;
// Animate the sparkler sprite
void sparkler_anim() {
if(++sparkler_idx==30) sparkler_idx=0;
P1_SCREEN_SPRITE_PTRS[0] = toSpritePtr(P1_SPRITES)+sparkler_idx/2;
}
// IRQ running during between parts
__interrupt void irq_demo() {
// Remember processor port value
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
char port_value = *PROCPORT;
// Enable IO
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
// Perform any demo work
demo_work();
// Acknowledge the IRQ
*IRQ_STATUS = IRQ_RASTER;
// Restore processor port value
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = port_value;
}

View File

@ -0,0 +1,355 @@
// Show the Happy New Year image as a MC bitmap
#include <c64.h>
#include <6502.h>
#include <string.h>
#include "byteboozer.h"
#pragma code_seg(CodePart1)
#pragma data_seg(DataPart1)
char * const P1_COLORS = 0xa800; // A800-AFFF
char * const P1_PIXELS = 0xc000; // C000-DFFF
char * const P1_SCREEN = 0xe000; // E000-E3FF
char * const P1_SPRITES = 0xfc00; // E000-E3FF
char * const PIXELS_EMPTY = 0xe800; // E800-EFFF
// A copy of the load screen and colors
char * const LOAD_SCREEN = 0xe400; // E400-E7FF
char * const LOAD_CHARSET = 0xf000; // F000-F7FF
char * const LOAD_COLORS = 0xf800; // F800-FBFF
// Flipper cosine easing table
unsigned int * const FLIPPER_EASING = 0xa400;
// Sprite pointers
char * const P1_SCREEN_SPRITE_PTRS = 0xe3f8; // P1_SCREEN+OFFSET_SPRITE_PTRS;
#pragma data_seg(InitPart1)
// MC Bitmap Data
char P1_PIXELS_CRUNCHED[] = kickasm(resource "happy-newyear.png", resource "mcbitmap.asm", uses P1_PIXELS) {{
.modify B2() {
.pc = P1_PIXELS "HAPPYNEWYEAR PIXELS"
#import "mcbitmap.asm"
.var mcBmmData1 = getMcBitmapData(LoadPicture("happy-newyear.png"))
.for (var y=0; y<25; y++)
.for (var x=0; x<40; x++)
.fill 8, getMcPixelData(x, y, i, mcBmmData1)
}
}};
char P1_SCREEN_CRUNCHED[] = kickasm(uses P1_SCREEN) {{
.modify B2() {
.pc = P1_SCREEN "HAPPYNEWYEAR SCREEN"
.for (var y=0; y<25; y++)
.for (var x=0; x<40; x++)
.byte getMcScreenData(x, y, mcBmmData1)
}
}};
char P1_COLORS_CRUNCHED[] = kickasm(uses P1_COLORS) {{
.modify B2() {
.pc = P1_COLORS "HAPPYNEWYEAR COLORS"
.for (var y=0; y<25; y++)
.for (var x=0; x<40; x++)
.byte getMcColorData(x, y, mcBmmData1)
}
}};
// Sparkler sprites
char P1_SPRITES_CRUNCHED[] = kickasm(uses P1_SPRITES, resource "sparklers.png") {{
.modify B2() {
.pc = P1_SPRITES "P1_SPRITES"
// Pixels 11 01 10 11
.var p1_sprites = LoadPicture("sparklers.png", List().add($000000, $daccc3, $472a24, $957a71))
.for(var sx=0;sx<15;sx++) {
.for (var y=0;y<21; y++) {
.for (var c=0; c<3; c++) {
.byte p1_sprites.getMulticolorByte(sx*3+c,y)
}
}
.byte 0
}
}
}};
// An easing curve from 0x000 to 0x130
export char FLIPPER_EASING_CRUNCHED[] = kickasm {{
.modify B2() {
.pc = FLIPPER_EASING "FLIPPER_EASING"
.fillword $130, round($98+$98*cos(PI+PI*i/$130))
}
}};
#pragma data_seg(DataPart1)
void part1_init() {
// Disable IO
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_ALL;
// Decrunch pixels
byteboozer_decrunch(P1_PIXELS_CRUNCHED);
// Enable IO, Disable kernal & basic
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
// Decrunch screen
byteboozer_decrunch(P1_SCREEN_CRUNCHED);
// Decrunch colors
byteboozer_decrunch(P1_COLORS_CRUNCHED);
// Decrunch sprites
byteboozer_decrunch(P1_SPRITES_CRUNCHED);
// Decrunch flipper sine table
byteboozer_decrunch(FLIPPER_EASING_CRUNCHED);
// Initialize the badlines
init_rasters();
// Fill some empty pixels
memset(PIXELS_EMPTY, 0x00, 0x800);
// Enable CHARGEN, Disable kernal & basic
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_CHARROM;
memcpy(LOAD_CHARSET, CHARGEN, 0x800);
// Enable IO, Disable kernal & basic
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
// Copy loading screen
memcpy(LOAD_SCREEN, DEFAULT_SCREEN, 0x0400);
// Copy loading colors
memcpy(LOAD_COLORS, COLS, 1000);
}
void part1_run() {
SEI();
// Disable kernal & basic
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
// Disable CIA 1 Timer IRQ
CIA1->INTERRUPT = CIA_INTERRUPT_CLEAR;
// Acknowledge any timer IRQ
asm { lda CIA1_INTERRUPT }
// Acknowledge any VIC IRQ
*IRQ_STATUS = 0x0f;
// Set raster line to 0x136
*VICII_CONTROL |= 0x80;
*RASTER = IRQ_PART1_TOP_LINE;
// Set the IRQ routine
*HARDWARE_IRQ = &irq_part1_top;
// Enable Raster Interrupt
*IRQ_ENABLE = IRQ_RASTER;
// Show Sparkler
VICII->SPRITES_MC = 0x01;
VICII->SPRITE0_COLOR = PINK;
VICII->SPRITES_MCOLOR1 = YELLOW;
VICII->SPRITES_MCOLOR2 = PURPLE;
VICII->SPRITES_XMSB = 0x01; // 262
VICII->SPRITE0_X = 22; // 262
VICII->SPRITE0_Y = 190; // 144
P1_SCREEN_SPRITE_PTRS[0] = toSpritePtr(P1_SPRITES);
CLI();
part1_loop();
}
// Signals the main() loop to do work when all rasters are complete
volatile char p1_work_ready = 0;
// Handle some stuff in the main() routine
void part1_loop() {
p1_work_ready = 0;
for(;;) {
while(p1_work_ready==0) ;
// Fix colors
flipper_fix_colors();
// Show sparkler at 0:09
if(!sparkler_active && demo_frame_count>9*50-3) {
VICII->SPRITES_ENABLE = 0x01;
sparkler_active = 1;
}
// Perform any demo-wide work
demo_work();
// Wait for the right place to exit part 1
if(demo_frame_count>14*50) {
// Re-start the demo base IRQ
demo_start();
// Leave part 1
break;
}
// My work is done!
p1_work_ready = 0;
}
}
// Top of the flipper
volatile unsigned int irq_flipper_top_line = 0x00;
// Bottom of the flipper
volatile unsigned int irq_flipper_bottom_line = 0x08;
// 1 if flipper is done
volatile char flipper_done = 0;
// 1 if the raster line is a badline
__align(0x100) char RASTER_BADLINES[0x130];
// Initialize the BADLINES
void init_rasters() {
for(unsigned int i=0;i<sizeof(RASTER_BADLINES);i++)
RASTER_BADLINES[i] = 0;
for(char b=0x32;b<0xfa;b+=8)
RASTER_BADLINES[b] = 1;
}
const char IRQ_PART1_TOP_LINE = 0x36;
// IRQ running during set-up
__interrupt void irq_part1_top() {
// Colors
VICII->BORDER_COLOR = BLACK;
VICII->BG_COLOR = BLACK;
// Set BMM
VICII->CONTROL1 |= VICII_BMM;
// Set MCM
VICII->CONTROL2 |= VICII_MCM;
// Change graphics bank
CIA2->PORT_A = toDd00(P1_SCREEN);
// Show screen
VICII->MEMORY = toD018(P1_SCREEN, P1_PIXELS);
// Set up the flipper IRQ
if(>irq_flipper_top_line)
*VICII_CONTROL |= 0x80;
else
*VICII_CONTROL &= 0x7f;
*RASTER = (<irq_flipper_top_line)&0xf8;
*HARDWARE_IRQ = &irq_flipper_top;
// Signal main routine to play music
p1_work_ready = 1;
// Acknowledge the IRQ
*IRQ_STATUS = IRQ_RASTER;
}
// IRQ running during set-up
// Flips from start screen to bitmap (stops the bitmap)
__interrupt void irq_flipper_top() {
asm { nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop }
raster_fine((<irq_flipper_top_line)&7);
asm {
lda #$9a // VICII->MEMORY = toD018(LOAD_SCREEN, PIXELS_EMPTY);
ldx #LIGHT_GREEN // VICII->BORDER_COLOR = LIGHT_GREEN;
ldy #$1b // VICII->CONTROL1 &= ~VICII_BMM;
sta VICII_MEMORY
stx BORDER_COLOR
sty VICII_CONTROL
stx BG_COLOR
lda #$c8 // VICII->CONTROL2 &= ~VICII_MCM;
sta VICII_CONTROL2
}
// Set up the flipper IRQ
if(>irq_flipper_bottom_line)
*VICII_CONTROL |= 0x80;
else
*VICII_CONTROL &= 0x7f;
*RASTER = (<irq_flipper_bottom_line)&0xf8;
*HARDWARE_IRQ = &irq_flipper_bottom;
// Acknowledge the IRQ
*IRQ_STATUS = IRQ_RASTER;
}
// Middle of the flipper
volatile unsigned int irq_flipper_idx = 0x00;
// IRQ running during set-up
// Flips from start screen to bitmap (starts the start-up screen)
__interrupt void irq_flipper_bottom() {
asm { nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop }
raster_fine((<irq_flipper_bottom_line)&7);
// Colors
asm { nop nop nop nop }
VICII->BORDER_COLOR = LIGHT_BLUE;
VICII->BG_COLOR = BLUE;
// Show default screen
VICII->MEMORY = toD018(LOAD_SCREEN, LOAD_CHARSET);
if(!flipper_done) {
// Move the flipper down
unsigned int irq_flipper_line = FLIPPER_EASING[irq_flipper_idx++];
// Check limits
if(irq_flipper_line<8)
irq_flipper_top_line = 0;
else
irq_flipper_top_line = irq_flipper_line-8;
if(irq_flipper_line>0x128)
irq_flipper_bottom_line = 0x130;
else
irq_flipper_bottom_line = irq_flipper_line+8;
// Are we done
if(irq_flipper_line==0x130)
flipper_done = 1;
}
// Set up the IRQ again
*VICII_CONTROL |=0x80;
*RASTER = IRQ_PART1_TOP_LINE;
*HARDWARE_IRQ = &irq_part1_top;
// Acknowledge the IRQ
*IRQ_STATUS = IRQ_RASTER;
}
// Waits until at the exact start of raster line
// Excepts to start at a line divisible by 8 (0x00, 0x08, x010, ...).
// Waits line_offset (0-7) additional lines.
void raster_fine(char line_offset) {
kickasm(uses line_offset, uses RASTER_BADLINES, clobbers "AXY") {{
jmp aligned
.align $100
aligned:
ldy RASTER
ldx line_offset
inx
rst:
nop
nop
nop
nop
dex // 2
beq done // 2
lda RASTER_BADLINES,y // 4
beq notbad // 3
bad:
nop // 2
nop
nop
nop
nop
dex
beq done
iny
nop
bit $ea
notbad:
.fill 18, NOP
bit $ea
iny
jmp rst
done:
}}
}
// The current char line where the flipper switches from bitmap to text
volatile char flipper_charline = 0;
// Fixes the colors for the flipper
// Updates with bitmap colors when the bitmap is being shown
void flipper_fix_colors() {
if(irq_flipper_top_line>0x2e && irq_flipper_top_line<0xf6) {
char charline = (char)((irq_flipper_top_line-0x2e)/8);
if(charline>=flipper_charline) {
unsigned int offset = (unsigned int)flipper_charline*40;
// We have to update some colors
char* colors = COLS+offset;
char* happy_cols = P1_COLORS+offset;
for(char i=0;i<40;i++)
colors[i] = happy_cols[i];
flipper_charline++;
}
}
}

View File

@ -0,0 +1,631 @@
// 2-screen bitmap logo and a multiplexer scroller
#include <c64.h>
#include <6502.h>
#include <string.h>
#include "demo.h"
#include "byteboozer.h"
#pragma code_seg(CodePart2)
#pragma data_seg(DataPart2)
#include "../clib/vsp.h"
#define PLEX_SPRITE_PTRS 0xe3f8
#include "multiplex-bucket.h"
// Memory layout of the graphics bank
char * const LOGO_DATA = 0x5400;
char * const PART2_BITMAP = 0xc000; // -0xdfff
char * const PART2_SCREEN = 0xe000; // -0xe400
char * const PART2_SPRITES = 0xe400; // -0xf400
// Location PLEX ID updaters are placed when running
char * const PLEX_ID_UPDATERS = 0x3c00;
// Location where the crunched PLEX ID updaters are placed to be decrunched
char * const PLEX_ID_UPDATERS_CRUNCHED2 = 0x7c00; // -0xFF72
// Size of the crunched PLEX ID updaters
const unsigned int PLEX_ID_UPDATERS_CRUNCHED_SIZE = 0x0b72;
// Location where the crunched LOGO DATA is placed to be decrunched
char * const LOGO_DATA_CRUNCHED2 = 0x8800; // -0xAA2D
// Size of the crunched PLEX ID updaters
const unsigned int LOGO_DATA_CRUNCHED_SIZE = 0x222d;
// Char-based sizes for the logo
const char LOGO_HEIGHT = 25;
const char LOGO_WIDTH = 80;
// Address of screen data
char * const LOGO_DATA_SCREEN = LOGO_DATA;
// Address of color data
char * const LOGO_DATA_COLORS = LOGO_DATA_SCREEN+LOGO_HEIGHT*LOGO_WIDTH;
// Address of pixel data
char * const LOGO_DATA_BITMAP = LOGO_DATA_COLORS+LOGO_HEIGHT*LOGO_WIDTH;
// Sprite pointer for sprite 0
#define SPRITE_0 toSpritePtr(PART2_SPRITES)
// The sprite font
#pragma data_seg(InitPart2)
char SPRITES_CRUNCHED[] = kickasm(resource "spritefont.png") {{
.modify B2() {
.pc = PART2_SPRITES "PART2_SPRITES"
.var p2_sprites = LoadPicture("spritefont.png", List().add($000000, $ffffff))
.for(var sy=0;sy<8;sy++) {
.for(var sx=0;sx<8;sx++) {
.for (var y=0;y<21; y++) {
.for (var c=0; c<3; c++) {
.byte p2_sprites.getSinglecolorByte(sx*3+c,sy*21+y)
}
}
.byte 0
}
}
}
}};
#pragma data_seg(DataPart2)
#pragma data_seg(InitPart2)
char LOGO_DATA_CRUNCHED[] = kickasm(resource "logo-bitmap-640.png", resource "mcbitmap.asm", uses LOGO_HEIGHT, uses LOGO_WIDTH) {{
.modify B2() {
.pc = LOGO_DATA "LOGO DATA"
#import "mcbitmap.asm"
.var mcBmmData2 = getMcBitmapData(LoadPicture("logo-bitmap-640.png"))
// Screen data
.for (var y=0; y<LOGO_HEIGHT; y++)
.for (var x=0; x<LOGO_WIDTH; x++)
.byte getMcScreenData(x, y, mcBmmData2)
// Color Data
.for (var y=0; y<LOGO_HEIGHT; y++)
.for (var x=0; x<LOGO_WIDTH; x++)
.byte getMcColorData(x, y, mcBmmData2)
// Bitmap Data (row by row)
.for (var y=0; y<LOGO_HEIGHT; y++)
.for (var i=0; i<8; i++)
.for (var x=0; x<LOGO_WIDTH; x++)
.byte getMcPixelData(x, y, i, mcBmmData2)
}
}};
#pragma data_seg(DataPart2)
// A sinus table with values [0;320]
__align(0x100) unsigned int VSP_SINTABLE[0x100] = kickasm {{
.fillword $100, round(160+160*sin(2*PI*i/256))
}};
// The sequence of colors for the sprites
char SPRITE_COLOR_SEQUENCE[] = {
WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE,
WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE,
WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE,
WHITE, YELLOW, CYAN, GREEN, PURPLE, RED, BLUE, RED, PURPLE, GREEN, CYAN, YELLOW,
WHITE, WHITE,
};
__align(0x100) char SCROLL_YSIN[0x100] = kickasm {{
.fill $100, round(139+89.5*sin(toRadians(360*i/256)))
}};
// The buckets containing sprites to show. 8 sprites in each bucket.
__align(0x100) struct BucketSprite BUCKET_SPRITES[FRAME_COUNT*BUCKET_COUNT*BUCKET_SIZE];
// Copy of the original buckets containing sprites to show. 8 sprites in each bucket. (Used for adding the plex_id_offset)
__align(0x100) struct BucketSprite ORIGINAL_BUCKET_SPRITES[FRAME_COUNT*BUCKET_COUNT*BUCKET_SIZE];
__align(0x100) char XMOVEMENT[0x400] = kickasm {{
//.lohifill $100, round(344-i*344/$100-86*sin(toRadians(360*i/$100)))
.lohifill $200, round(344-i*344/$100-129*sin(toRadians(360*i/$100)))
//.lohifill $100, round(344-i*344/$100-86*sin(toRadians(360*i/$80)))
//.lohifill $100, round(344-i*344/$100 -86*sin(toRadians(360*i/$100)) -43*sin(toRadians(360*i/$80)))
//.lohifill $100, 344-i*344/$100
}};
// The high-value table
char * const XMOVEMENT_HI = XMOVEMENT+0x200;
// The scroll text
char SCROLL_TEXT[] =
"\x2a "
"most people will remember 2020 for a long time. "
"for us nerds, it was a chance to dig deep into our hobbies. "
"but we do miss the demoparties, and drinking beers with you crazy people... "
"it is the 30th birthday of camelot, and this virtual greeting card is our way of celebrating with all of you! "
" credits "
"\x2a code: rex "
"\x2a music: linus "
"\x2a graphics: bizkid, snabel & vic "
"\x2a "
"camelot sends love to "
"\xff abyss connection "
"\xff algotech "
"\xff ancients "
"\xff arsenic "
"\xff arise "
"\xff artline designs "
"\xff artstate "
"\xff atlantis "
"\xff bonzai "
"\xff booze design "
"\xff censor design "
"\xff cosine "
"\xff crest "
"\xff chorus "
"\xff dekadence "
"\xff delysid "
"\xff desire "
"\xff elysium "
"\xff excess "
"\xff extend "
"\xff faic "
"\xff f4cg "
"\xff fairlight "
"\xff fossil "
"\xff glance "
"\xff genesis project "
"\xff haujobb "
"\xff hitmen "
"\xff hoaxers "
"\xff hokuto force "
"\xff horizon "
"\xff illusion "
"\xff john dillermand "
"\xff laxity "
"\xff lepsi de "
"\xff lethargy "
"\xff mayday "
"\xff megastyle "
"\xff multistyle labs "
"\xff nah-kolor "
"\xff noice "
"\xff offence "
"\xff onslaught "
"\xff oxyron "
"\xff padua "
"\xff panda design "
"\xff panoramic designs "
"\xff performers "
"\xff plush "
"\xff pretzel logic "
"\xff prosonix "
"\xff proxima "
"\xff rabenauge "
"\xff radwar "
"\xff rebels "
"\xff resource "
"\xff samar "
"\xff scenesat "
"\xff shape "
"\xff siesta "
"\xff silicon ltd. "
"\xff singular "
"\xff software of sweden "
"\xff starion "
"\xff success "
"\xff svenonacid "
"\xff the dreams "
"\xff the solution "
"\xff triad "
"\xff tropyx "
"\xff trsi "
"\xff unicess "
"\xff up rough "
"\xff vision "
"\xff xenon "
"\xff xentax "
"\xff "
"... we hope to see you all again in 2021..."
" "
;
// IRQ performing the VSP
const char IRQ_SWING_VSP_LINE = 0x2d;
void part2_init() {
// Decrunch sprites
byteboozer_decrunch(SPRITES_CRUNCHED);
// Move the crunched logo data out of harms way
memcpy(LOGO_DATA_CRUNCHED2, LOGO_DATA_CRUNCHED, LOGO_DATA_CRUNCHED_SIZE);
// Move the crunched plex updaters out of harms way
memcpy(PLEX_ID_UPDATERS_CRUNCHED2, PLEX_ID_UPDATERS_CRUNCHED, PLEX_ID_UPDATERS_CRUNCHED_SIZE);
// Decrunch multiplexer frame updaters (from new location)
byteboozer_decrunch(PLEX_ID_UPDATERS_CRUNCHED2);
// Decrunch logo data
byteboozer_decrunch(LOGO_DATA_CRUNCHED2);
// Empty the hidden part of the bitmap
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_ALL;
memset(PART2_BITMAP+8000, 0, 192);
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
// Empty screen & cols
memset(COLS, BLACK, 1024);
memset(PART2_SCREEN, BLACK, 1000);
// Fade the sparkler
VICII->SPRITE0_COLOR = GREY;
VICII->SPRITES_MCOLOR1 = BROWN;
VICII->SPRITES_MCOLOR2 = BLUE;
// Initialize PLEX tables
plexPrepareInit();
// Prepare 8 frames of y-positions into BUCKET_SPRITES
struct BucketSprite* frame = BUCKET_SPRITES;
for(char frame_idx=0;frame_idx<8;frame_idx++) {
char sin_idx = frame_idx;
for(char s=0; s<PLEX_COUNT;s++) {
PLEX_YPOS[s] = SCROLL_YSIN[sin_idx];
sin_idx += 8;
}
// Perform bucket sort
plexPrepareFrame(frame);
// Move to Next frame
frame += BUCKET_SIZE*BUCKET_COUNT;
}
// Copy the original buckets
memcpy(ORIGINAL_BUCKET_SPRITES, BUCKET_SPRITES, sizeof(BUCKET_SPRITES));
// Set the initial sprite pointers
for(char s=0;s<PLEX_COUNT;s++) {
PLEX_PTR[s] = SPRITE_0+' ';
}
// Disable sparkler
VICII->SPRITES_XMSB = 0x00;
VICII->SPRITE0_X = 0x00;
VICII->SPRITES_ENABLE = 0x00;
// Set sprite colors
VICII->SPRITES_MC = 0x00;
for(char s=0;s<8;s++)
SPRITES_COLOR[s] = WHITE;
// Empty the rest of the screen
memset(PART2_SCREEN+1000, BLACK, 24);
}
void part2_run() {
SEI();
// Colors
VICII->BORDER_COLOR = BLACK;
VICII->BG_COLOR = BLACK;
// Change graphics bank
CIA2->PORT_A = toDd00(PART2_SCREEN);
// Show screen
VICII->MEMORY = toD018(PART2_SCREEN, PART2_BITMAP);
// Set bitmap mode
VICII->CONTROL1 |= VICII_BMM;
// Enable & initialize sprites
*SPRITES_ENABLE = 0xff;
// Disable CIA 1 Timer IRQ
CIA1->INTERRUPT = CIA_INTERRUPT_CLEAR;
// Acknowledge any timer IRQ
asm { lda CIA1_INTERRUPT }
// Acknowledge any VIC IRQ
*IRQ_STATUS = 0x0f;
// Set raster line to first bucket
*VICII_CONTROL &=0x7f;
*RASTER = BUCKET_YPOS[0];
// Enable Raster Interrupt
*IRQ_ENABLE = IRQ_RASTER;
// Set the IRQ routine
*HARDWARE_IRQ = &irq_swing_top;
// Enable the IRQ again
CLI();
// The current frame ID (0-7)
plex_frame_id = 0;
// Pointer to the buckets of the current frame
plex_frame = BUCKET_SPRITES;
// Offset added to plex_id to ensure the sprite cycling works (decreased 1 every time a cycle is complete)
plex_id_offset = 0;
part2_loop();
}
// Part 2 main loop
void part2_loop() {
p2_work_ready = 0;
for(;;) {
while(p2_work_ready==0) ;
// Play music
demo_work();
// Reveal logo
if(p2_logo_revealing && !p2_logo_reveal_done)
p2_logo_reveal();
// Show logo at 0:18,50
if(!p2_logo_revealing && demo_frame_count>18*50+25)
p2_logo_revealing = 1;
// Move logo as soon as reveal is done (40 frames) 0:19,30
if(!p2_logo_swinging && p2_logo_reveal_done)
p2_logo_swinging = 1;
// Start plex scroller at 0:26
if(!p2_plex_scroller_moving && demo_frame_count>26*50)
p2_plex_scroller_moving = 1;
// My work is done!
p2_work_ready = 0;
}
}
// Signals the main() loop to do work when all rasters are complete
volatile char p2_work_ready;
// 1 if the logo is being revealed
volatile char p2_logo_revealing = 0;
// 1 if the logo is completely revealed
volatile char p2_logo_reveal_done = 0;
// 1 if the logo is being showed
volatile char p2_logo_swinging = 0;
// 1 if the scroll is moving
volatile char p2_plex_scroller_moving = 0;
// Number of columns shown of the logo
volatile char p2_logo_reveal_idx = 0;
// Reveals the logo column by column
void p2_logo_reveal() {
if(p2_logo_reveal_idx>=40)
p2_logo_reveal_done = 1;
else
vsp_update_screen(p2_logo_reveal_idx++);
}
// X-movement index
volatile char x_movement_idx = 0;
// The next char to use from the scroll text
char* volatile scroll_text_next = SCROLL_TEXT;
// Scroll the plex sprites to the left.
void plex_scroller_move() {
char x_idx = x_movement_idx;
for(char s=0; s<PLEX_COUNT;s++) {
PLEX_XPOS[s] = XMOVEMENT[x_idx];
PLEX_XPOS_MSB[s] = XMOVEMENT_HI[x_idx];
if(x_idx==0) {
// Page boundary crossed - new scroll char! Detection is a bit flaky!
// Restart scroll text of needed
if(*scroll_text_next==0x00)
scroll_text_next = SCROLL_TEXT;
// Read next char from the scroll text
char letter = *scroll_text_next++;
// If the letter is \xff then add a heart
if(letter==0xff)
letter = 0x00;
// Add the letter
PLEX_PTR[s] = SPRITE_0+letter;
}
x_idx +=8;
}
x_movement_idx++;
}
// The current frame ID (0-7)
volatile char plex_frame_id = 0;
// Pointer to the buckets of the current frame
struct BucketSprite* volatile plex_frame = BUCKET_SPRITES;
// Offset added to plex_id to ensure the sprite cycling works (decreased 1 every time a cycle is complete)
volatile char plex_id_offset = 0;
// Pointer to the current bucket of the current frame
struct BucketSprite* volatile plex_bucket = BUCKET_SPRITES;
// Index of the current bucket in the current frame (0..BUCKET_COUNT-1)
volatile char plex_bucket_id = 0;
// Inititialize plex frame and show first bucket
__interrupt void irq_swing_top() {
//*BORDER_COLOR = DARK_GREY;
//VICII->BORDER_COLOR++;
// Initialize the multiplexer frame
plexFrameStart();
plex_bucket = plex_frame;
plex_bucket_id = 0;
// Show the first bucket
plexBucketShow(plex_bucket);
// Move forward to the next bucket
plex_bucket += BUCKET_SIZE;
plex_bucket_id++;
// Acknowledge the IRQ
*IRQ_STATUS = IRQ_RASTER;
// Set up the VSP IRQ
*HARDWARE_IRQ = &irq_swing_vsp;
*RASTER = IRQ_SWING_VSP_LINE;
//*BORDER_COLOR = BLACK;
//VICII->BORDER_COLOR--;
}
// The fine scroll (0-7)
volatile char vsp_fine_scroll;
// The coarse scroll (0-40)
volatile char vsp_coarse_scroll;
// Show sprites from the multiplexer, rescheduling the IRQ as many times as needed
__interrupt void irq_swing_vsp() {
// Perform VSP scrolling of the screen (must be the first call in the IRQ)
vsp_perform();
// Set BMM
VICII->CONTROL1 |= VICII_BMM;
// Set fine scroll (and MCM)
VICII->CONTROL2 = vsp_fine_scroll | VICII_MCM;
//*BORDER_COLOR = DARK_GREY;
// Acknowledge the IRQ
*IRQ_STATUS = IRQ_RASTER;
// Set up the PLEX IRQ (handles the rest of the multiplexer buckets)
*HARDWARE_IRQ = &irq_swing_plex;
*RASTER = BUCKET_YPOS[1];
//*BORDER_COLOR = BLACK;
}
// Index into the VSP sinus value
volatile char vsp_sin_idx = 0x40;
// Index into the sprite color sequence
volatile char sprite_color_idx = 0;
// Show sprites from the multiplexer, rescheduling the IRQ for each bucket
__interrupt void irq_swing_plex() {
//*BORDER_COLOR = DARK_GREY;
// Show the bucket
plexBucketShow(plex_bucket);
// Move forward to the next bucket
plex_bucket += BUCKET_SIZE;
plex_bucket_id++;
if(plex_bucket_id<BUCKET_COUNT) {
// Not done with the frame yet - set up the next PLEX IRQ (handles the rest of the multiplexer buckets)
*HARDWARE_IRQ = &irq_swing_plex;
*RASTER = BUCKET_YPOS[plex_bucket_id];
//*BORDER_COLOR = BLACK;
} else {
// We are done with this frame - finish it and perform other stuff!
//VICII->BORDER_COLOR = RED;
// Set up the TOP IRQ
*HARDWARE_IRQ = &irq_swing_top;
*RASTER = BUCKET_YPOS[0];
// Move to the next frame of the plexer
const char YMOVE = 3;
plex_frame_id += YMOVE;
plex_frame += (unsigned int)YMOVE*BUCKET_COUNT*BUCKET_SIZE;
if(plex_frame_id>=FRAME_COUNT) {
// Reset to start of cycle
plex_frame -= BUCKET_COUNT*BUCKET_SIZE*FRAME_COUNT;
plex_frame_id -= FRAME_COUNT;
// And change the PLEX ID offset
plex_id_offset--;
}
// Update plex_id in the next frame
//VICII->BORDER_COLOR = BLUE;
update_frame_plex_id_offset(plex_frame_id);
if(p2_logo_swinging) {
// Update the VSP value with a sinus
unsigned int scroll = VSP_SINTABLE[(unsigned int)(vsp_sin_idx++)];
vsp_fine_scroll = (char)scroll&7;
char new_coarse_scroll = (char)(scroll/8);
// Update the VSP value with a sinus
char coarse_scroll_diff = vsp_coarse_scroll - new_coarse_scroll;
// Update screen column (if needed)
if(coarse_scroll_diff==0x01) {
// Moving left - put a new column at the right border
char x_offset = 0x50-vsp_coarse_scroll;
// Only move 24 - because the last line is empty (and holds sprite pointers)
vsp_update_screen(x_offset);
} else if(coarse_scroll_diff==0xff) {
// Moving right - put a new column at the left border
char x_offset = 0x27-vsp_coarse_scroll;
vsp_update_screen(x_offset);
// Clear line 25 - because the start of the last line was over-written by line #24 chars 40-80
(PART2_SCREEN+24*40)[x_offset] = 0; //(LOGO_DATA_SCREEN+24*80)[x_offset];
(COLS+24*40)[x_offset] = 0; //(LOGO_DATA_COLORS+24*80)[x_offset];
}
vsp_coarse_scroll = new_coarse_scroll;
vsp_scroll = 40-vsp_coarse_scroll;
}
// Move the sprites (X-position & new scroll chars)
if(p2_plex_scroller_moving) {
plex_scroller_move();
// Change sprite colors
if(++sprite_color_idx == sizeof(SPRITE_COLOR_SEQUENCE)) sprite_color_idx = 0;
for(char s=0;s<8;s++)
SPRITES_COLOR[s] = SPRITE_COLOR_SEQUENCE[sprite_color_idx];
}
// Signal the main routine
p2_work_ready = 1;
}
// Acknowledge the IRQ
*IRQ_STATUS = IRQ_RASTER;
}
// Update the plex_id's of a multiplexer frame to reflect a specific plex_id_offset
void update_frame_plex_id_offset(char plex_frame_id) {
unsigned int* jmp_table = (unsigned int*)PLEX_ID_UPDATERS;
unsigned int jmp_address = jmp_table[plex_frame_id];
kickasm(uses jmp_address) {{
lda jmp_address
sta call+1
lda jmp_address+1
sta call+2
call: jsr $0000
}}
}
// Unrolled ASM to update plex_id_offset for FRAMES 0-7
#pragma data_seg(InitPart2)
char PLEX_ID_UPDATERS_CRUNCHED[] = kickasm(uses ORIGINAL_BUCKET_SPRITES, uses BUCKET_SPRITES, uses plex_id_offset) {{
.modify B2() {
.pc = PLEX_ID_UPDATERS "PLEX_ID_UPDATERS"
// First generate a jump table
.for(var frame=0;frame<8;frame++)
.word updaters[frame].updater
// Generate the 8 unrolled updaters
updaters:
.for(var frame=0;frame<8;frame++) {
updater:
ldx #$1f
.for(var sprite=0; sprite<9*8; sprite++ ) {
lda ORIGINAL_BUCKET_SPRITES + frame*8*9*2 + sprite*2 +1
clc
adc plex_id_offset
sax BUCKET_SPRITES + frame*8*9*2 + sprite*2 +1
}
rts
}
}
}};
#pragma data_seg(DataPart2)
// Update screen, colors and bitmap with a single column of new data
// - x_offset is the offset of the column to update (0-79)
void vsp_update_screen(__ma char x_offset) {
// Update screen and colors
kickasm(uses x_offset, uses PART2_SCREEN, uses COLS,uses LOGO_DATA, uses LOGO_DATA_COLORS, clobbers "AX") {{
ldx x_offset
.for(var row=0;row<24;row++) {
lda LOGO_DATA+80*row,x
sta PART2_SCREEN+40*row,x
lda LOGO_DATA_COLORS+80*row,x
sta COLS+40*row,x
}
}}
// Disable I/O (BITMAP is below I/O)
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_ALL;
// Update bitmap (using 3 routines to handle all bitmap columns)
unsigned int x_offset8 = (unsigned int)x_offset*8;
if(>x_offset8 == 0) {
kickasm(uses x_offset, uses x_offset8, uses PART2_BITMAP, uses LOGO_DATA_BITMAP, clobbers "AXY") {{
ldx x_offset
ldy x_offset8
.for(var row=0;row<24;row++)
.for(var pix=0;pix<8;pix++) {
lda LOGO_DATA_BITMAP+80*(row*8+pix),x
sta PART2_BITMAP+row*40*8+pix,y
}
}}
} else if(>x_offset8 == 1) {
kickasm(uses x_offset, uses x_offset8, uses PART2_BITMAP, uses LOGO_DATA_BITMAP, clobbers "AXY") {{
ldx x_offset
ldy x_offset8
.for(var row=0;row<24;row++)
.for(var pix=0;pix<8;pix++) {
lda LOGO_DATA_BITMAP+80*(row*8+pix),x
sta PART2_BITMAP+$100+row*40*8+pix,y
}
}}
} else { // >x_offset8 == 2
kickasm(uses x_offset, uses x_offset8, uses PART2_BITMAP, uses LOGO_DATA_BITMAP, clobbers "AXY") {{
ldx x_offset
ldy x_offset8
.for(var row=0;row<24;row++)
.for(var pix=0;pix<8;pix++) {
lda LOGO_DATA_BITMAP+80*(row*8+pix),x
sta PART2_BITMAP+$200+row*40*8+pix,y
}
}}
}
*PROCPORT_DDR = PROCPORT_DDR_MEMORY_MASK;
*PROCPORT = PROCPORT_RAM_IO;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB