From 8e243a435e4fe3e093d8635ff1c6b481a27b4a37 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Tue, 9 May 2023 14:54:47 -0700 Subject: [PATCH] wordle: initial Almost nothing is implemented. --- Makefile | 4 +- common/draw_font.hpp | 25 ++++ common/vdp2_func.hpp | 20 +++ smpc/input_keyboard.cpp | 17 +-- wordle/draw.cpp | 28 ++++ wordle/draw.hpp | 5 + wordle/main_saturn.cpp | 293 ++++++++++++++++++++++++++++++++++++++++ wordle/wordle.cpp | 50 +++++++ wordle/wordle.hpp | 31 +++++ 9 files changed, 458 insertions(+), 15 deletions(-) create mode 100644 common/vdp2_func.hpp create mode 100644 wordle/draw.cpp create mode 100644 wordle/draw.hpp create mode 100644 wordle/main_saturn.cpp create mode 100644 wordle/wordle.cpp create mode 100644 wordle/wordle.hpp diff --git a/Makefile b/Makefile index aeae80d..ee9116f 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,9 @@ smpc/input_keyboard.o: common/keyboard.hpp smpc/input_keyboard.elf: smpc/input_keyboard.o sh/lib1funcs.o res/dejavusansmono.font.bin.o common/keyboard.o common/draw_font.o common/palette.o -games/tetris.elf: games/tetris.o sh/lib1funcs.o +wordle/main_saturn.o: common/keyboard.hpp + +wordle/wordle.elf: wordle/main_saturn.o wordle/wordle.o wordle/draw.o sh/lib1funcs.o res/dejavusansmono.font.bin.o common/keyboard.o common/draw_font.o common/palette.o # clean diff --git a/common/draw_font.hpp b/common/draw_font.hpp index 77fe7c3..0c790f1 100644 --- a/common/draw_font.hpp +++ b/common/draw_font.hpp @@ -49,4 +49,29 @@ uint32_t horizontal_string(state const& s, return total_advance; } +template +void single_character(state const& s, + const uint32_t cmd_ix, + const T c, + const int32_t x, // in 26.6 fixed point + const int32_t y) // in 26.6 fixed point +{ + //assert(c <= s.char_code_offset); + const T c_offset = c - s._font->char_code_offset; + + glyph_bitmap const& bitmap = s._glyphs[c_offset].bitmap; + glyph_metrics const& metrics = s._glyphs[c_offset].metrics; + + if (bitmap.pitch != 0) { + vdp1.vram.cmd[cmd_ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE; + vdp1.vram.cmd[cmd_ix].LINK = 0; + vdp1.vram.cmd[cmd_ix].PMOD = PMOD__COLOR_MODE__COLOR_BANK_256; + vdp1.vram.cmd[cmd_ix].COLR = s.color_address; + vdp1.vram.cmd[cmd_ix].SRCA = SRCA(s.character_address + bitmap.offset); + vdp1.vram.cmd[cmd_ix].SIZE = SIZE__X(bitmap.pitch) | SIZE__Y(bitmap.rows); + vdp1.vram.cmd[cmd_ix].XA = (x + metrics.horiBearingX) >> 6; + vdp1.vram.cmd[cmd_ix].YA = (y - metrics.horiBearingY) >> 6; + } +} + } diff --git a/common/vdp2_func.hpp b/common/vdp2_func.hpp new file mode 100644 index 0000000..8fb81c7 --- /dev/null +++ b/common/vdp2_func.hpp @@ -0,0 +1,20 @@ +static inline void v_blank_out() { + /* + v + _____ + ____| |____ + */ + while ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) == 0); + while ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) != 0); +} + +static inline void v_blank_in() { + /* + v + _____ + ____| |____ + + */ + while ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) != 0); + while ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) == 0); +} diff --git a/smpc/input_keyboard.cpp b/smpc/input_keyboard.cpp index 52904a0..51dbb24 100644 --- a/smpc/input_keyboard.cpp +++ b/smpc/input_keyboard.cpp @@ -9,6 +9,7 @@ #include "../common/font.hpp" #include "../common/draw_font.hpp" #include "../common/palette.hpp" +#include "../common/vdp2_func.hpp" /* begin font */ @@ -197,8 +198,7 @@ void smpc_int(void) { This intback handling is oversimplified: - up to 2 controllers may be connected - - controller port 1 must be connected (could relax this) - - both controllers must be "digital pad" controllers + - multitaps are not parsed correctly */ while (oreg_ix < 31) { reg8 const& oreg = smpc.reg.oreg[oreg_ix++]; @@ -268,7 +268,7 @@ void smpc_int(void) { draw_font::horizontal_string(font_state, cmd_ix, // modified &str_num[0], - 2, + 2, qx, qy); } @@ -336,17 +336,6 @@ void v_blank_in_int() { } } -static inline void v_blank_in() { - /* - v - _____ - ____| |____ - - */ - while ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) != 0); - while ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) == 0); -} - uint32_t init_font(uint32_t top) { // 256 is the number of colors in the color palette, not the number of grays diff --git a/wordle/draw.cpp b/wordle/draw.cpp new file mode 100644 index 0000000..7ed25de --- /dev/null +++ b/wordle/draw.cpp @@ -0,0 +1,28 @@ +#include "wordle.hpp" + +namespace wordle { + namespace draw { + +void keyboard() +{ +} + +static inline void guess(struct row const& r, void (*draw_char)(uint8_t, int32_t, int32_t)) +{ + for (uint32_t i = 0; i < word_length; i++) { + uint8_t l = r.letters[i]; + + draw_char(l, 0, 0); + + break; + } +} + +void guesses(struct screen& s, void (*draw_char)(uint8_t, int32_t, int32_t)) +{ + guess(s.rows[0], draw_char); +} + +// end namespace + } +} diff --git a/wordle/draw.hpp b/wordle/draw.hpp new file mode 100644 index 0000000..734878b --- /dev/null +++ b/wordle/draw.hpp @@ -0,0 +1,5 @@ +namespace wordle { + namespace draw { + void guesses(struct screen& s, void (*draw_char)(uint8_t, int32_t, int32_t)); + } +} diff --git a/wordle/main_saturn.cpp b/wordle/main_saturn.cpp new file mode 100644 index 0000000..a794195 --- /dev/null +++ b/wordle/main_saturn.cpp @@ -0,0 +1,293 @@ +#include +#include "vdp1.h" +#include "vdp2.h" +#include "smpc.h" +#include "sh2.h" +#include "scu.h" + +#include "../common/keyboard.hpp" +#include "../common/font.hpp" +#include "../common/draw_font.hpp" +#include "../common/palette.hpp" +#include "../common/vdp2_func.hpp" + +#include "wordle.hpp" +#include "draw.hpp" + +extern void * _dejavusans_start __asm("_binary_res_dejavusansmono_font_bin_start"); + +struct draw_state { + uint32_t cmd_ix; + struct draw_font::state font; +}; + +static struct draw_state draw_state; + +static struct wordle::screen wordle_state; + +#define assert(n) if ((n) == 0) while (1); + +enum intback_fsm { + PORT_STATUS = 0, + PERIPHERAL_ID, + DATA1, + DATA2, + DATA3, + DATA4, + FSM_NEXT +}; + +struct intback_state { + int fsm; + int controller_ix; + int port_ix; +}; + +static intback_state intback; +static int oreg_ix; + +void smpc_int(void) __attribute__ ((interrupt_handler)); +void smpc_int(void) { + scu.reg.IST &= ~(IST__SMPC); + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); + + if ((smpc.reg.SR & SR__PDL) != 0) { + // PDL == 1; 1st peripheral data + oreg_ix = 0; + intback.controller_ix = 0; + intback.port_ix = 0; + intback.fsm = PORT_STATUS; + } + + int port_connected = 0; + int data_size = 0; + int peripheral_type = 0; + (void)peripheral_type; + int kbd_bits = 0; + + /* + This intback handling is oversimplified: + + - up to 2 controllers may be connected + - multitaps are not parsed correctly + */ + while (oreg_ix < 31) { + reg8 const& oreg = smpc.reg.oreg[oreg_ix++]; + switch (intback.fsm++) { + case PORT_STATUS: + port_connected = (PORT_STATUS__CONNECTORS(oreg) == 1); + if (port_connected) { + assert(PORT_STATUS__MULTITAP_ID(oreg) == 0xf); + } else { + intback.fsm = FSM_NEXT; + } + break; + case PERIPHERAL_ID: + peripheral_type = PERIPHERAL_ID__TYPE(oreg); + data_size = PERIPHERAL_ID__DATA_SIZE(oreg); + break; + case DATA1: + { + //controller_state& c = intback.controller[intback.controller_ix]; + } + break; + case DATA2: + break; + case DATA3: + kbd_bits = oreg & 0b1111; + break; + case DATA4: + { + uint32_t keysym = oreg; + + if (kbd_bits & 0b1000) { // Make + enum keysym k = scancode_to_keysym(keysym); + char16_t c = keysym_to_char16(k); + (void)c; + + } else if (kbd_bits & 0b0001) { // Break + + } + } + break; + default: + assert(0); + break; + } + + if ((intback.fsm >= PERIPHERAL_ID && (data_size--) < 0) || intback.fsm == FSM_NEXT) { + if (intback.port_ix == 1) + break; + else { + intback.port_ix++; + intback.controller_ix++; + intback.fsm = PORT_STATUS; + } + } + } + + if ((smpc.reg.SR & SR__NPE) != 0) { + smpc.reg.ireg[0] = INTBACK__IREG0__CONTINUE; + } else { + smpc.reg.ireg[0] = INTBACK__IREG0__BREAK; + } +} + +// rendering + +void draw_char(uint8_t c, int32_t x, int32_t y) +{ + draw_font::single_character(draw_state.font, + draw_state.cmd_ix++, + c, + (x) << 6, + (y + 30) << 6); + + vdp1.vram.cmd[draw_state.cmd_ix].CTRL = CTRL__END; +} + +void render() +{ + draw_state.cmd_ix = 2; + + + wordle_state.rows[0].letters[0] = 't'; + + wordle::draw::guesses(wordle_state, &draw_char); +} + +void v_blank_in_int(void) __attribute__ ((interrupt_handler)); +void v_blank_in_int() { + scu.reg.IST &= ~(IST__V_BLANK_IN); + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); + + sh2.reg.FRC.H = 0; + sh2.reg.FRC.L = 0; + sh2.reg.FTCSR = 0; // clear flags + + // render + + render(); + + // wait at least 300us, as specified in the SMPC manual. + // It appears reading FRC.H is mandatory and *must* occur before FRC.L on real + // hardware. + while ((sh2.reg.FTCSR & FTCSR__OVF) == 0 && sh2.reg.FRC.H == 0 && sh2.reg.FRC.L < 63); + + if ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) != 0) { + // on real hardware, SF contains uninitialized garbage bits other than the + // lsb. + while ((smpc.reg.SF & 1) != 0); + + smpc.reg.SF = 0; + + smpc.reg.ireg[0] = INTBACK__IREG0__STATUS_DISABLE; + smpc.reg.ireg[1] = ( INTBACK__IREG1__PERIPHERAL_DATA_ENABLE + | INTBACK__IREG1__PORT2_15BYTE + | INTBACK__IREG1__PORT1_15BYTE + ); + smpc.reg.ireg[2] = INTBACK__IREG2__MAGIC; + + smpc.reg.COMREG = COMREG__INTBACK; + } +} + +uint32_t init_font(uint32_t top) +{ + // 256 is the number of colors in the color palette, not the number of grays + // that are used by the font. + constexpr uint32_t colors_per_palette = 256; + constexpr uint32_t color_bank_index = 0; // completely random and arbitrary value + + palette::vdp2_cram_32grays(colors_per_palette, color_bank_index); + // For color bank color, COLR is concatenated bitwise with pixel data. See + // Figure 6.17 in the VDP1 manual. + draw_state.font.color_address = color_bank_index << 8; + + top = font_data(&_dejavusans_start, top, draw_state.font); + + return top; +} + +void main() +{ + uint32_t top = (sizeof (union vdp1_vram)); + top = init_font(top); + + // wait for the beginning of a V blank + v_blank_in(); + + // DISP: Please make sure to change this bit from 0 to 1 during V blank. + vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE + | TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320); + + // disable all VDP2 backgrounds (e.g: the Sega bios logo) + vdp2.reg.BGON = 0; + + // zeroize BACK color + vdp2.reg.BKTAU = 0; + vdp2.reg.BKTAL = 0; + vdp2.vram.u16[0] = 0; + + vdp2.reg.PRISA = PRISA__S0PRIN(1); // Sprite register 0 PRIority Number + + /* TVM settings must be performed from the second H-blank IN interrupt after the + V-blank IN interrupt to the H-blank IN interrupt immediately after the V-blank + OUT interrupt. */ + // "normal" display resolution, 16 bits per pixel, 512x256 framebuffer + vdp1.reg.TVMR = TVMR__TVM__NORMAL; + + // swap framebuffers every 1 cycle; non-interlace + vdp1.reg.FBCR = 0; + + // during a framebuffer erase cycle, write the color "black" to each pixel + constexpr uint16_t black = 0x0000; + vdp1.reg.EWDR = black; + + // erase upper-left coordinate + vdp1.reg.EWLR = EWLR__16BPP_X1(0) | EWLR__Y1(0); + + // erase lower-right coordinate + vdp1.reg.EWRR = EWRR__16BPP_X3(319) | EWRR__Y3(239); + + vdp1.vram.cmd[0].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__SYSTEM_CLIP_COORDINATES; + vdp1.vram.cmd[0].LINK = 0; + vdp1.vram.cmd[0].XC = 319; + vdp1.vram.cmd[0].YC = 239; + + vdp1.vram.cmd[1].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__LOCAL_COORDINATE; + vdp1.vram.cmd[1].LINK = 0; + vdp1.vram.cmd[1].XA = 0; + vdp1.vram.cmd[1].YA = 0; + + vdp1.vram.cmd[2].CTRL = CTRL__END; + + draw_state.cmd_ix = 2; + + // start drawing (execute the command list) on every frame + vdp1.reg.PTMR = PTMR__PTM__FRAME_CHANGE; + + // free-running timer + sh2.reg.TCR = TCR__CKS__INTERNAL_DIV128; + sh2.reg.FTCSR = 0; + + // initialize smpc + smpc.reg.DDR1 = 0; // INPUT + smpc.reg.DDR2 = 0; // INPUT + smpc.reg.IOSEL = 0; // SMPC control + smpc.reg.EXLE = 0; // + + // interrupts + sh2_vec[SCU_VEC__SMPC] = (u32)(&smpc_int); + sh2_vec[SCU_VEC__V_BLANK_IN] = (u32)(&v_blank_in_int); + + scu.reg.IST = 0; + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); +} + +extern "C" +void start(void) +{ + main(); + while (1); +} diff --git a/wordle/wordle.cpp b/wordle/wordle.cpp new file mode 100644 index 0000000..2f01e40 --- /dev/null +++ b/wordle/wordle.cpp @@ -0,0 +1,50 @@ +#include +#include + +#include "wordle.hpp" + +namespace wordle { + +void init_game(struct screen& s, const uint8_t * word) +{ + s.edit.row = 0; + s.edit.index = 0; + + for (uint32_t j = 0; j < word_length; j++) { + for (uint32_t i = 0; i < guesses; i++) { + s.rows[i].letters[j] = ' '; + } + + s.word[j] = word[j]; + } + + s.all_letters = 0; +} + +bool type_letter(struct screen& s, const uint8_t letter) +{ + if (s.edit.index >= word_length) + return false; + if (!(letter >= 'a' && letter <= 'z')) + return false; + + struct row& r = s.rows[s.edit.row]; + + r.letters[s.edit.index++] = letter; + + return true; +} + +bool backspace(struct screen& s) +{ + if (s.edit.index <= 0) + return false; + + struct row& r = s.rows[s.edit.row]; + + r.letters[--s.edit.index] = ' '; + + return true; +} + +} diff --git a/wordle/wordle.hpp b/wordle/wordle.hpp new file mode 100644 index 0000000..2e6d777 --- /dev/null +++ b/wordle/wordle.hpp @@ -0,0 +1,31 @@ +#include + +namespace wordle { + +constexpr uint32_t word_length = 5; +constexpr uint32_t guesses = 6; + +enum class clue { + correct, + position, + absent, + none +}; + +struct row { + uint8_t letters[word_length]; +}; + +struct edit { + uint32_t row; + uint32_t index; +}; + +struct screen { + struct edit edit; + struct row rows[guesses]; + uint8_t word[word_length]; + uint32_t all_letters; +}; + +}