This commit is contained in:
commit
aab2829c9c
1 changed files with 247 additions and 0 deletions
247
tennis.c
Normal file
247
tennis.c
Normal file
|
@ -0,0 +1,247 @@
|
|||
// 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[] = {
|
||||
"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;
|
||||
|
||||
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.
|
||||
if (!validate_entry("48HWOR482")) return -1;
|
||||
if (!validate_entry("5G3LTQ5GN")) return -1;
|
||||
if (!validate_entry("A3W5KQA3C")) return -1;
|
||||
if (!validate_entry("ARM6JQARU")) return -1;
|
||||
if (!validate_entry("E8ULJRE8M")) return -1;
|
||||
if (!validate_entry("J6M9PQJ6U")) return -1;
|
||||
if (!validate_entry("LA98JRLAR")) return -1;
|
||||
if (!validate_entry("M1C2YQM1W")) return -1;
|
||||
if (!validate_entry("MM55MQMMJ")) return -1;
|
||||
if (!validate_entry("N24K8QN2P")) return -1;
|
||||
if (!validate_entry("OF9XFQOFR")) return -1;
|
||||
if (!validate_entry("UOUFMPUOM")) return -1;
|
||||
if (!validate_entry("V2UFMPUZM")) return -1;
|
||||
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;
|
||||
}
|
Loading…
Reference in a new issue