From b475e5330aa83586775762e9a3b8e1b3063d625f Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 19 Jan 2012 16:30:04 -0700 Subject: [PATCH] cowd which runs --- packages/cowbull/src/cow.txt | 58 ++++++++++ packages/cowbull/src/cowd.c | 204 ++++++++++++++++++++++++++++------- 2 files changed, 226 insertions(+), 36 deletions(-) create mode 100644 packages/cowbull/src/cow.txt diff --git a/packages/cowbull/src/cow.txt b/packages/cowbull/src/cow.txt new file mode 100644 index 0000000..cceaca7 --- /dev/null +++ b/packages/cowbull/src/cow.txt @@ -0,0 +1,58 @@ +The Cow Game +============ + +You are trying to guess a 4-nybble sequence. Each nybble will have +either 1 or 2 bits set, and the highest bit will never be set. The +game server will tell you how many nybbles in each guess were correct, +and how many had one correct bit. It does not tell you which +positions + + + +The Cow Client +============== + +The client connects to the Cow server running on the IPv6 address +provided in argument 1. If argument 2 is present, the client will +try to run it, providing stdin and stdout as in interactive mode. + +In interactive mode (no argument 2), the client reads a guess in the +form of 4 ASCII numerals, and prints the number of correct nybbles +followed by the number of nybbles with one correct bit. + +Here is an example of a session: + + 1111 + 12 + 2222 + 10 + 4444 + 02 + 4244 + 12 + 1244 + 22 + 1255 + cow:xylep-radar-nanox + + + +The Cow Protocol +================ + +cowd runs on port 3782. + +The client always sends 6 octets. To request a new session, it sends +all zeroes. Otherwise it sends the 4-octet game identifier provided +by the server, concatenated with a 2-octet guess. + +The server will respond with a new game identifier (4 octets) to a new +game request or if the game requested is too old. If a guess is +incorrect, the server will respond with either a 1-octet score in +which the high nybble is the number of correct nybbles in the guess, +and the low nybble is the number of nybbles in the guess with one +correct bit. If a guess is correct, the server will respond with a +token of length 5 octets or more. + +There are multiple tokens, one per number of guesses used, up to +some maximum number of guesses defined per server instance. diff --git a/packages/cowbull/src/cowd.c b/packages/cowbull/src/cowd.c index 14dfed7..33cc22d 100644 --- a/packages/cowbull/src/cowd.c +++ b/packages/cowbull/src/cowd.c @@ -1,27 +1,183 @@ #include -#include +#include +#include +#include +#include +#include +#include +#include + +#define TIMEOUT 30 + +#define NTOKENS 20 +#define TOKENLEN 50 +char tokens[NTOKENS][TOKENLEN]; +int ntokens; + +struct state { + time_t death; + uint16_t answer; + uint16_t guesses; +}; + +#define NSTATES 500 +struct state states[NSTATES] = { 0 }; + +int +bind_port(struct in6_addr *addr, int fd, uint16_t port) +{ + struct sockaddr_in6 saddr = { 0 }; + + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(port); + memcpy(&saddr.sin6_addr, addr, sizeof *addr); + return bind(fd, (struct sockaddr *) &saddr, sizeof saddr); +} + + +struct newgame { + uint16_t offset; + uint16_t token; +}; void -mungle(char *str, int len) +new_game(int sock, time_t now, struct sockaddr_in6 *from, + socklen_t fromlen) { - int i; + int i; + struct newgame g; - for (i = 0; i < len; i += 1) { - str[i] ^= 0xff; + for (g.offset = 0; g.offset < NSTATES; g.offset += 1) { + struct state *s = &states[g.offset]; + + if (s->death < now) { + s->death = now + TIMEOUT; + s->guesses = 0; + s->answer = 0; + + for (i = 0; i < 4; i += 1) { + s->answer = (s->answer << 4) | ((random() % 6) + 1); + } + break; + } + } + + if (g.offset < NSTATES) { + sendto(sock, &g, sizeof(g), 0, (struct sockaddr *) from, fromlen); + } +} + +struct guess { + uint16_t offset; + uint16_t token; + uint16_t guess; +}; + +void +loop(int sock) +{ + struct guess g; + struct state *cur; + struct sockaddr_in6 from; + socklen_t fromlen = sizeof from; + time_t now = time(NULL); + + /* + * Read guess + */ + { + ssize_t inlen; + + inlen = recvfrom(sock, &g, sizeof g, 0, + (struct sockaddr *) &from, &fromlen); + if (inlen != sizeof g) { + return; + } + } + + /* + * Bounds check + */ + if (g.offset >= NSTATES) { + g.offset = 0; + } + cur = &states[g.offset]; + + if ((g.token != cur->answer) || /* Wrong token? */ + (cur->death < now) || /* Old game? */ + (cur->guesses++ > 100)) { /* Too dumb? */ + /* + * Start a new game + */ + new_game(sock, now, &from, fromlen); + return; + } else { + uint8_t reply; + int i; + + for (i = 0; i < 4; i += 1) { + int s = (g.guess >> (i * 4)) & 0xf; + int a = (cur->answer >> (i * 4)) & 0xf; + if ((s < 1) || (s > 7)) { + reply = 0; + break; + } else if (s == a) { + reply += 0x10; + } else if (s & a) { + reply += 0x01; + } + } + + if (reply == 0x40) { + if (cur->guesses > ntokens) { + sendto(sock, tokens[cur->guesses], + strlen(tokens[cur->guesses]), 0, + (struct sockaddr *) &from, fromlen); + } + } else { + sendto(sock, &reply, sizeof reply, 0, (struct sockaddr *) &from, + fromlen); + } } } int main(int argc, char *argv[]) { - long answer = 0; - int i; + long answer = 0; + int sock; + int i; + struct in6_addr addr; - { - struct timeval tv; + srand(time(NULL)); - gettimeofday(&tv, NULL); - srandom(tv.tv_usec); + if (argc > 1) { + if (0 >= inet_pton(AF_INET6, argv[1], &addr)) { + fprintf(stderr, "invalid address: %s\n", argv[1]); + return EX_IOERR; + } + } else { + memcpy(&addr, &in6addr_any, sizeof addr); + } + + /* + * Read in tokens + */ + for (ntokens = 0; ntokens < NTOKENS; ntokens += 1) { + if (NULL == fgets(tokens[ntokens], TOKENLEN, stdin)) { + break; + } + } + printf("Read %d tokens.\n", ntokens); + + /* + * Set up socket + */ + sock = socket(AF_INET6, SOCK_DGRAM, 0); + i = bind_port(&addr, sock, 3782); + if (-1 == i) { + perror("Bind port 3782"); + return EX_IOERR; } for (i = 0; i < 4; i += 1) { @@ -29,31 +185,7 @@ main(int argc, char *argv[]) } while (1) { - char line[20]; - long guess; - int ret = 0; - - if (NULL == fgets(line, sizeof(line), stdin)) { - break; - } - - guess = strtol(line, NULL, 16); - - for (i = 0; i < 4; i += 1) { - int g = (guess >> (i*4)) & 0xf; - int a = (answer >> (i*4)) & 0xf; - - if ((g < 1) || (g > 7)) { - ret = 0; - break; - } else if (g == a) { - ret += 0x10; - } else if (g & a) { - ret += 0x01; - } - } - - printf("%02x\n", ret); + loop(sock); } return 0;