/*============================================================================
MiSTer test harness OS - Main application
Author: Jim Gregory - https://github.com/JimmyStones/
Version: 0.1
Date: 2021-06-29
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see .
===========================================================================*/
#include
#include
#include
#include "sys.c"
#include "ui.c"
#include "ps2.c"
bool hsync;
bool hsync_last;
bool vsync;
bool vsync_last;
// Application state
#define STATE_START_INPUTTESTER 0
#define STATE_INPUTTESTER 1
#define STATE_START_INPUTTESTERADV 2
#define STATE_INPUTTESTERADV 3
#define STATE_FADEOUT 8
#define STATE_START_FADEIN 9
#define STATE_FADEIN 10
#define STATE_START_ATTRACT 16
#define STATE_ATTRACT 17
#define STATE_START_GAME 24
#define STATE_GAME 25
char state = STATE_START_INPUTTESTERADV;
char nextstate = STATE_START_INPUTTESTERADV;
// SNEK variables
unsigned char movefreqinit = 14;
unsigned char movefreqdecfreq = 200;
unsigned char movefreqdectimer = 0;
unsigned char movefreq = 0;
unsigned char movetimer = 0;
signed int x = 20;
signed int y = 15;
signed char xd = 0;
signed char yd = 1;
signed char nxd = 0;
signed char nyd = 1;
unsigned int length = 0;
unsigned char playerchar = 83;
// Fade in/out variables
unsigned char fade = 0;
unsigned char fadetimer = 0;
unsigned char fadefreq = 4;
// Attract mode variables
unsigned char attractstate = 0;
// Input tester variables
unsigned char __at(0xC200) joystick_last[12];
signed char __at(0xC210) ax_last[6];
signed char __at(0xC220) ay_last[6];
unsigned char __at(0xC230) px_last[6];
signed char __at(0xC240) sx_toggle_last[6];
signed char __at(0xC250) sx_last[6];
unsigned long __at(0xC260) sx_pos[6];
unsigned char con_x; // Console cursor X position
unsigned char con_y; // Console cursor X position
unsigned char con_l = 2; // Console left edge X
unsigned char con_t = 20; // Console top edge Y
unsigned char con_r = 37; // Console right edge X
unsigned char con_b = 37; // Console bottom edge Y
bool con_cursor;
unsigned char con_cursortimer = 1;
unsigned char con_cursorfreq = 30;
// DPAD tracker
bool bdown_left = 0;
bool bdown_left_last = 0;
bool bdown_right = 0;
bool bdown_right_last = 0;
bool bdown_up = 0;
bool bdown_up_last = 0;
bool bdown_down = 0;
bool bdown_down_last = 0;
char history[4];
// Draw static elements for input test page
void page_inputtester_adv()
{
clear_chars(0);
page_border(0b00000111);
write_string("- MiSTer Input Tester -", 0b11100011, 8, 1);
write_string("RLDUABXYLRsSCZ", 0xFF, 7, 3);
write_string("AX", 0xFF, 26, 3);
write_string("AY", 0xFF, 31, 3);
write_string("POS", 0xFF, 7, 11);
write_string("SPD POS", 0xFF, 18, 11);
char label[5];
for (unsigned char j = 0; j < 6; j++)
{
sprintf(label, "JOY%d", j + 1);
write_string(label, 0xFF - (j * 2), 2, 4 + j);
sprintf(label, "PAD%d", j + 1);
write_string(label, 0xFF - (j * 2), 2, 12 + j);
sprintf(label, "SPN%d", j + 1);
write_string(label, 0xFF - (j * 2), 14, 12 + j);
}
write_string("CON", 0xFF, 2, 19);
}
void reset_inputstates()
{
for (char i = 0; i < 12; i++)
{
joystick_last[i] = 1;
}
for (char i = 0; i < 6; i++)
{
ax_last[i] = 1;
ay_last[i] = 1;
px_last[i] = 1;
sx_toggle_last[i] = 1;
sx_last[i] = 1;
sx_pos[i] = 0;
}
}
// Initialise advanced inputtester state and draw static elements
void start_inputtester_adv()
{
state = STATE_INPUTTESTERADV;
// Draw page
page_inputtester_adv();
// Reset console cursor
con_x = con_l;
con_y = con_t;
// Reset last states for inputs
reset_inputstates();
}
// Initialise fadeout state
void start_fadeout()
{
state = STATE_FADEOUT;
fadetimer = fadefreq;
fade = 0;
}
// Initialise fadein state
void start_fadein()
{
state = STATE_FADEIN;
fadetimer = fadefreq;
fade = 15;
}
// Initialise attract state and draw static elements
void start_attract()
{
state = STATE_ATTRACT;
attractstate = 0;
clear_chars(0);
page_border(0b00000111);
write_string("SNEK", 0b00000111, 18, 0);
movefreq = 5;
movetimer = 1;
}
// Initialise attract state and draw static elements
void start_gameplay()
{
state = STATE_GAME;
length = 0;
x = 20;
y = 15;
xd = 0;
yd = 1;
nxd = 0;
nyd = 1;
clear_chars(0);
page_border(0b00000111);
write_string("SNEK", 0b00000111, 18, 0);
write_char(playerchar, 0xFF, x, y);
movefreq = movefreqinit;
movefreqdectimer = movefreqdecfreq;
movetimer = movefreq;
}
// Fade out state
void fadeout()
{
if (vsync && !vsync_last)
{
fadetimer--;
if (fadetimer == 0)
{
box(fade, fade, 39 - fade, 29 - fade, 127, 0b0000111);
fadetimer = fadefreq;
fade++;
if (fade == 16)
{
start_fadein();
}
}
}
}
// Fade in state
void fadein()
{
if (vsync && !vsync_last)
{
fadetimer--;
if (fadetimer == 0)
{
box(fade, fade, 39 - fade, 29 - fade, 0, 0b0000000);
fadetimer = fadefreq;
fade--;
if (fade == 0)
{
state = nextstate;
}
}
}
}
// Rotate DPAD direction history and push new entry
void pushhistory(char new)
{
for (char h = 1; h < 4; h++)
{
history[h - 1] = history[h];
}
history[3] = new;
}
void handle_codes()
{
// Track input history of P1 DPAD for secret codes!
bdown_up_last = bdown_up;
bdown_down_last = bdown_down;
bdown_left_last = bdown_left;
bdown_right_last = bdown_right;
bdown_up = CHECK_BIT(joystick[0], 3);
bdown_down = CHECK_BIT(joystick[0], 2);
bdown_left = CHECK_BIT(joystick[0], 1);
bdown_right = CHECK_BIT(joystick[0], 0);
if (!bdown_up && bdown_up_last)
{
pushhistory(1);
}
if (!bdown_down && bdown_down_last)
{
pushhistory(2);
}
if (!bdown_left && bdown_left_last)
{
pushhistory(3);
}
if (!bdown_right && bdown_right_last)
{
pushhistory(4);
}
// Check for SNEK code
if (history[0] == 4 && history[1] == 2 && history[2] == 3 && history[3] == 1)
{
nextstate = STATE_START_ATTRACT;
pushhistory(0);
start_fadeout();
return;
}
}
// Input tester state
void inputtester_adv()
{
// Handle PS/2 inputs whenever possible to improve latency
handle_ps2();
// Handle secret code detection (joypad 1 directions)
if (hsync && !hsync_last)
{
handle_codes();
}
// As soon as vsync is detected start drawing screen updates
if (vsync && !vsync_last)
{
// Draw joystick inputs (only update each byte if value has changed)
for (char inputindex = 0; inputindex < 6; inputindex++)
{
char m = 0b00000001;
char x = 6;
char y = 4 + inputindex;
char inputoffset = (inputindex * 32);
char lastoffset = (inputindex * 2);
for (char b = 0; b < 2; b++)
{
char index = (b * 8) + inputoffset;
char lastindex = b + lastoffset;
char joy = joystick[index];
if (joy != joystick_last[lastindex])
{
m = 0b00000001;
for (char i = 0; i < 8; i++)
{
x++;
write_char((joy & m) ? asc_1 : asc_0, 0xFF, x, y);
m <<= 1;
}
}
else
{
x += 8;
}
joystick_last[lastindex] = joy;
}
// Draw analog inputs (only update if value has changed)
signed char ax = analog[(inputindex * 16)];
signed char ay = analog[(inputindex * 16) + 8];
if (ax != ax_last[inputindex] || ay != ay_last[inputindex])
{
char stra[10];
sprintf(stra, "%4d %4d", ax, ay);
write_string(stra, 0xFF, 24, 4 + inputindex);
}
ax_last[inputindex] = ax;
ay_last[inputindex] = ay;
// Draw paddle inputs (only update if value has changed)
unsigned char px = paddle[(inputindex * 8)];
if (px != px_last[inputindex])
{
char strp[5];
sprintf(strp, "%4d", px);
write_string(strp, 0xFF, 6, 12 + inputindex);
}
px_last[inputindex] = px;
// Draw spinner inputs (only update when update clock changes)
bool sx_toggle = CHECK_BIT(spinner[(inputindex * 16) + 8], 0);
signed char sx = spinner[(inputindex * 16)];
if (sx_toggle != sx_toggle_last[inputindex])
{
sx_pos[inputindex] += sx;
write_stringf("%4d", 0xFF, 22, 12 + inputindex, sx_pos[inputindex] / 16);
}
else
{
sx = 0;
}
if (sx_last[inputindex] != sx)
{
write_stringfs("%4d", 0xFF, 17, 12 + inputindex, sx);
}
sx_last[inputindex] = sx;
sx_toggle_last[inputindex] = sx_toggle;
}
// Keyboard test console
if (kbd_buffer_len > 0)
{
// Clear existing cursor if visible
if (con_cursor)
{
write_char(' ', 0xFF, con_x, con_y);
}
// Write characters in buffer
for (char k = 0; k < kbd_buffer_len; k++)
{
if (kbd_buffer[k] == '\n')
{
// New line
con_x = con_l;
con_y++;
if (con_y > con_b)
{
// Wrap to top
con_y = con_t;
}
}
else if (kbd_buffer[k] == '\b')
{
// Backspace - only if not at beginning of line
if (con_x > con_l)
{
con_x--;
// Clear existing character
write_char(' ', 0xFF, con_x, con_y);
}
}
else
{
// Write character
write_char(kbd_buffer[k], 0xFF, con_x, con_y);
// Move cursor right
con_x++;
if (con_x > con_r)
{
// New line
con_x = con_l;
con_y++;
if (con_y > con_b)
{
// Wrap to top
con_y = con_t;
}
}
}
}
// Clear buffer and enable cursor
kbd_buffer_len = 0;
con_cursor = 0;
con_cursortimer = 1;
}
// Cursor blink timer
con_cursortimer--;
if (con_cursortimer <= 0)
{
con_cursor = !con_cursor;
con_cursortimer = con_cursorfreq;
write_char(con_cursor ? '|' : ' ', 0xFF, con_x, con_y);
}
}
}
// SNEK - gameplay state
void gameplay()
{
if (hsync && !hsync_last)
{
if (yd != 1 && joystick[0] & 0b00001000) // up
{
nyd = -1;
nxd = 0;
}
if (yd != -1 && joystick[0] & 0b00000100) // down
{
nyd = 1;
nxd = 0;
}
if (xd != 1 && joystick[0] & 0b00000010) // left
{
nxd = -1;
nyd = 0;
}
if (xd != -1 && joystick[0] & 0b00000001) //right
{
nxd = 1;
nyd = 0;
}
if (CHECK_BIT(joystick[8], 2)) // select to quit
{
start_inputtester_adv();
return;
}
}
if (vsync && !vsync_last)
{
movetimer--;
if (movetimer == 0)
{
write_char(127, 0x66, x, y);
xd = nxd;
yd = nyd;
x += xd;
y += yd;
unsigned int p = (y * chram_cols) + x;
if (chram[p] > 0)
{
nextstate = 4;
start_fadeout();
return;
}
length++;
write_char(playerchar, 0xFF, x, y);
movetimer = movefreq;
char score[5];
sprintf(score, "%4d", length);
write_string(score, 0xFF, 35, 0);
}
movefreqdectimer--;
if (movefreqdectimer == 0)
{
movefreqdectimer = movefreqdecfreq;
if (movefreq > 3)
{
movefreq--;
}
char str_movefreq[3];
sprintf(str_movefreq, "%4d", movefreq);
write_string(str_movefreq, 0xFF, 35, 29);
}
}
}
// SNEK - attract state
void attract()
{
if (hsync && !hsync_last)
{
if (CHECK_BIT(joystick[8], 3)) // start
{
start_gameplay();
}
}
if (vsync && !vsync_last)
{
movetimer--;
if (movetimer == 0)
{
attractstate = !attractstate;
write_string("PRESS START", attractstate == 0 ? 0x00 : 0xFF, 16, 15);
movetimer = movefreq;
}
}
}
// Main entry and state machine
void main()
{
chram_size = chram_cols * chram_rows;
while (1)
{
hsync = input0 & 0x80;
vsync = input0 & 0x40;
switch (state)
{
case STATE_START_INPUTTESTERADV:
start_inputtester_adv();
break;
case STATE_INPUTTESTERADV:
inputtester_adv();
break;
case STATE_FADEOUT:
fadeout();
break;
case STATE_FADEIN:
fadein();
break;
case STATE_START_ATTRACT:
start_attract();
break;
case STATE_ATTRACT:
attract();
break;
case STATE_START_GAME:
start_gameplay();
break;
case STATE_GAME:
gameplay();
break;
default:
break;
}
hsync_last = hsync;
vsync_last = vsync;
}
}