gists/tennis.c
2018-06-03 03:36:00 -07:00

272 lines
7.3 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 c0 = 64;
const u32 c1 = 11;
const u32 c2 = 21;
const u32 c3 = 2;
const u32 c4 = 21;
const u32 c5 = 8;
typedef struct {
char code[9];
// data[0]: cup name
// data[1]: ?
// data[2]: player character (-1 for player's choice)
// data[3]: game mode? (boolean)
// data[4]: opponent character (-1 for player's choice)
// data[5]: ?
// 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(code_t *code) {
int within_range = 1;
within_range &= code->data[0] < c0;
within_range &= code->data[1] < c1;
within_range &= code->data[2] + 1 < c2 - 4;
within_range &= code->data[3] < c3;
within_range &= code->data[4] + 1 < c2 - 4;
within_range &= code->data[5] < c5;
within_range &= code->data[6] + 1 < 6;
return within_range;
}
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 % c0; leftover /= c0;
code->data[1] = leftover % c1; leftover /= c1;
code->data[2] = leftover % c2; leftover /= c2;
code->data[3] = leftover % c3; leftover /= c3;
code->data[4] = leftover % c4; leftover /= c4;
code->data[5] = leftover % c5; leftover /= c5;
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;
// TODO: refactor so this isn't repeated in encode_data.
const u32 neg1 = (u32)-1;
if (code->data[2] == neg1 || code->data[2] != code->data[4]); else return 0;
if (code->data[1] < 7 || (code->data[2] != neg1 && code->data[4] != neg1)); else return 0;
return 1;
}
int encode_data(code_t *code) {
const u32 neg1 = (u32)-1;
if (code->data[2] == neg1 || code->data[2] != code->data[4]); else return 0;
if (code->data[1] < 7 || (code->data[2] != neg1 && code->data[4] != neg1)); else return 0;
if (!check_ranges(code)) return 0;
u32 combined = 0;
combined = code->data[6] + 1;
combined = combined * c5 + code->data[5];
combined = combined * c4 + code->data[4] + 1;
combined = combined * c3 + code->data[3];
combined = combined * c2 + code->data[2] + 1;
combined = combined * c1 + code->data[1];
combined = combined * c0 + 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 u64 prng_state = 1;
static u32 prng() {
prng_state = 3935559000370003845 * prng_state + 1;
return prng_state ^ (prng_state >> 32);
}
int bruteforce() { // with a fixed seed, this is useful for testing.
int found = 0;
code_t code = {0};
while (found < 10) {
for (int j = 0; j < 9; j++) {
code.code[j] = prng() % lutlen;
}
if (decode_data(&code)) {
found++;
for (int j = 0; j < 9; j++) {
int index = code.code[j];
printf("%c", lut[index][0]);
}
printf("\n");
}
}
return 1;
}
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("5PURGR5PM");
//TEST("6PJEGRGP5");
//TEST("8GYUHR86F");
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");
//TEST("ZKZLZKNNR");
#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) {
invalid_count += !validate_entry(arg);
} else if (!strcmp(arg, "bruteforce")) {
invalid_count += !bruteforce();
} 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)) {
for (int j = 0; j < 9; j++) {
int index = code.code[j];
printf("%c", lut[index][0]);
}
printf("\n");
} else {
invalid_count++;
}
}
}
}
return invalid_count;
}