#include #include #include #include #include #define TEST false char *shapes[] = {"rock", "paper", "scissors"}; void help(bool bad) { size_t i; /* Errors usually go to stderr but since this is an interactive * program it doesn't really matter. * If you write errors to stderr you can redirect them to other * places, e.g. `./rock-paper-scissors 2>/tmp/errors`, you also * wouldn't miss them if you decided to redirect stdout to * /dev/null with `>/dev/null`. */ if (bad) fputs("bad choice.\n", stderr); fputs("pick a shape: ", bad ? stderr : stdout); for (i = 0; i < sizeof(shapes) / sizeof(shapes[0]); i++) fprintf(bad ? stderr : stdout, "%s ", shapes[i]); fputc('\n', bad ? stderr : stdout); } /* Returns true if `a` (index in `shapes`), beats `b` (also an index in `shapes`). */ bool beats(int a, int b) { /* Check out the `shapes` array, you can notice it's ordered so that each * shape beats the one that was behind it (the one with the lower index) * except when you get to the end and have to wrap around, i.e. rock (index 0) * beats scissors (index 2), that's what the modulo is for. */ return a == (b + 1) % 3; } #if TEST void test(void) { /* In the inner arrays, index 0 is the shape of player A (the stdin input), * index 1 is the shape of player B (randomly generated) and index 3 is * the outcome (0 - lose, 1 - win). */ signed char v[9][3] = { {0, 0, 0}, {0, 1, 0}, {0, 2, 1}, {1, 0, 1}, {1, 1, 0}, {1, 2, 0}, {2, 0, 0}, {2, 1, 1}, {2, 2, 0}, }; int i; for (i = 0; i < sizeof(v) / sizeof(v[0]); i++) { if (beats(v[i][0], v[i][1]) != v[i][2]) { fprintf(stderr, "error: %s %s beat %s\n", shapes[v[i][0]], v[i][2] ? "should" : "shouldn't", shapes[v[i][1]]); exit(1); } printf("%s %s %s\n", shapes[v[i][0]], v[i][2] ? "beats" : "loses to", shapes[v[i][1]]); } } #endif /* Function return type and identifier on a separate line. * I like that it's easier to grep (e.g. `grep "^main"`) to find functions. */ /* Leaving the argument empty in a declaration (pre C23) would cause the compiler * to not check the arguments that are passed. * Starting with C23 empty parentheses in declarations imply no arguments. * Since this is a definition it doesn't matter but I would say it's good to * include the void anyways so you don't even have to think about this. */ int main(void) { /* Variable definitions near the beginning of a block. * This used to be mandatory in C89 but hasn't been since C99. * I like that it lets me see all the variables used in a single place. * It's also easier to notice when your functions start to get really * crowded with them and it might be a good time to rethink how * you're solving the problem. */ int shape_input; int shape_random; #if TEST test(); return 0; #endif help(false); /* Infinite loop to retry getting input in case it was invalid. */ for (;;) { /* The size 10 was good enough but I'd use something bigger * because if the input will be longer than that it'll get * buffer for the next read from stdin, so you'll get kind of * a weird half response. * 64 bytes just give you more headroom for this and on all the * CPU architectures I'm aware of the stack allocations need to * be aligned to some number of bytes anyways so you're not * even wasting memory. */ char choice[64]; size_t sz; int i; /* Reads sizeof(choice) - 1 bytes or until a newline. * The way you used scanf() you could read too much data into * the buffer causing a buffer overflow. * You can limit the size in the scanf() call but scanf() also * does parsing of strings to different types of values, here * that's not needed since you just need a string so you can * just fgets(). */ if (!fgets(choice, sizeof(choice), stdin)) return 0; /* EOF/ctrl+d */ /* If sizeof(choice) - 1 bytes were read, the newline will be * omitted to fit the NUL terminator, so just strip it always. * You'll also need to strip it for the strcmp() with the * shapes. */ sz = strlen(choice); if (choice[sz - 1] == '\n') choice[sz - 1] = 0; /* !strcmp() is equivalent to strcmp() == 0, just shorter. */ for (i = 0; i < sizeof(shapes) / sizeof(shapes[0]); i++) if (!strcmp(choice, shapes[i])) { shape_input = i; goto choice_ok; } help(true); } choice_ok: /* Or you could use /dev/urandom for less predictability. */ srand(time(NULL)); shape_random = rand() % 3; printf("computer: %s\n", shapes[shape_random]); if (shape_input == shape_random) puts("it's a draw"); else if (beats(shape_input, shape_random)) printf("%s beats %s, you win\n", shapes[shape_input], shapes[shape_random]); else printf("%s beats %s, you lose\n", shapes[shape_random], shapes[shape_input]); return 0; }