wordle: minimally working game
A few "minor" features are missing, including showing the correct word if you get it wrong.
This commit is contained in:
parent
cfad16c514
commit
e15f0a9eaf
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ res/mai.data
|
||||
tools/ttf-convert
|
||||
common/keyboard.cpp
|
||||
common/keyboard.hpp
|
||||
wordle/word_list.hpp
|
7
Makefile
7
Makefile
@ -62,6 +62,11 @@ smpc/input_keyboard.elf: smpc/input_keyboard.o sh/lib1funcs.o res/dejavusansmono
|
||||
|
||||
wordle/main_saturn.o: common/keyboard.hpp
|
||||
|
||||
wordle/word_list.hpp: wordle/word_list.csv wordle/word_list.py
|
||||
python wordle/word_list.py > $@
|
||||
|
||||
wordle/wordle.o: wordle/word_list.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
|
||||
|
||||
|
||||
@ -74,4 +79,4 @@ clean-sh:
|
||||
-regextype posix-egrep \
|
||||
-regex '.*\.(iso|o|bin|elf|cue)$$' \
|
||||
-exec rm {} \;
|
||||
rm -f common/keyboard.cpp common/keyboard.hpp
|
||||
rm -f common/keyboard.cpp common/keyboard.hpp wordle/word_list.hpp
|
||||
|
@ -56,7 +56,8 @@ uint32_t single_character_centered(state const& s,
|
||||
const int32_t x1, // in 26.6 fixed point
|
||||
const int32_t y1,
|
||||
const int32_t x2,
|
||||
const int32_t y2)
|
||||
const int32_t y2,
|
||||
uint16_t color)
|
||||
{
|
||||
//assert(c <= s.char_code_offset);
|
||||
const T c_offset = c - s._font->char_code_offset;
|
||||
@ -64,12 +65,10 @@ uint32_t single_character_centered(state const& s,
|
||||
glyph_bitmap const& bitmap = s._glyphs[c_offset].bitmap;
|
||||
glyph_metrics const& metrics = s._glyphs[c_offset].metrics;
|
||||
|
||||
constexpr uint16_t magenta = (0x31 << 10) | (0x31 << 0);
|
||||
|
||||
vdp1.vram.cmd[cmd_ix].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__POLYLINE;
|
||||
vdp1.vram.cmd[cmd_ix].LINK = 0;
|
||||
vdp1.vram.cmd[cmd_ix].PMOD = PMOD__ECD | PMOD__SPD;
|
||||
vdp1.vram.cmd[cmd_ix].COLR = COLR__RGB | magenta; // non-palettized (rgb15) color data
|
||||
vdp1.vram.cmd[cmd_ix].COLR = COLR__RGB | color; // non-palettized (rgb15) color data
|
||||
vdp1.vram.cmd[cmd_ix].XA = x1;
|
||||
vdp1.vram.cmd[cmd_ix].YA = y1;
|
||||
vdp1.vram.cmd[cmd_ix].XB = x2;
|
||||
|
@ -47,7 +47,7 @@ void main()
|
||||
vdp1.vram.cmd[1].XA = 0;
|
||||
vdp1.vram.cmd[1].YA = 0;
|
||||
|
||||
constexpr uint16_t magenta = (0x31 << 10) | (0x31 << 0);
|
||||
constexpr uint16_t magenta = (31 << 10) | (31 << 0);
|
||||
vdp1.vram.cmd[2].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__POLYGON;
|
||||
vdp1.vram.cmd[2].LINK = 0;
|
||||
// "Set [ECD] to '1' for polygons, polylines, and lines"
|
||||
|
@ -13,10 +13,10 @@ const static uint8_t layout[3][10] = {
|
||||
};
|
||||
|
||||
|
||||
void keyboard(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t))
|
||||
void keyboard(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t, enum clue))
|
||||
{
|
||||
constexpr int32_t origin_x[3] = {46, 57, 69};
|
||||
constexpr int32_t origin_y = 170;
|
||||
constexpr int32_t origin_y = 160;
|
||||
constexpr uint32_t rows = 3;
|
||||
constexpr uint32_t cols[3] = {10, 9, 7};
|
||||
|
||||
@ -29,19 +29,20 @@ void keyboard(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_
|
||||
int32_t x2 = x1 + box_dim;
|
||||
int32_t y2 = y1 + box_dim;
|
||||
|
||||
draw_char(l, x1, y1, x2, y2);
|
||||
int32_t l_ix = static_cast<int32_t>(l) - static_cast<int32_t>('A');
|
||||
draw_char(l, x1, y1, x2, y2, s.clues[l_ix]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void guesses(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t))
|
||||
void guesses(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t, enum clue))
|
||||
{
|
||||
// first row is at (104,23).
|
||||
// midpoint is +(10,10)
|
||||
// grid is +(13,13)
|
||||
|
||||
constexpr int32_t origin_x = 103;
|
||||
constexpr int32_t origin_y = 17;
|
||||
constexpr int32_t origin_y = 12;
|
||||
|
||||
for (uint32_t row = 0; row < wordle::guesses; row++) {
|
||||
|
||||
@ -55,7 +56,7 @@ void guesses(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t
|
||||
int32_t x2 = x1 + box_dim;
|
||||
int32_t y2 = y1 + box_dim;
|
||||
|
||||
draw_char(l, x1, y1, x2, y2);
|
||||
draw_char(l, x1, y1, x2, y2, r.clues[col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace wordle {
|
||||
namespace draw {
|
||||
void keyboard(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t));
|
||||
void guesses(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t));
|
||||
void keyboard(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t, enum clue));
|
||||
void guesses(struct screen const& s, void (*draw_char)(uint8_t, int32_t, int32_t, int32_t, int32_t, enum clue));
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,23 @@ struct intback_state {
|
||||
static intback_state intback;
|
||||
static int oreg_ix;
|
||||
|
||||
|
||||
struct xorshift32_state {
|
||||
uint32_t a;
|
||||
};
|
||||
|
||||
uint32_t xorshift32(struct xorshift32_state *state)
|
||||
{
|
||||
uint32_t x = state->a;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
return state->a = x;
|
||||
}
|
||||
|
||||
static xorshift32_state random_state = { 0x12345678 };
|
||||
static uint32_t frame_count = 0;
|
||||
|
||||
void smpc_int(void) __attribute__ ((interrupt_handler));
|
||||
void smpc_int(void) {
|
||||
scu.reg.IST &= ~(IST__SMPC);
|
||||
@ -71,6 +88,7 @@ void smpc_int(void) {
|
||||
- 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++) {
|
||||
@ -103,7 +121,20 @@ void smpc_int(void) {
|
||||
if (kbd_bits & 0b1000) { // Make
|
||||
enum keysym k = scancode_to_keysym(keysym);
|
||||
char16_t c = keysym_to_char16(k);
|
||||
(void)c;
|
||||
if (k != keysym::UNKNOWN) {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
// uppercase
|
||||
wordle::type_letter(wordle_state, c);
|
||||
} else if (k == keysym::ENTER) {
|
||||
wordle::confirm_word(wordle_state);
|
||||
} else if (k == keysym::BACKSPACE) {
|
||||
wordle::backspace(wordle_state);
|
||||
} else if (k == keysym::ESC) {
|
||||
random_state.a += frame_count;
|
||||
uint32_t rand = xorshift32(&random_state);
|
||||
wordle::init_screen(wordle_state, rand);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (kbd_bits & 0b0001) { // Break
|
||||
|
||||
@ -135,16 +166,28 @@ void smpc_int(void) {
|
||||
|
||||
// rendering
|
||||
|
||||
void draw_char(uint8_t c, int32_t x1, int32_t y1, int32_t x2, int32_t y2)
|
||||
inline constexpr uint16_t clue_color(enum wordle::clue c)
|
||||
{
|
||||
switch (c) {
|
||||
default:
|
||||
case wordle::clue::exact: return ( 0 << 10) | (31 << 5) | ( 0 << 0);
|
||||
case wordle::clue::present: return ( 7 << 10) | (19 << 5) | (22 << 0);
|
||||
case wordle::clue::absent: return ( 4 << 10) | ( 4 << 5) | ( 4 << 0);
|
||||
case wordle::clue::none: return (14 << 10) | (14 << 5) | (14 << 0);
|
||||
};
|
||||
}
|
||||
|
||||
void draw_char(uint8_t l, int32_t x1, int32_t y1, int32_t x2, int32_t y2, enum wordle::clue c)
|
||||
{
|
||||
draw_state.cmd_ix =
|
||||
draw_font::single_character_centered(draw_state.font,
|
||||
draw_state.cmd_ix,
|
||||
c,
|
||||
l,
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2);
|
||||
y2,
|
||||
clue_color(c));
|
||||
}
|
||||
|
||||
void render()
|
||||
@ -162,6 +205,8 @@ void v_blank_in_int() {
|
||||
scu.reg.IST &= ~(IST__V_BLANK_IN);
|
||||
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
|
||||
|
||||
frame_count++;
|
||||
|
||||
sh2.reg.FRC.H = 0;
|
||||
sh2.reg.FRC.L = 0;
|
||||
sh2.reg.FTCSR = 0; // clear flags
|
||||
@ -219,8 +264,8 @@ void main()
|
||||
v_blank_in();
|
||||
|
||||
// wordle init
|
||||
const uint8_t word[] = "67890";
|
||||
wordle::init_screen(wordle_state, word);
|
||||
const uint8_t word_ix = 6;
|
||||
wordle::init_screen(wordle_state, word_ix);
|
||||
// end wordle init
|
||||
|
||||
// DISP: Please make sure to change this bit from 0 to 1 during V blank.
|
||||
|
12972
wordle/word_list.csv
Normal file
12972
wordle/word_list.csv
Normal file
File diff suppressed because it is too large
Load Diff
41
wordle/word_list.py
Normal file
41
wordle/word_list.py
Normal file
@ -0,0 +1,41 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
abspath = os.path.abspath(__file__)
|
||||
dirname = os.path.dirname(abspath)
|
||||
os.chdir(dirname)
|
||||
|
||||
print("namespace wordle {")
|
||||
print("const uint8_t word_list[][word_length] = {")
|
||||
|
||||
answers = list()
|
||||
|
||||
with open("word_list.csv", "r") as f:
|
||||
index = 0
|
||||
for line in f:
|
||||
l = line.strip()
|
||||
if not l:
|
||||
continue
|
||||
word, occurrence, day = l.split(",")
|
||||
w = word.upper()
|
||||
o = eval(occurrence)
|
||||
d = day.strip()
|
||||
|
||||
if o > 1e-08:
|
||||
print(f"{{'{w[0]}', '{w[1]}', '{w[2]}', '{w[3]}', '{w[4]}'}},")
|
||||
if day:
|
||||
answers.append(index)
|
||||
index+=1
|
||||
|
||||
print("};")
|
||||
|
||||
print()
|
||||
|
||||
print("const uint32_t answers[] = {")
|
||||
for answer in answers:
|
||||
print(f"{answer},")
|
||||
print("};")
|
||||
|
||||
print("}")
|
||||
print(f"words: {index}", file=sys.stderr)
|
||||
print(f"answers: {len(answers)}", file=sys.stderr)
|
@ -2,10 +2,11 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "wordle.hpp"
|
||||
#include "word_list.hpp"
|
||||
|
||||
namespace wordle {
|
||||
|
||||
void init_screen(struct screen& s, const uint8_t * word)
|
||||
void init_screen(struct screen& s, uint32_t rand)
|
||||
{
|
||||
s.edit.row = 0;
|
||||
s.edit.index = 0;
|
||||
@ -13,66 +14,48 @@ void init_screen(struct screen& s, const uint8_t * word)
|
||||
for (uint32_t j = 0; j < word_length; j++) {
|
||||
for (uint32_t i = 0; i < guesses; i++) {
|
||||
s.rows[i].letters[j] = ' ';
|
||||
s.rows[i].clues[j] = clue::none;
|
||||
}
|
||||
|
||||
s.word[j] = word[j];
|
||||
}
|
||||
|
||||
s.rows[0].letters[0] = 'A';
|
||||
s.rows[0].letters[1] = 'B';
|
||||
s.rows[0].letters[2] = 'C';
|
||||
s.rows[0].letters[3] = 'D';
|
||||
s.rows[0].letters[4] = 'E';
|
||||
for (uint32_t k = 0; k < 26; k++) {
|
||||
s.clues[k] = clue::none;
|
||||
}
|
||||
|
||||
s.rows[1].letters[0] = 'F';
|
||||
s.rows[1].letters[1] = 'G';
|
||||
s.rows[1].letters[2] = 'H';
|
||||
s.rows[1].letters[3] = 'I';
|
||||
s.rows[1].letters[4] = 'J';
|
||||
|
||||
s.rows[2].letters[0] = 'K';
|
||||
s.rows[2].letters[1] = 'L';
|
||||
s.rows[2].letters[2] = 'M';
|
||||
s.rows[2].letters[3] = 'N';
|
||||
s.rows[2].letters[4] = 'O';
|
||||
|
||||
s.rows[3].letters[0] = 'P';
|
||||
s.rows[3].letters[1] = 'Q';
|
||||
s.rows[3].letters[2] = 'R';
|
||||
s.rows[3].letters[3] = 'S';
|
||||
s.rows[3].letters[4] = 'T';
|
||||
|
||||
s.rows[4].letters[0] = 'U';
|
||||
s.rows[4].letters[1] = 'V';
|
||||
s.rows[4].letters[2] = 'W';
|
||||
s.rows[4].letters[3] = 'X';
|
||||
s.rows[4].letters[4] = 'Y';
|
||||
|
||||
s.rows[5].letters[0] = 'Z';
|
||||
s.rows[5].letters[1] = '1';
|
||||
s.rows[5].letters[2] = '2';
|
||||
s.rows[5].letters[3] = '3';
|
||||
s.rows[5].letters[4] = '4';
|
||||
|
||||
s.all_letters = 0;
|
||||
constexpr uint32_t answer_length = (sizeof (answers)) / (sizeof (uint32_t));
|
||||
s.word_ix = answers[rand % answer_length];
|
||||
}
|
||||
|
||||
bool type_letter(struct screen& s, const uint8_t letter)
|
||||
{
|
||||
if (s.edit.row >= guesses)
|
||||
return false;
|
||||
|
||||
uint8_t l;
|
||||
|
||||
if (s.edit.index >= word_length)
|
||||
return false;
|
||||
if (!(letter >= 'a' && letter <= 'z'))
|
||||
if (letter >= 'a' && letter <= 'z') {
|
||||
l = letter - ('a' - 'A'); // upcase
|
||||
} else if (letter >= 'A' && letter <= 'Z') {
|
||||
l = letter;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct row& r = s.rows[s.edit.row];
|
||||
|
||||
r.letters[s.edit.index++] = letter;
|
||||
r.letters[s.edit.index++] = l;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool backspace(struct screen& s)
|
||||
{
|
||||
if (s.edit.row >= guesses)
|
||||
return false;
|
||||
|
||||
if (s.edit.index <= 0)
|
||||
return false;
|
||||
|
||||
@ -83,4 +66,83 @@ bool backspace(struct screen& s)
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline int32_t word_in_list(const uint8_t * word)
|
||||
{
|
||||
constexpr uint32_t word_list_items = (sizeof word_list) / (word_length);
|
||||
|
||||
for (uint32_t i = 0; i < word_list_items; i++) {
|
||||
for (uint32_t j = 0; j < word_length; j++) {
|
||||
if (word_list[i][j] != word[j])
|
||||
goto next_word;
|
||||
}
|
||||
// word_length loop exited and all letters matched
|
||||
return true;
|
||||
|
||||
next_word:
|
||||
continue;
|
||||
}
|
||||
// exhausted word list; nothing matches
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr inline enum clue next_clue(enum clue a, enum clue b)
|
||||
{
|
||||
if (a == clue::exact || b == clue::exact)
|
||||
return clue::exact;
|
||||
if (a == clue::present || b == clue::present)
|
||||
return clue::present;
|
||||
if (a == clue::absent || b == clue::absent)
|
||||
return clue::absent;
|
||||
|
||||
return clue::none;
|
||||
}
|
||||
|
||||
static inline void update_clues(enum clue * clues, struct row &r, const uint8_t * word)
|
||||
{
|
||||
for (uint32_t i = 0; i < word_length; i++) {
|
||||
enum clue &c = r.clues[i];
|
||||
|
||||
c = clue::absent;
|
||||
const uint8_t l = r.letters[i];
|
||||
|
||||
if (l == word[i]) {
|
||||
c = clue::exact;
|
||||
} else {
|
||||
// also check for position errors
|
||||
for (uint32_t j = 0; j < word_length; j++) {
|
||||
if (word[j] == l) {
|
||||
c = clue::present;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t l_ix = static_cast<int32_t>(l) - static_cast<int32_t>('A');
|
||||
if (l_ix >= 0 && l_ix < 26) {
|
||||
// update global clues
|
||||
clues[l_ix] = next_clue(clues[l_ix], c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool confirm_word(struct screen& s)
|
||||
{
|
||||
if (s.edit.row >= guesses)
|
||||
return false;
|
||||
|
||||
if (s.edit.index < word_length)
|
||||
return false;
|
||||
|
||||
if (!(word_in_list(s.rows[s.edit.row].letters)))
|
||||
return false;
|
||||
|
||||
update_clues(s.clues, s.rows[s.edit.row], word_list[s.word_ix]);
|
||||
|
||||
s.edit.row++;
|
||||
s.edit.index = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -6,14 +6,15 @@ constexpr uint32_t word_length = 5;
|
||||
constexpr uint32_t guesses = 6;
|
||||
|
||||
enum class clue {
|
||||
correct,
|
||||
position,
|
||||
exact,
|
||||
present,
|
||||
absent,
|
||||
none
|
||||
};
|
||||
|
||||
struct row {
|
||||
uint8_t letters[word_length];
|
||||
enum clue clues[word_length];
|
||||
};
|
||||
|
||||
struct edit {
|
||||
@ -24,10 +25,12 @@ struct edit {
|
||||
struct screen {
|
||||
struct edit edit;
|
||||
struct row rows[guesses];
|
||||
uint8_t word[word_length];
|
||||
uint32_t all_letters;
|
||||
uint32_t word_ix;
|
||||
enum clue clues[26];
|
||||
};
|
||||
|
||||
void init_screen(struct screen& s, const uint8_t * word);
|
||||
|
||||
void init_screen(struct screen& s, uint32_t word_ix);
|
||||
bool type_letter(struct screen& s, const uint8_t letter);
|
||||
bool backspace(struct screen& s);
|
||||
bool confirm_word(struct screen& s);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user