Last active
July 22, 2021 19:11
-
-
Save rymdolle/d1aa26d69ae7c7df72bc0f0ec9bdd50b to your computer and use it in GitHub Desktop.
Shift game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2021 Olle Mattsson <rymdolle@gmail.com> | |
* | |
* Compile: | |
* gcc -lcurses -o shiftgame shiftgame.c -Wall | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <ncurses.h> | |
#include <string.h> | |
#include <time.h> | |
#include <getopt.h> | |
typedef struct timespec Timer; | |
void usage() { | |
printf("Usage:\n"); | |
printf("Find the target before the time runs out\n"); | |
printf("\n"); | |
printf(" -s, --seed=SEED Set seed\n"); | |
printf(" -p, --print Print box to stdout\n"); | |
printf(" --width=WIDTH Width of box\n"); | |
printf(" --height=HEIGHT Height of box\n"); | |
printf(" --shift=MS Shift time in milliseconds\n"); | |
printf(" -t, --timeout=SECONDS Timeout in seconds\n"); | |
printf("\n"); | |
printf("Copyright (c) 2021 Olle Mattsson <rymdolle@gmail.com>\n"); | |
} | |
void print_box(const uint8_t* box, int width, int height) { | |
for (int i = 0; i < width * height; ++i) { | |
printf("%02X", box[i]); | |
if (i % width == width - 1) | |
printf("\n"); | |
else | |
printf(" "); | |
} | |
} | |
void generate_box(uint8_t* box, size_t size) { | |
// initialize box | |
for (int i = 0; i < size; ++i) { | |
box[i] = rand() % 0xff; | |
} | |
} | |
// copy part and wrap around if out of bounds | |
bool part(const uint8_t* src, size_t size, uint8_t* dst, int offset, size_t length) { | |
if (offset < 0 || offset > size || length > size) | |
return false; | |
if (offset + length < size) { | |
memcpy(dst, src+offset, length); | |
} else { | |
int first = length-(size-offset); | |
int second = length-first; | |
memcpy(dst, src+offset, first); | |
memcpy(dst+first, src, second); | |
} | |
return true; | |
} | |
long elapsed_ms(Timer start) { | |
Timer now; | |
clock_gettime(CLOCK_MONOTONIC_RAW, &now); | |
return (now.tv_sec - start.tv_sec) * 1000L + (now.tv_nsec - start.tv_nsec) / 1000000L; | |
} | |
void reset_timer(Timer* timer) { | |
clock_gettime(CLOCK_MONOTONIC_RAW, timer); | |
} | |
void exit_error(const char* error, ...) { | |
endwin(); | |
va_list args; | |
va_start(args, error); | |
vprintf(error, args); | |
va_end(args); | |
exit(EXIT_FAILURE); | |
} | |
int wrap(int selection, int size) { | |
if (selection > size) | |
selection = selection % size; | |
if (selection < 0) | |
selection = (size + (selection % size)) % size; | |
return selection; | |
} | |
int main(int argc, char* argv[]) | |
{ | |
int width = 10; | |
int height = 8; | |
size_t size; | |
uint8_t* box; | |
int target; | |
int target_size = 4; | |
int selection = 0; | |
int shift = 0; | |
int shift_time = 1000; | |
time_t seed = time(NULL); | |
Timer start, timer; | |
int timeout = 30; | |
// time > 0 correct; time < 0 incorrect; time == 0 running | |
int time = 0; | |
bool print = false; | |
struct option optlong[] = { | |
{"seed", required_argument, NULL, 's'}, | |
{"print", no_argument, NULL, 'p'}, | |
{"width", required_argument, NULL, 0}, | |
{"height", required_argument, NULL, 0}, | |
{"shift", required_argument, NULL, 0}, | |
{"timeout", required_argument, NULL, 't'}, | |
{0}, | |
}; | |
initscr(); | |
cbreak(); | |
noecho(); | |
halfdelay(1); | |
keypad(stdscr, true); | |
curs_set(0); | |
clear(); | |
int opt, index; | |
while ((opt = getopt_long(argc, argv, "ps:t:", optlong, &index)) != -1) { | |
switch (opt) { | |
case 0: | |
if(strcmp(optlong[index].name, "width") == 0) { | |
if(strcmp(optarg, "max") == 0) | |
width = COLS / 3; | |
else | |
width = atoi(optarg); | |
} | |
if(strcmp(optlong[index].name, "height") == 0) { | |
if(strcmp(optarg, "max") == 0) | |
height = LINES - 11; | |
else | |
height = atoi(optarg); | |
} | |
if(strcmp(optlong[index].name, "shift") == 0) | |
shift_time = atoi(optarg); | |
break; | |
case 's': | |
seed = atoi(optarg); | |
break; | |
case 't': | |
timeout = atoi(optarg); | |
break; | |
case 'p': | |
print = true; | |
break; | |
case '?': | |
default: | |
endwin(); | |
usage(); | |
exit(EXIT_SUCCESS); | |
break; | |
} | |
} | |
// initialize seed | |
srand(seed); | |
size = width * height; | |
target = rand() % (size - target_size); | |
box = malloc(size); | |
if (box == NULL) | |
exit_error("could not allocate enough memory\n"); | |
generate_box(box, size); | |
clock_gettime(CLOCK_MONOTONIC_RAW, &start); | |
timer = start; | |
if (print) { | |
printf("seed: %ld\n", seed); | |
printf("target: "); | |
for (int i = 0; i < target_size; ++i) { | |
printf("%02X ", box[target+i]); | |
} | |
printf("\n"); | |
print_box(box, width, height); | |
free(box); | |
exit(EXIT_SUCCESS); | |
} | |
if (width * 3 > COLS) { | |
free(box); | |
exit_error("width is greater than terminal columns (max %d)\n", COLS / 3); | |
} | |
if (height + 11 > LINES) { | |
free(box); | |
exit_error("height is greater than terminal lines (max %d)\n", LINES - 11); | |
} | |
do { | |
if (elapsed_ms(start) > timeout * 1000 && time == 0) { | |
cbreak(); | |
time = -1; | |
move(7 + height + 1, 1); | |
addstr("you failed to find the target in time!\n"); | |
move(7 + height + 2, 1); | |
addstr("press 'r' to try again"); | |
selection = wrap(target - shift, size); | |
} | |
move(1, 1); | |
printw("seed: %d", seed); | |
move(2, 1); | |
clrtoeol(); | |
addstr("time: "); | |
if (time == 0) | |
printw("%.1f", timeout - (float)elapsed_ms(start) / 1000.f); | |
else | |
addstr("0.0"); | |
move(5, 1); | |
addstr("target: "); | |
for (int i = 0; i < target_size; ++i) { | |
printw("%02X ", box[target+i]); | |
} | |
int row = 7; | |
move(row, 1); | |
for (int i = 0; i < size; ++i) { | |
// highlight selection | |
for (int j = 0; j < target_size; ++j) { | |
if (i == (selection+j) % size) { | |
attron(A_STANDOUT); | |
break; | |
} | |
} | |
printw("%02X", box[(i + shift) % size]); | |
attroff(A_STANDOUT); | |
if (i % width == width - 1) | |
move(++row, 1); | |
else | |
addch(' '); | |
} | |
if (time == 0 && elapsed_ms(timer) > shift_time) { | |
reset_timer(&timer); | |
shift = wrap(shift + 1, size); | |
} | |
int ch = wgetch(stdscr); | |
switch (ch) { | |
case 'k': | |
case KEY_UP: | |
selection = wrap(selection - width, size); | |
break; | |
case 'j': | |
case KEY_DOWN: | |
selection = wrap(selection + width, size); | |
break; | |
case 'h': | |
case KEY_LEFT: | |
selection = wrap(selection - 1, size); | |
break; | |
case 'l': | |
case KEY_RIGHT: | |
selection = wrap(selection + 1, size); | |
break; | |
case KEY_END: | |
selection = (selection / width) * width + width - target_size; | |
break; | |
case KEY_HOME: | |
selection = (selection / width) * width; | |
break; | |
case KEY_NPAGE: | |
selection = size - (width - (selection % width)); | |
break; | |
case KEY_PPAGE: | |
selection = selection % width; | |
break; | |
case 0x0A: { // Enter | |
uint8_t selection_part[target_size]; | |
part(box, size, selection_part, (selection + shift) % size, target_size); | |
move(row + 1, 1); | |
if (time) | |
break; | |
if (memcmp(box + target, selection_part, target_size) == 0) { | |
cbreak(); | |
time = elapsed_ms(start); | |
printw("you did it in %.3f seconds!\n", (float)time / 1000); | |
move(row + 2, 1); | |
printw("press 'r' to try again\n"); | |
move(2, 1); | |
clrtoeol(); | |
printw("time: %.3f", time); | |
} else { | |
printw("wrong target, try again!\n"); | |
} | |
} break; | |
case 's': | |
clear(); | |
break; | |
case 'r': | |
clear(); | |
halfdelay(1); | |
time = 0; | |
seed = rand(); | |
srand(seed); | |
selection = 0; | |
shift = 0; | |
target = rand() % (size - target_size); | |
generate_box(box, size); | |
reset_timer(&start); | |
reset_timer(&timer); | |
break; | |
case 'q': | |
endwin(); | |
free(box); | |
exit(EXIT_SUCCESS); | |
break; | |
case ERR: | |
default: | |
break; | |
} | |
refresh(); | |
} while (1); | |
free(box); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment