Skip to content

Instantly share code, notes, and snippets.

@rymdolle
Last active July 22, 2021 19:11
Show Gist options
  • Save rymdolle/d1aa26d69ae7c7df72bc0f0ec9bdd50b to your computer and use it in GitHub Desktop.
Save rymdolle/d1aa26d69ae7c7df72bc0f0ec9bdd50b to your computer and use it in GitHub Desktop.
Shift game
/*
* 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