gists/mario_tennis/tennis.c
2018-10-11 16:45:32 +02:00

329 lines
7.8 KiB
C

// arbitrary terminology i made up on the spot:
// "entry" = code as exposed to user, in ASCII.
// "code" = code as indices into lut, used internally.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef uint32_t u32;
typedef uint64_t u64;
#define lament(...) fprintf(stderr, __VA_ARGS__)
#define sizeofel(a) (sizeof(*(a)))
#define LEN(a) (sizeof(a) / sizeofel(a))
const char *lut[] = {
// characters within the same string are considered the same.
// that means the number of choices for a single character
// goes down from 36 to 28.
"N", "P", "Q", "R",
"T", "W", "X", "Y",
"17I", "2Z", "5S", "8B",
"0OD", "UV", "3", "4",
"6", "9", "A", "C",
"E", "F", "G", "H",
"J", "K", "L", "M",
};
enum {lutlen = LEN(lut)}; // should be 28.
const u32 bases[] = {
64,
11,
21,
2,
21,
8,
6, // dummy value.
};
enum {
MT_CHOICE = -1,
};
typedef enum {
MT_YOSHI,
MT_PEACH,
MT_MARIO,
MT_BOWSER,
MT_BOO,
MT_DK,
MT_BABY_MARIO,
MT_TOAD,
MT_WALUIGI,
MT_WARIO,
MT_LUIGI,
MT_DAISY,
MT_BIRDO,
MT_SHYGUY,
MT_DK_JR,
MT_PARATROOPA,
} character_t;
typedef enum {
MT_SINGLES_GAME,
MT_SINGLES_TIME_3,
MT_SINGLES_TIME_5,
MT_SINGLES_BALL_1,
MT_SINGLES_BALL_3,
MT_SINGLES_POINT_3,
MT_SINGLES_POINT_5,
MT_DOUBLES_GAME_1,
MT_DOUBLES_TIME_3,
MT_DOUBLES_BALL_1,
MT_DOUBLES_POINT_3,
} gamemode_t;
typedef enum {
MT_HARD,
MT_CLAY,
MT_GRASS,
MT_COMPOSITION,
MT_OPEN,
} court_t;
typedef struct {
char code[9];
// data[0]: cup name
// data[1]: gamemode
// data[2]: player character (-1 for player's choice)
// data[3]: unknown (boolean)
// data[4]: opponent character (-1 for player's choice)
// data[5]: unknown
// data[6]: court (-1 for player's choice)
u32 data[7];
} code_t;
code_t translate(const char *entry) {
code_t code = {0};
for (int i = 0; i < 9; i++) {
char e = entry[i];
if (e == '\0') return code;
for (int j = 0; j < lutlen; j++) {
const char *c = lut[j];
while (*c != '\0') {
if (e == *c) {
code.code[i] = j;
goto nextchar;
}
c++;
}
}
nextchar:
;
}
return code;
}
int check_ranges(const code_t *code) {
const u32 choice = (u32)MT_CHOICE;
const u32 *data = code->data;
return
(data[0] < bases[0]) &&
(data[1] < bases[1]) &&
(data[2] + 1 < bases[2] - 4) &&
(data[3] < bases[3]) &&
(data[4] + 1 < bases[4] - 4) &&
(data[5] < bases[5]) &&
(data[6] + 1 < bases[6]) &&
(data[2] == choice || data[2] != data[4]) &&
(data[1] < 7 || (data[2] != choice && data[4] != choice));
}
int decode_data(code_t *code) {
u32 part0 = 0, part1 = 0;
for (int i = 5; i >= 0; i--) part0 = part0 * lutlen + code->code[i];
for (int i = 8; i >= 6; i--) part1 = part1 * lutlen + code->code[i];
u32 checksum = (part0 + 0x2AE0) % (lutlen * lutlen * lutlen);
#if !NDEBUG
lament("merged data: %08X\n", part0);
lament("expected checksum: %08X\n", part1);
lament("actual checksum: %08X\n", checksum);
#endif
u32 leftover = part0 ^ 0x02AAAAAA;
code->data[0] = leftover % bases[0]; leftover /= bases[0];
code->data[1] = leftover % bases[1]; leftover /= bases[1];
code->data[2] = leftover % bases[2]; leftover /= bases[2];
code->data[3] = leftover % bases[3]; leftover /= bases[3];
code->data[4] = leftover % bases[4]; leftover /= bases[4];
code->data[5] = leftover % bases[5]; leftover /= bases[5];
code->data[6] = leftover;
code->data[2]--;
code->data[4]--;
code->data[6]--;
#if !NDEBUG
lament("stored data:");
for (int i = 0; i < 7; i++) lament(" %i", code->data[i]);
lament("\n");
#endif
if (checksum != part1) return 0;
if (!check_ranges(code)) return 0;
return 1;
}
int encode_data(code_t *code) {
if (!check_ranges(code)) return 0;
u32 combined = 0;
combined = code->data[6] + 1;
combined = combined * bases[5] + code->data[5];
combined = combined * bases[4] + code->data[4] + 1;
combined = combined * bases[3] + code->data[3];
combined = combined * bases[2] + code->data[2] + 1;
combined = combined * bases[1] + code->data[1];
combined = combined * bases[0] + code->data[0];
combined = combined ^ 0x02AAAAAA;
u32 checksum = (combined + 0x2AE0) % (lutlen * lutlen * lutlen);
#if !NDEBUG
lament("merged data: %08X\n", combined);
lament("checksum: %08X\n", checksum);
#endif
u32 part0 = combined, part1 = checksum;
for (int i = 0; i <= 5; i++) {
code->code[i] = part0 % lutlen; part0 /= lutlen;
}
for (int i = 6; i <= 8; i++) {
code->code[i] = part1 % lutlen; part1 /= lutlen;
}
return 1;
}
int validate_entry(const char *entry) {
code_t code = translate(entry);
#if !NDEBUG
lament("character mapping:");
for (int i = 0; i < 9; i++) {
lament(" %c->%i,", entry[i], code.code[i]);
}
lament("\n");
#endif
return decode_data(&code);
}
static void print_code(const code_t *code) {
for (int j = 0; j < 9; j++) {
int index = code->code[j];
printf("%c", lut[index][0]);
}
printf("\n");
}
static u64 prng_state = 1;
static u32 prng() {
prng_state = 3935559000370003845 * prng_state + 1;
return prng_state ^ (prng_state >> 32);
}
int bruteforce(int count) {
/* with a fixed seed, this is useful for testing.
the first 10 results should be:
8RPEHR8R4
MNXKWQMNE
CE922RCER
MQ1KMQMQG
2N9GGR2NR
25L53R250
15R09R159
9R9LMQ9RR
L312MQL3G
P93M6QP9N
*/
int found = 0;
code_t code = {0};
while (found < count) {
for (int j = 0; j < 9; j++) {
code.code[j] = prng() % lutlen;
}
if (decode_data(&code)) {
found++;
print_code(&code);
}
}
return 1;
}
#ifndef TENNIS_NO_MAIN
int main(int argc, char **argv) {
if (argc == 1) {
// self-test with codes known before this program was written.
#define TEST(entry) if (!validate_entry(entry)) return -1
TEST("48HWOR482");
TEST("5G3LTQ5GN");
TEST("A3W5KQA3C");
TEST("ARM6JQARU");
TEST("E880MPE8K");
TEST("E8ULJRE8M");
TEST("EPJEGREP5");
TEST("GH4KNQGHP");
TEST("H6L3MPH60");
TEST("HH4KNQHHP");
TEST("J6M9PQJ6U");
TEST("JEP8YQJE4");
TEST("LA98JRLAR");
TEST("LQM1MPLQU");
TEST("LTHWYQLT2");
TEST("M1C2YQM1W");
TEST("MM55MQMMJ");
TEST("N24K8QN2P");
TEST("OF9XFQOFR");
TEST("P4K6GRP48");
TEST("TE6WARTEQ");
TEST("TQJEGRTQ5");
TEST("UOUFMPUOM");
TEST("V2UFMPUZM");
TEST("W2HEGRW22");
TEST("WQJEGRWQ5");
TEST("WRWQARWRC");
TEST("YQJEGRYQ5");
#undef TEST
return 0;
}
code_t code = {0};
int code_i = 0;
int invalid_count = 0;
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (strlen(arg) == 9) {
//if (validate_entry(arg)) printf("%s\n", arg);
invalid_count += !validate_entry(arg);
} else if (!strcmp(arg, "bruteforce")) {
invalid_count += !bruteforce(10);
} else {
u32 datum = strtoul(arg, NULL, 0);
code.data[code_i] = datum;
code_i++;
if (code_i >= 7) {
code_i = 0;
if (encode_data(&code)) {
print_code(&code);
} else {
invalid_count++;
}
}
}
}
return invalid_count;
}
#endif