obsolete.computer

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("&amp;","&",$content);
        $content = str_replace("&nbsp;"," ",$content);
        $content = str_replace("&#8217;","'",$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;
?>

Meta