misc-scripts/c64-schedule.php
File Type: text/x-php
<?php
// Path to WordPress wp-load.php
require_once 'wp-load.php';
$debug_data = array();
//List of C64 basic keyword tokens
$tokens = array(
'AND' => chr(0xaf),
'ATN' => chr(0xc1),
'CHR$' => chr(0xc7),
'CLOSE' => chr(0xa0),
'CLR' => chr(0x9c),
'CMD' => chr(0x9d),
'CONT' => chr(0x9a),
'COS' => chr(0xbe),
'DATA' => chr(0x83),
'DEF' => chr(0x96),
'DIM' => chr(0x86),
'END' => chr(0x80),
'FN' => chr(0xa5),
'FOR' => chr(0x81),
'FRE' => chr(0xb8),
'GET' => chr(0xa1),
'GOSUB' => chr(0x8d),
'GOTO' => chr(0x89),
'IF' => chr(0x8b),
'INPUT' => chr(0x85),
'INT' => chr(0xb5),
'LEFT$' => chr(0xc8),
'LEN' => chr(0xc3),
'LET' => chr(0x88),
'LIST' => chr(0x9b),
'LOAD' => chr(0x93),
'LOG' => chr(0xbc),
'MID$' => chr(0xca),
'NEW' => chr(0xa2),
'NEXT' => chr(0x82),
'NOT' => chr(0xa8),
'ON' => chr(0x91),
'OPEN' => chr(0x9f),
'OR' => chr(0xb0),
'PEEK' => chr(0xc2),
'POKE' => chr(0x97),
'POS' => chr(0xb9),
'PRINT' => chr(0x99),
'READ' => chr(0x87),
'REM' => chr(0x8f),
'RESTORE' => chr(0x8c),
'RETURN' => chr(0x8e),
'RIGHT$' => chr(0xc9),
'RND' => chr(0xbb),
'RUN' => chr(0x8a),
'SAVE' => chr(0x94),
'SGN' => chr(0xb4),
'SIN' => chr(0xbf),
'SPC(' => chr(0xa6),
'SQR' => chr(0xba),
'STEP' => chr(0xa9),
'STOP' => chr(0x90),
'STR$' => chr(0xc4),
'SYS' => chr(0x9e),
'TAB(' => chr(0xa3),
'TAN' => chr(0xc0),
'THEN' => chr(0xa7),
'TO' => chr(0xa4),
'USR' => chr(0xb7),
'VAL' => chr(0xc5),
'VERIFY' => chr(0x95),
'WAIT' => chr(0x92),
'ABS' => chr(0xb6),
// Symbols
'+' => chr(0xaa),
'-' => chr(0xab),
'*' => chr(0xac),
'/' => chr(0xad),
'^' => chr(0xae),
'=' => chr(0xb2),
'<' => chr(0xb3),
'>' => chr(0xb1),
);
uksort($tokens, function($a,$b) { return strlen($b) <=> strlen($a); });
$control_codes = array(
'REVON' => chr(0x12),
'REVOFF' => chr(0x92),
'CLR' => chr(0x93),
'HOME' => chr(0x13)
);
// Commodore 64 color palette in RGB format
$rgbColors = [
[0, 0, 0], // Black
[255, 255, 255], // White
[136, 0, 0], // Red
[170, 255, 238], // Cyan
[204, 68, 204], // Purple
[0, 204, 85], // Green
[0, 0, 170], // Blue
[238, 238, 119], // Yellow
[221, 136, 85], // Orange
[102, 68, 0], // Brown
[255, 119, 119], // Light Red
[51, 51, 51], // Dark Grey
[119, 119, 119], // Grey
[170, 255, 102], // Light Green
[0, 136, 255], // Light Blue
[187, 187, 187] // Light Grey
];
// Commodore 64 color control codes
$colorControlCodes = [
chr(0x90), // (0) Black
chr(0x05), // (1) White
chr(0x1c), // (2) Red
chr(0x9f), // (3) Cyan
chr(0x9c), // (4) Purple
chr(0x1e), // (5) Green
chr(0x1f), // (6) Blue
chr(0x9e), // (7) Yellow
chr(0x81), // (8) Orange
chr(0x95), // (9) Brown
chr(0x96), // (10) Light Red
chr(0x97), // (11) Dark Grey
chr(0x98), // (12) Grey
chr(0x99), // (13) Light Green
chr(0x9a), // (14) Light Blue
chr(0x9b) // (15) Light Grey
];
$event_type_colors = [
83 => 14, //Free Concerts
102 => 7, //Beer Releases
105 => 3, //Benefit Concerts
80 => 9, //Breakfast
76 => 8, //Brew Fest
141 => 5, //Christmas Party
84 => 13, //Community Event
135 => 14,//Cover Concert
75 => 5, //Festival
95 => 5, //Festival Concert
71 => 2, //Food Guest
72 => 8, //Local Market
132 => 7, //Movie Night
78 => 9, //Off-Site
87 => 4, //Party in the Woods
140 => 7, //Sports Ball
122 => 4, //Tap room party
137 => 15,//Theater Performance
126 => 7, //Ticketed Concert
];
// Custom post type (adjust as necessary)
$post_type = 'ajde_events';
// Filter by date (e.g., last 30 days)
$now = date('Y-m-d', strtotime('-4 hours'));
// Query posts
$args = array(
'post_type' => $post_type,
'posts_per_page' => -1, // Fetch all posts
'meta_query' => array(
array(
'key' => 'evcal_erow',
'value' => strtotime($now),
'compare' => '>=',
),
array(
'key' => 'evcal_srow',
'value' => strtotime("last monday +1 week", strtotime($now)),
'compare' => '<',
),
),
'meta_key' => 'evcal_srow',
'orderby' => 'meta_value_num',
'order' => 'ASC',
/*
'tax_query' => array(
array (
'taxonomy' => 'event_type',
'field' => 'term_id',
'terms' => '71',
'operator' => 'NOT IN',
)
),
*/
);
$query = new WP_Query($args);
// Prepare data array
$events = array();
$foodguests = array();
if ($query->have_posts()) :
while ($query->have_posts()) : $query->the_post();
$id=get_the_ID();
$event_types = get_the_terms($id, 'event_type');
if( is_array($event_types) ) {
$tid = $event_types[0]->term_id;
$tname = $event_types[0]->name;
} else {
$tid = 0;
$tname = "";
}
//sanitize the crap out of the content
$content = wp_strip_all_tags(str_replace("<br>",PHP_EOL,get_the_content()));
$content = iconv('UTF-8', 'ASCII//TRANSLIT', $content);
$content = str_replace("&","&",$content);
$content = str_replace(" "," ",$content);
$content = str_replace("’","'",$content);
$event = array(
'title' => get_the_title(),
'content' => $content,
'start' => get_post_meta($id,'evcal_srow',true),
'end' => get_post_meta($id,'evcal_erow',true),
'status' => get_post_meta($id,'_status',true),
'cancelreason' => get_post_meta($id,'_cancel_reason',true),
'type' => $tname,
'color' => array_key_exists($tid, $event_type_colors) ? $event_type_colors[ $tid ] : 1,
);
if ( $tid != 71 )
$events[] = $event;
else
$foodguests[] = $event;
$debug_data[] = array_key_exists($tid, $event_type_colors) ? $event_type_colors[ $tid ] : 1;
endwhile;
endif;
wp_reset_postdata();
function hexColorToC64($hex) {
// Convert hex to RGB
$hex = str_replace("#","",$hex);
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
$closestColor = null;
$minDistance = PHP_INT_MAX;
foreach ($rgbColors as $index => $color) {
$distance = sqrt(
pow($r - $color[0], 2) +
pow($g - $color[1], 2) +
pow($b - $color[2], 2)
);
if ($distance < $minDistance) {
$minDistance = $distance;
$closestColor = $index;
}
}
return $closestColor || 1;
}
// Function to convert ASCII to PETSCII - must be actual ASCII string, not Unicode crap!
function asciiToPetscii($ascii) {
global $control_codes;
$petscii = '';
for ($i = 0; $i < strlen($ascii); $i++) {
$char = ord($ascii[$i]);
if ($char >= 1 && $char <= 31) { // Control codes
if(in_array(chr($char),$control_codes)) {
$petscii .= chr($char);
}
} elseif ($char >= 32 && $char <= 64) { // Misc printable symbols
$petscii .= chr($char);
} elseif ($char >= 65 && $char <= 90) { // Uppercase letters
$petscii .= chr($char + 32);
} elseif ($char >= 91 && $char <= 96) { // Misc printable symbols
$petscii .= chr($char);
} elseif ($char >= 97 && $char <= 122) { // Lowercase letters
$petscii .= chr($char - 32);
} elseif ($char >= 123 && $char <= 127) { // Misc printable symbols
$petscii .= chr($char);
} elseif ($char >= 128 && $char <= 159) { // Control codes
if(in_array(chr($char),$control_codes)) {
$petscii .= chr($char);
}
} elseif ($char >= 160 && $char <= 255) { // Misc printable symbols
$petscii .= chr($char);
} else {
//ascii number out of range, do nothing
}
}
return $petscii;
}
// Tokenize BASIC keywords function
function tokenize($text) {
global $tokens;
$result = '';
$quotemode = 0;
//Thank goodness the C64 doesn't do nested quotes.
$splitbyquotes = explode('"',$text);
foreach ($splitbyquotes as $section) {
if($quotemode == 0) {
foreach ($tokens as $word => $token) {
$section = str_replace($word, $token, $section);
}
$result .= $section;
} else {
$result .= '"' . asciiToPetscii($section) . '"';
}
$quotemode = 1 - $quotemode;
}
return $result;
}
// Function to encode line numbers as pointers
function encodeLine($lineNumber, $content, &$currentAddress) {
$nextLinePointer = pack('v', 0); // Placeholder, updated later
$lineNumberBytes = pack('v', $lineNumber); // Little-endian
$tokens = tokenize($content);
$lineContent = $lineNumberBytes . $tokens . chr(0); // line number, content, terminator
// Update current address
$lineStart = $currentAddress;
$currentAddress += strlen($lineContent) + 2; // +2 for next line pointer
// If not last line, update next line pointer
$nextLinePointer = pack('v', $currentAddress);
return $nextLinePointer . $lineContent;
}
// Convert data to BASIC program
$program = "";
$currentAddress = 0x0801; // Start address for BASIC programs
$currentLine = 10;
$dataLine = 1000; // Start with high number for DATA statements
$dataSubroutineLine = $dataLine - 100;
$maxDataLength = 39;
$maxChunks = 60;
$defaultTextColor = 1; //White
$defaultBackgroundColor = 0; //Black
$defaultBorderColor = 0; //Black
// First line (clear screen)
$program .= encodeLine($currentLine++, 'PRINT CHR$(147)', $currentAddress);
$program .= encodeLine($currentLine++, 'POKE 53272, 23 : REM CHAR SET 2', $currentAddress);
$program .= encodeLine($currentLine++, 'POKE 646, ' . $defaultTextColor . ' : REM TEXT COLOR', $currentAddress);
$program .= encodeLine($currentLine++, 'POKE 53281, '. $defaultBackgroundColor .' : REM BACKGROUND', $currentAddress);
$program .= encodeLine($currentLine++, 'POKE 53280, '. $defaultBorderColor .' : REM BORDER', $currentAddress);
//Grab DATA
$program .= encodeLine($currentLine++, 'DIM TT$('.count($events).'),DT$('.count($events).'),DE$('.count($events).','.$maxChunks.') : REM TITLE, DATE, DESCRIPTION', $currentAddress);
$program .= encodeLine($currentLine++, 'DIM CH('.count($events).') : REM CHUNKS', $currentAddress);
$program .= encodeLine($currentLine++, 'DIM CO('.count($events).') : REM COLORS', $currentAddress);
$program .= encodeLine($currentLine++, 'GOSUB '. $dataSubroutineLine, $currentAddress);
// Loop setup
$mainLoopLine = $currentLine;
$program .= encodeLine($currentLine++, 'FOR I = 1 TO ' . count($events), $currentAddress);
$program .= encodeLine($currentLine++, 'PRINT CHR$(147)', $currentAddress);
$program .= encodeLine($currentLine++, 'PRINT "'.$control_codes['REVON'].'"; SPC(ABS(20-LEN(DT$(I))/2)); DT$(I); "'.$control_codes['REVOFF'].'"', $currentAddress);
$program .= encodeLine($currentLine++, 'POKE 646, CO(I)', $currentAddress);
$program .= encodeLine($currentLine++, 'POKE 53280, CO(I)', $currentAddress);
$program .= encodeLine($currentLine++, 'PRINT "'.$control_codes['REVON'].'"; SPC(ABS(20-LEN(TT$(I))/2)); TT$(I); "'.$control_codes['REVOFF'].'"', $currentAddress);
$program .= encodeLine($currentLine++, 'POKE 646, 1', $currentAddress);
$program .= encodeLine($currentLine++, 'PRINT', $currentAddress);
$program .= encodeLine($currentLine++, 'FOR J = 1 TO CH(I)', $currentAddress);
$program .= encodeLine($currentLine++, 'FOR C = 1 TO LEN(DE$(I,J))', $currentAddress);
$program .= encodeLine($currentLine++, 'PRINT MID$(DE$(I,J),C,1);', $currentAddress);
$program .= encodeLine($currentLine++, 'FOR T = 1 TO 10 : NEXT : REM CHAR DELAY', $currentAddress);
$program .= encodeLine($currentLine++, 'NEXT C : PRINT', $currentAddress);
$program .= encodeLine($currentLine++, 'FOR T = 1 TO 200 : NEXT : REM LINE DELAY', $currentAddress);
$program .= encodeLine($currentLine++, 'NEXT J', $currentAddress);
$program .= encodeLine($currentLine++, 'FOR T = 1 TO 5000 : NEXT : REM EVENT DELAY', $currentAddress);
$program .= encodeLine($currentLine++, 'NEXT I', $currentAddress);
$program .= encodeLine($currentLine++, 'GOTO ' . $mainLoopLine, $currentAddress);
// Data reader subroutine
$currentLine = $dataSubroutineLine;
$program .= encodeLine($currentLine++, 'PRINT "READING IN DATA... ";', $currentAddress);
$program .= encodeLine($currentLine++, 'FOR I = 1 TO ' . count($events), $currentAddress);
$program .= encodeLine($currentLine++, 'READ DT$(I), CO(I), TT$(I), CH(I)', $currentAddress);
$program .= encodeLine($currentLine++, 'FOR J = 1 TO CH(I)', $currentAddress);
$program .= encodeLine($currentLine++, 'READ DE$(I, J)', $currentAddress);
$program .= encodeLine($currentLine++, 'NEXT J', $currentAddress);
$program .= encodeLine($currentLine++, 'NEXT I', $currentAddress);
$program .= encodeLine($currentLine++, 'PRINT "DONE"', $currentAddress);
$program .= encodeLine($currentLine++, 'RETURN', $currentAddress);
// Data lines
$currentLine = $dataLine;
foreach ($events as $event) {
if( date('l, F jS',$event['start']) == date('l, F jS',$event['end']) ) {
$program .= encodeLine($currentLine++, 'DATA "' . date('l, F jS',$event['start']) . '"', $currentAddress);
} else {
$program .= encodeLine($currentLine++, 'DATA "' . date('F jS',$event['start']) . ' - ' . date('F jS',$event['end']) . '"', $currentAddress);
}
$program .= encodeLine($currentLine++, 'DATA ' . $event['color'] . ' : REM COLOR', $currentAddress);
$program .= encodeLine($currentLine++, 'DATA "' . $event['title'] . '"', $currentAddress);
if ( $event['status'] == "cancelled" ) {
$event['content'] = "CANCELLED";
if ( $event['cancelreason'] ) {
$event['content'] .= PHP_EOL . PHP_EOL . $event['cancelreason'];
}
}
foreach ($foodguests as $foodguest) {
if( date('F jS', $event['start']) == date('F jS', $foodguest['start']) ) {
$event['content'] = $colorControlCodes[$foodguest['color']] . "Food Guest: " . $foodguest['title'] . PHP_EOL . PHP_EOL . $event['content'];
}
}
//Split the description up here
$content_wrapped = wordwrap(str_replace("\"","''",preg_replace('/(' . PHP_EOL . '){2,}/', PHP_EOL . PHP_EOL, $event['content'])),$maxDataLength,PHP_EOL, true);
$content_chunks = array_slice(explode(PHP_EOL,$content_wrapped), 0, $maxChunks);
$program .= encodeLine($currentLine++, 'DATA ' . count($content_chunks) . " : REM CHUNKS", $currentAddress);
foreach ($content_chunks as $chunk) {
$program .= encodeLine($currentLine++, 'DATA "' . $chunk . '"', $currentAddress);
}
}
// Add final $0000 pointer to signify end of program
$program .= pack('v', 0);
// Commodore 64 PRG file structure starts with two bytes:
// 1. Load address low byte
// 2. Load address high byte
$load_address = 0x0801; // Default BASIC start address in C64
$prg_file = pack('v', $load_address) . $program;
// Output the PRG file
if ( is_array($debug_data) || is_object($debug_data) ) {
header('X-PHP-Debug-Data: ' . json_encode($debug_data));
} else {
header('X-PHP-Debug-Data: ' . $debug_data);
}
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="schedule.prg"');
echo $prg_file;
?>