commit 5e36322f2638771f55a3ee8f3c0ab1641c70fdd6 parent c31617451b685cf87e50b3d8e3a33190aefa3081 Author: MikoĊaj Lenczewski <mblenczewski@gmail.com> Date: Fri, 26 Apr 2024 23:22:22 +0000 Simplify build system for server and agents Diffstat:
43 files changed, 1037 insertions(+), 1106 deletions(-)
diff --git a/Makefile b/Makefile @@ -1,91 +0,0 @@ -.PHONY: all build clean cleanall dist extra install uninstall - -PREFIX ?= /usr/local - -CC ?= cc -AR ?= ar -RANLIB ?= ranlib -TAR ?= tar - -SRC := src -INC := include -OBJ := obj - -WARN := -Wall -Wextra -Wpedantic -Werror - -CFLAGS := -std=c11 $(WARN) -Og -g -CPPFLAGS := -I$(INC) -LDFLAGS := - -HEX_SERVER_TARGET := hex-server - -HEX_SERVER_SOURCES := $(SRC)/hex.c \ - $(SRC)/server.c \ - $(SRC)/board.c \ - $(SRC)/proto.c \ - $(SRC)/utils.c - -HEX_SERVER_OBJECTS := $(HEX_SERVER_SOURCES:$(SRC)/%.c=$(OBJ)/%.o) -HEX_SERVER_OBJDEPS := $(HEX_SERVER_OBJECTS:%.o=%.d) -HEX_SERVER_FLAGS := - -HEX_AGENT_SOURCES := $(wildcard agents/*) - -ARCHIVE_TARGET := hex-server.tar - -ARCHIVE_SOURCES := .editorconfig \ - .gitignore \ - Makefile \ - README.md \ - schedule.txt \ - tournament-host.py \ - agents/ \ - include/ \ - src/ - -HEX_AGENT_USERS := $(shell seq 1 16) - -all: build extra - -build: $(HEX_SERVER_TARGET) - -clean: - rm -rf $(HEX_SERVER_TARGET) $(OBJ) - -cleanall: clean | $(HEX_AGENT_SOURCES) - @for d in $(HEX_AGENT_SOURCES); do make -C $$d clean; done - -dist: cleanall - $(TAR) cf $(ARCHIVE_TARGET) $(ARCHIVE_SOURCES) - -extra: | $(HEX_AGENT_SOURCES) - @for d in $(HEX_AGENT_SOURCES); do make -C $$d; done - -install: build - for i in $(HEX_AGENT_USERS); do \ - if [ ! $$(id hex-agent-$$i -u 2>/dev/null) ]; then \ - useradd -M -N -e '' hex-agent-$$i; \ - fi; \ - done - mkdir -p $(DESTDIR)$(PREFIX)/bin - install -m 0755 $(HEX_SERVER_TARGET) $(DESTDIR)$(PREFIX)/bin/$(HEX_SERVER_TARGET) - -uninstall: - for i in $(HEX_AGENT_USERS); do \ - if [ $$(id hex-agent-$$i -u 2>/dev/null) ]; then \ - userdel hex-agent-$$i; \ - fi; \ - done - rm -f $(DESTDIR)$(PREFIX)/bin/$(HEX_SERVER_TARGET) - -$(HEX_SERVER_TARGET): $(HEX_SERVER_OBJECTS) - $(CC) -o $@ $^ $(LDFLAGS) $(HEX_SERVER_FLAGS) - --include $(HEX_SERVER_OBJDEPS) - -$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) - @mkdir -p $(dir $@) - $(CC) -MMD -o $@ -c $< $(CFLAGS) $(CPPFLAGS) - -$(OBJ): - mkdir $@ diff --git a/README.txt b/README.txt @@ -16,25 +16,35 @@ thread limit is 4, this agent cannot be used without increasing the limit to hex-server: Building ------------------------------------------------------------------------------ -To build the server, run the following shell commands in the project's root -directory: +To build the server and all agents, run the following shell command in the +project's root directory: ```sh -$ make all # default, builds the hex server and all included agents -$ make build # optional, builds only the hex server -$ make extra # optional, builds all included agents +$ ./build.sh ``` -To clean all built artefacts, run the following shell commands: +To clean all built artefacts, run the following shell command: ```sh -$ make clean # cleans only the hex server binary -$ make cleanall # cleans the hex server binary and all agent artefacts +$ ./clean.sh +``` +Before running the hex-server, or the tournament-host.py helper script, a +number of hex agent runner users must be added to the system. This is done +to enable hard limits on the number of threads and memory any given agent is +able to use (avoiding starving out other agents, or killing the server or +opponent agent). To install these users, run the following shell command: +```sh +$ sudo ./install # this must be ran as root, to be able to add users +``` + +To remove the previously added users, run the followind shell command: +```sh +$ sudo ./uninstall ``` hex-server: Usage ------------------------------------------------------------------------------ The server can be invoked using the following shell command: ```sh -$ sudo hex-server -a <agent-1> -ua <uid> -b <agent-2> -ub <uid> \ +$ sudo ./server/bin/hex-server -a <agent-1> -ua <uid> -b <agent-2> -ub <uid> \ [-d 11] [-s 300] [-t 4] [-m 1024] [-v] ``` diff --git a/agents/example_c_agent/Makefile b/agents/example_c_agent/Makefile @@ -1,38 +0,0 @@ -.PHONY: all build clean - -CC ?= cc - -SRC := src -INC := include -DEPINC := ../../include -OBJ := obj - -WARN := -Wall -Wextra -Wpedantic -Werror - -CFLAGS := -std=c11 $(WARN) -Og -g -CPPFLAGS := -I$(INC) -I$(DEPINC) -LDFLAGS := - -TARGET := agent -SOURCES := $(SRC)/agent.c -OBJECTS := $(SOURCES:$(SRC)/%.c=$(OBJ)/%.o) -OBJDEPS := $(OBJECTS:%.o=%.d) - -all: build - -build: $(TARGET) - -clean: - rm -rf $(TARGET) $(OBJ) - -$(TARGET): $(OBJECTS) - $(CC) -o $@ $^ $(LDFLAGS) - --include $(OBJDEPS) - -$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) - @mkdir -p $(dir $@) - $(CC) -MMD -o $@ -c $< $(CFLAGS) $(CPPFLAGS) - -$(OBJ): - mkdir -p $@ diff --git a/agents/example_c_agent/agent.c b/agents/example_c_agent/agent.c @@ -0,0 +1,465 @@ +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif + +#define _XOPEN_SOURCE 700 + +#include "hex/types.h" +#include "hex/proto.h" + +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +int +net_init(char *restrict host, char *restrict port); + +bool +net_recv_msg(int sock, struct hex_msg *out, enum hex_msg_type expected[], size_t len); + +bool +net_send_msg(int sock, struct hex_msg *msg); + +enum board_cell { + CELL_BLACK = HEX_PLAYER_BLACK, + CELL_WHITE = HEX_PLAYER_WHITE, + CELL_EMPTY, +}; + +struct move { + u32 x, y; +}; + +void +move_swap(struct move *restrict lhs, struct move *restrict rhs); + +struct board { + u32 size; + + enum board_cell *cells; + + size_t moves_len; + struct move *moves; +}; + +bool +board_init(struct board *self, u32 size); + +bool +board_play(struct board *self, enum hex_player player, u32 x, u32 y); + +void +board_swap(struct board *self); + +bool +board_next(struct board *self, u32 *out_x, u32 *out_y); + +enum game_state { + GAME_START, + GAME_RECV, + GAME_SEND, + GAME_END, +}; + +int +main(int argc, char **argv) +{ + srandom(getpid()); + + if (argc < 3) { + fprintf(stderr, "Not enough args: %s <host> <port>\n", argv[0]); + exit(EXIT_FAILURE); + } + + char *host = argv[1], *port = argv[2]; + + int sockfd = net_init(host, port); + if (sockfd == -1) { + fprintf(stderr, "Failed to initialise network\n"); + exit(EXIT_FAILURE); + } + + enum game_state game_state = GAME_START; + struct board board; + + /* initialised to satisfy GCC's linters and sanitisers */ + enum hex_player player = HEX_PLAYER_BLACK; + enum hex_player opponent = HEX_PLAYER_WHITE; + enum hex_player winner = HEX_PLAYER_BLACK; + + u32 game_secs, thread_limit, mem_limit_mib; // currently unused + (void) game_secs; (void) thread_limit; (void) mem_limit_mib; + + bool game_over = false, first_round = true; + while (!game_over) { + switch (game_state) { + case GAME_START: { + enum hex_msg_type expected_msg_types[] = { + HEX_MSG_START, + }; + + struct hex_msg msg; + if (!net_recv_msg(sockfd, &msg, expected_msg_types, ARRLEN(expected_msg_types))) { + fprintf(stderr, "Failed to receive message from hex server\n"); + exit(EXIT_FAILURE); + } + + // unpack all parameters + player = msg.data.start.player; + opponent = hexopponent(player); + game_secs = msg.data.start.game_secs; + thread_limit = msg.data.start.thread_limit; + mem_limit_mib = msg.data.start.mem_limit_mib; + + u32 board_size = msg.data.start.board_size; + + if (!board_init(&board, board_size)) { + fprintf(stderr, "Failed to allocate game board of size %" PRIu32 "x%" PRIu32 "\n", + board_size, board_size); + exit(EXIT_FAILURE); + } + + printf("[%s] Starting game: %" PRIu32 "x%" PRIu32 ", %" PRIu32 " secs\n", + hexplayerstr(player), board_size, board_size, game_secs); + + switch (player) { + case HEX_PLAYER_BLACK: game_state = GAME_SEND; break; + case HEX_PLAYER_WHITE: game_state = GAME_RECV; break; + } + } break; + + case GAME_RECV: { + enum hex_msg_type expected_msg_types[] = { + HEX_MSG_MOVE, + HEX_MSG_SWAP, + HEX_MSG_END, + }; + + struct hex_msg msg; + if (!net_recv_msg(sockfd, &msg, expected_msg_types, ARRLEN(expected_msg_types))) { + fprintf(stderr, "Failed to receive message from hex server\n"); + exit(EXIT_FAILURE); + } + + switch (msg.type) { + case HEX_MSG_MOVE: + board_play(&board, opponent, msg.data.move.board_x, msg.data.move.board_y); + + if (first_round && random() % 2) { + board_swap(&board); + + msg.type = HEX_MSG_SWAP; + if (!net_send_msg(sockfd, &msg)) { + fprintf(stderr, "Failed to send swap message to hex server\n"); + exit(EXIT_FAILURE); + } + + game_state = GAME_RECV; + } else { + game_state = GAME_SEND; + } + break; + + case HEX_MSG_SWAP: + board_swap(&board); + game_state = GAME_SEND; + break; + + case HEX_MSG_END: + winner = msg.data.end.winner; + game_state = GAME_END; + break; + } + + first_round = false; + } break; + + case GAME_SEND: { + struct hex_msg msg = { + .type = HEX_MSG_MOVE, + }; + + if (!board_next(&board, &msg.data.move.board_x, &msg.data.move.board_y)) { + fprintf(stderr, "Failed to generate next board move\n"); + exit(EXIT_FAILURE); + } + + board_play(&board, player, msg.data.move.board_x, msg.data.move.board_y); + + if (!net_send_msg(sockfd, &msg)) { + fprintf(stderr, "Failed to send message to hex server\n"); + exit(EXIT_FAILURE); + } + + game_state = GAME_RECV; + first_round = false; + } break; + + case GAME_END: { + printf("[%s] Player %s has won the game\n", + hexplayerstr(player), hexplayerstr(winner)); + game_over = true; + } break; + + default: + fprintf(stderr, "Unknown game state: %d\n", game_state); + exit(EXIT_FAILURE); + break; + } + } + + exit(EXIT_SUCCESS); +} + +int +net_init(char *restrict host, char *restrict port) +{ + assert(host); + assert(port); + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, *addrinfo, *ptr; + + int res; + if ((res = getaddrinfo(host, port, &hints, &addrinfo))) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res)); + return -1; + } + + int sockfd; + for (ptr = addrinfo; ptr; ptr = ptr->ai_next) { + sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); + if (sockfd == -1) continue; + if (connect(sockfd, ptr->ai_addr, ptr->ai_addrlen) != -1) break; + close(sockfd); + } + + freeaddrinfo(addrinfo); + + if (!ptr) { + fprintf(stderr, "Failed to connect to %s:%s\n", host, port); + return -1; + } + + return sockfd; +} + +static inline size_t +net_recv_all(int sock, u8 *buf, size_t len) +{ + assert(buf); + + size_t nbytes_received = 0; + + do { + ssize_t res = recv(sock, buf + nbytes_received, len - nbytes_received, 0); + if (res <= 0) break; // error or socket shutdown + nbytes_received += res; + } while (nbytes_received < len); + + return nbytes_received; +} + +bool +net_recv_msg(int sock, struct hex_msg *out, enum hex_msg_type expected[], size_t len) +{ + assert(out); + assert(expected); + + u8 buf[HEX_MSG_SZ]; + if (!(net_recv_all(sock, buf, HEX_MSG_SZ) == HEX_MSG_SZ)) return false; + + struct hex_msg msg; + if (!hex_msg_try_deserialise(buf, &msg)) return false; + + for (size_t i = 0; i < len; i++) { + if (msg.type == expected[i]) { + *out = msg; + return true; + } + } + + return false; +} + +static inline size_t +net_send_all(int sock, u8 *buf, size_t len) +{ + assert(buf); + + size_t nbytes_sent = 0; + + do { + ssize_t res = send(sock, buf + nbytes_sent, len - nbytes_sent, 0); + if (res <= 0) break; // error or socket shutdown + nbytes_sent += res; + } while (nbytes_sent < len); + + return nbytes_sent; +} + +bool +net_send_msg(int sock, struct hex_msg *msg) +{ + assert(msg); + + u8 buf[HEX_MSG_SZ]; + if (!hex_msg_try_serialise(msg, buf)) return false; + + return net_send_all(sock, buf, HEX_MSG_SZ) == HEX_MSG_SZ; +} + +void +move_swap(struct move *restrict lhs, struct move *restrict rhs) +{ + assert(lhs); + assert(rhs); + + struct move tmp = *lhs; + *lhs = *rhs; + *rhs = tmp; +} + +static void +shuffle_moves(struct move *arr, size_t len) +{ + for (size_t i = 0; i < len - 2; i++) { + size_t j = (i + random()) % len; + move_swap(&arr[i], &arr[j]); + } +} + +bool +board_init(struct board *self, u32 size) +{ + assert(self); + + self->size = size; + + if (!(self->cells = malloc(size * size * sizeof *self->cells))) + return false; + + self->moves_len = size * size; + if (!(self->moves = malloc(size * size * sizeof *self->moves))) { + free(self->cells); + return false; + } + + for (size_t j = 0; j < size; j++) { + for (size_t i = 0; i < size; i++) { + size_t idx = j * size + i; + + self->cells[idx] = CELL_EMPTY; + + self->moves[idx].x = i; + self->moves[idx].y = j; + } + } + + shuffle_moves(self->moves, self->moves_len); + + return true; +} + +bool +board_play(struct board *self, enum hex_player player, u32 x, u32 y) +{ + assert(self); + + enum board_cell *cell = &self->cells[y * self->size + x]; + if (*cell != CELL_EMPTY) return false; + + switch (player) { + case HEX_PLAYER_BLACK: + *cell = CELL_BLACK; + break; + + case HEX_PLAYER_WHITE: + *cell = CELL_WHITE; + break; + + default: + return false; + } + + for (size_t i = 0; i < self->moves_len; i++) { + if (self->moves[i].x == x && self->moves[i].y == y) { + move_swap(&self->moves[i], &self->moves[--self->moves_len]); + break; + } + } + + return true; +} + +void +board_swap(struct board *self) +{ + assert(self); + + self->moves_len = 0; + + for (size_t j = 0; j < self->size; j++) { + for (size_t i = 0; i < self->size; i++) { + enum board_cell *cell = &self->cells[j * self->size + i]; + + switch (*cell) { + case CELL_BLACK: + *cell = CELL_WHITE; + break; + + case CELL_WHITE: + *cell = CELL_BLACK; + break; + + default: { + struct move *move = &self->moves[self->moves_len++]; + move->x = i; + move->y = j; + } break; + } + } + } + + shuffle_moves(self->moves, self->moves_len); +} + +bool +board_next(struct board *self, u32 *out_x, u32 *out_y) +{ + assert(self); + assert(out_x); + assert(out_y); + + if (self->moves_len == 0) return false; + + struct move move = self->moves[--self->moves_len]; + *out_x = move.x; + *out_y = move.y; + + return true; +} + +extern inline b32 +hex_msg_try_serialise(struct hex_msg const *msg, u8 out[static HEX_MSG_SZ]); + +extern inline b32 +hex_msg_try_deserialise(u8 buf[static HEX_MSG_SZ], struct hex_msg *out); + +extern inline char const * +hexplayerstr(enum hex_player val); + +extern inline enum hex_player +hexopponent(enum hex_player val); diff --git a/agents/example_c_agent/build.sh b/agents/example_c_agent/build.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +CC="${CC:-cc}" + +WARNINGS="-Wall -Wextra -Wpedantic -Werror" + +CFLAGS="-std=c11 -Og -g" +CPPFLAGS="-UNDEBUG -Iinclude -I../../server/include" +LDFLAGS="" + +TARGET="agent" + +SOURCES=" + agent.c +" + +set -ex + +mkdir -p bin + +$CC -o bin/$TARGET $SOURCES $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS diff --git a/agents/example_c_agent/clean.sh b/agents/example_c_agent/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin diff --git a/agents/example_c_agent/include/agent.h b/agents/example_c_agent/include/agent.h @@ -1,27 +0,0 @@ -#ifndef AGENT_H -#define AGENT_H - -#ifdef _XOPEN_SOURCE -#undef _XOPEN_SOURCE -#endif - -#define _XOPEN_SOURCE 700 - -#include "hex/types.h" -#include "hex/proto.h" - -#include <assert.h> -#include <errno.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <arpa/inet.h> -#include <netdb.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#endif /* AGENT_H */ diff --git a/agents/example_c_agent/run.sh b/agents/example_c_agent/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -exec $(dirname $0)/agent $@ +exec $(dirname $0)/bin/agent $@ diff --git a/agents/example_c_agent/src/agent.c b/agents/example_c_agent/src/agent.c @@ -1,444 +0,0 @@ -#include "agent.h" - -int -net_init(char *restrict host, char *restrict port); - -bool -net_recv_msg(int sock, struct hex_msg *out, enum hex_msg_type expected[], size_t len); - -bool -net_send_msg(int sock, struct hex_msg *msg); - -enum board_cell { - CELL_BLACK = HEX_PLAYER_BLACK, - CELL_WHITE = HEX_PLAYER_WHITE, - CELL_EMPTY, -}; - -struct move { - u32 x, y; -}; - -void -move_swap(struct move *restrict lhs, struct move *restrict rhs); - -struct board { - u32 size; - - enum board_cell *cells; - - size_t moves_len; - struct move *moves; -}; - -bool -board_init(struct board *self, u32 size); - -bool -board_play(struct board *self, enum hex_player player, u32 x, u32 y); - -void -board_swap(struct board *self); - -bool -board_next(struct board *self, u32 *out_x, u32 *out_y); - -enum game_state { - GAME_START, - GAME_RECV, - GAME_SEND, - GAME_END, -}; - -int -main(int argc, char **argv) -{ - srandom(getpid()); - - if (argc < 3) { - fprintf(stderr, "Not enough args: %s <host> <port>\n", argv[0]); - exit(EXIT_FAILURE); - } - - char *host = argv[1], *port = argv[2]; - - int sockfd = net_init(host, port); - if (sockfd == -1) { - fprintf(stderr, "Failed to initialise network\n"); - exit(EXIT_FAILURE); - } - - enum game_state game_state = GAME_START; - struct board board; - - /* initialised to satisfy GCC's linters and sanitisers */ - enum hex_player player = HEX_PLAYER_BLACK; - enum hex_player opponent = HEX_PLAYER_WHITE; - enum hex_player winner = HEX_PLAYER_BLACK; - - u32 game_secs, thread_limit, mem_limit_mib; // currently unused - (void) game_secs; (void) thread_limit; (void) mem_limit_mib; - - bool game_over = false, first_round = true; - while (!game_over) { - switch (game_state) { - case GAME_START: { - enum hex_msg_type expected_msg_types[] = { - HEX_MSG_START, - }; - - struct hex_msg msg; - if (!net_recv_msg(sockfd, &msg, expected_msg_types, ARRLEN(expected_msg_types))) { - fprintf(stderr, "Failed to receive message from hex server\n"); - exit(EXIT_FAILURE); - } - - // unpack all parameters - player = msg.data.start.player; - opponent = hexopponent(player); - game_secs = msg.data.start.game_secs; - thread_limit = msg.data.start.thread_limit; - mem_limit_mib = msg.data.start.mem_limit_mib; - - u32 board_size = msg.data.start.board_size; - - if (!board_init(&board, board_size)) { - fprintf(stderr, "Failed to allocate game board of size %" PRIu32 "x%" PRIu32 "\n", - board_size, board_size); - exit(EXIT_FAILURE); - } - - printf("[%s] Starting game: %" PRIu32 "x%" PRIu32 ", %" PRIu32 " secs\n", - hexplayerstr(player), board_size, board_size, game_secs); - - switch (player) { - case HEX_PLAYER_BLACK: game_state = GAME_SEND; break; - case HEX_PLAYER_WHITE: game_state = GAME_RECV; break; - } - } break; - - case GAME_RECV: { - enum hex_msg_type expected_msg_types[] = { - HEX_MSG_MOVE, - HEX_MSG_SWAP, - HEX_MSG_END, - }; - - struct hex_msg msg; - if (!net_recv_msg(sockfd, &msg, expected_msg_types, ARRLEN(expected_msg_types))) { - fprintf(stderr, "Failed to receive message from hex server\n"); - exit(EXIT_FAILURE); - } - - switch (msg.type) { - case HEX_MSG_MOVE: - board_play(&board, opponent, msg.data.move.board_x, msg.data.move.board_y); - - if (first_round && random() % 2) { - board_swap(&board); - - msg.type = HEX_MSG_SWAP; - if (!net_send_msg(sockfd, &msg)) { - fprintf(stderr, "Failed to send swap message to hex server\n"); - exit(EXIT_FAILURE); - } - - game_state = GAME_RECV; - } else { - game_state = GAME_SEND; - } - break; - - case HEX_MSG_SWAP: - board_swap(&board); - game_state = GAME_SEND; - break; - - case HEX_MSG_END: - winner = msg.data.end.winner; - game_state = GAME_END; - break; - } - - first_round = false; - } break; - - case GAME_SEND: { - struct hex_msg msg = { - .type = HEX_MSG_MOVE, - }; - - if (!board_next(&board, &msg.data.move.board_x, &msg.data.move.board_y)) { - fprintf(stderr, "Failed to generate next board move\n"); - exit(EXIT_FAILURE); - } - - board_play(&board, player, msg.data.move.board_x, msg.data.move.board_y); - - if (!net_send_msg(sockfd, &msg)) { - fprintf(stderr, "Failed to send message to hex server\n"); - exit(EXIT_FAILURE); - } - - game_state = GAME_RECV; - first_round = false; - } break; - - case GAME_END: { - printf("[%s] Player %s has won the game\n", - hexplayerstr(player), hexplayerstr(winner)); - game_over = true; - } break; - - default: - fprintf(stderr, "Unknown game state: %d\n", game_state); - exit(EXIT_FAILURE); - break; - } - } - - exit(EXIT_SUCCESS); -} - -int -net_init(char *restrict host, char *restrict port) -{ - assert(host); - assert(port); - - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - }, *addrinfo, *ptr; - - int res; - if ((res = getaddrinfo(host, port, &hints, &addrinfo))) { - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res)); - return -1; - } - - int sockfd; - for (ptr = addrinfo; ptr; ptr = ptr->ai_next) { - sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); - if (sockfd == -1) continue; - if (connect(sockfd, ptr->ai_addr, ptr->ai_addrlen) != -1) break; - close(sockfd); - } - - freeaddrinfo(addrinfo); - - if (!ptr) { - fprintf(stderr, "Failed to connect to %s:%s\n", host, port); - return -1; - } - - return sockfd; -} - -static inline size_t -net_recv_all(int sock, u8 *buf, size_t len) -{ - assert(buf); - - size_t nbytes_received = 0; - - do { - ssize_t res = recv(sock, buf + nbytes_received, len - nbytes_received, 0); - if (res <= 0) break; // error or socket shutdown - nbytes_received += res; - } while (nbytes_received < len); - - return nbytes_received; -} - -bool -net_recv_msg(int sock, struct hex_msg *out, enum hex_msg_type expected[], size_t len) -{ - assert(out); - assert(expected); - - u8 buf[HEX_MSG_SZ]; - if (!(net_recv_all(sock, buf, HEX_MSG_SZ) == HEX_MSG_SZ)) return false; - - struct hex_msg msg; - if (!hex_msg_try_deserialise(buf, &msg)) return false; - - for (size_t i = 0; i < len; i++) { - if (msg.type == expected[i]) { - *out = msg; - return true; - } - } - - return false; -} - -static inline size_t -net_send_all(int sock, u8 *buf, size_t len) -{ - assert(buf); - - size_t nbytes_sent = 0; - - do { - ssize_t res = send(sock, buf + nbytes_sent, len - nbytes_sent, 0); - if (res <= 0) break; // error or socket shutdown - nbytes_sent += res; - } while (nbytes_sent < len); - - return nbytes_sent; -} - -bool -net_send_msg(int sock, struct hex_msg *msg) -{ - assert(msg); - - u8 buf[HEX_MSG_SZ]; - if (!hex_msg_try_serialise(msg, buf)) return false; - - return net_send_all(sock, buf, HEX_MSG_SZ) == HEX_MSG_SZ; -} - -void -move_swap(struct move *restrict lhs, struct move *restrict rhs) -{ - assert(lhs); - assert(rhs); - - struct move tmp = *lhs; - *lhs = *rhs; - *rhs = tmp; -} - -static void -shuffle_moves(struct move *arr, size_t len) -{ - for (size_t i = 0; i < len - 2; i++) { - size_t j = (i + random()) % len; - move_swap(&arr[i], &arr[j]); - } -} - -bool -board_init(struct board *self, u32 size) -{ - assert(self); - - self->size = size; - - if (!(self->cells = malloc(size * size * sizeof *self->cells))) - return false; - - self->moves_len = size * size; - if (!(self->moves = malloc(size * size * sizeof *self->moves))) { - free(self->cells); - return false; - } - - for (size_t j = 0; j < size; j++) { - for (size_t i = 0; i < size; i++) { - size_t idx = j * size + i; - - self->cells[idx] = CELL_EMPTY; - - self->moves[idx].x = i; - self->moves[idx].y = j; - } - } - - shuffle_moves(self->moves, self->moves_len); - - return true; -} - -bool -board_play(struct board *self, enum hex_player player, u32 x, u32 y) -{ - assert(self); - - enum board_cell *cell = &self->cells[y * self->size + x]; - if (*cell != CELL_EMPTY) return false; - - switch (player) { - case HEX_PLAYER_BLACK: - *cell = CELL_BLACK; - break; - - case HEX_PLAYER_WHITE: - *cell = CELL_WHITE; - break; - - default: - return false; - } - - for (size_t i = 0; i < self->moves_len; i++) { - if (self->moves[i].x == x && self->moves[i].y == y) { - move_swap(&self->moves[i], &self->moves[--self->moves_len]); - break; - } - } - - return true; -} - -void -board_swap(struct board *self) -{ - assert(self); - - self->moves_len = 0; - - for (size_t j = 0; j < self->size; j++) { - for (size_t i = 0; i < self->size; i++) { - enum board_cell *cell = &self->cells[j * self->size + i]; - - switch (*cell) { - case CELL_BLACK: - *cell = CELL_WHITE; - break; - - case CELL_WHITE: - *cell = CELL_BLACK; - break; - - default: { - struct move *move = &self->moves[self->moves_len++]; - move->x = i; - move->y = j; - } break; - } - } - } - - shuffle_moves(self->moves, self->moves_len); -} - -bool -board_next(struct board *self, u32 *out_x, u32 *out_y) -{ - assert(self); - assert(out_x); - assert(out_y); - - if (self->moves_len == 0) return false; - - struct move move = self->moves[--self->moves_len]; - *out_x = move.x; - *out_y = move.y; - - return true; -} - -extern inline b32 -hex_msg_try_serialise(struct hex_msg const *msg, u8 out[static HEX_MSG_SZ]); - -extern inline b32 -hex_msg_try_deserialise(u8 buf[static HEX_MSG_SZ], struct hex_msg *out); - -extern inline char const * -hexplayerstr(enum hex_player val); - -extern inline enum hex_player -hexopponent(enum hex_player val); diff --git a/agents/example_cpp_agent/Makefile b/agents/example_cpp_agent/Makefile @@ -1,38 +0,0 @@ -.PHONY: all build clean - -CCX ?= c++ - -SRC := src -INC := include -DEPINC := ../../include -OBJ := obj - -WARN := -Wall -Wextra -Wpedantic -Werror - -CFLAGS := -std=c++14 $(WARN) -Og -g -CPPFLAGS := -I$(INC) -I$(DEPINC) -LDFLAGS := - -TARGET := agent -SOURCES := $(SRC)/agent.cpp -OBJECTS := $(SOURCES:$(SRC)/%.cpp=$(OBJ)/%.o) -OBJDEPS := $(OBJECTS:%.o=%.d) - -all: build - -build: $(TARGET) - -clean: - rm -rf $(TARGET) $(OBJ) - -$(TARGET): $(OBJECTS) - $(CCX) -o $@ $^ $(LDFLAGS) - --include $(OBJDEPS) - -$(OBJ)/%.o: $(SRC)/%.cpp | $(OBJ) - @mkdir -p $(dir $@) - $(CCX) -MMD -o $@ -c $< $(CFLAGS) $(CPPFLAGS) - -$(OBJ): - mkdir -p $@ diff --git a/agents/example_cpp_agent/agent.cpp b/agents/example_cpp_agent/agent.cpp @@ -0,0 +1,357 @@ +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif + +#define _XOPEN_SOURCE 700 + +#include "hex/types.h" +#include "hex/proto.h" + +#include <cassert> +#include <cerrno> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <array> +#include <iostream> +#include <memory> +#include <random> +#include <vector> + +enum class Cell { + BLACK = HEX_PLAYER_BLACK, + WHITE = HEX_PLAYER_WHITE, + EMPTY, +}; + +struct Move { + u32 x, y; + + bool operator==(const Move &rhs) { + return this->x == rhs.x && this->y == rhs.y; + } +}; + +void swap(Move &lhs, Move &rhs) { + std::swap(lhs.x, rhs.x); + std::swap(lhs.y, rhs.y); +} + +class Board { + u32 size; + std::vector<Cell> cells; + std::vector<Move> moves; + +public: + template <class URBG> + Board(u32 size, URBG &&rng) : size(size) { + cells.reserve(size * size); + moves.reserve(size * size); + + for (u32 j = 0; j < this->size; j++) { + for (u32 i = 0; i < this->size; i++) { + this->cells.push_back(Cell::EMPTY); + + Move move{i, j}; + this->moves.push_back(move); + } + } + + std::shuffle(this->moves.begin(), this->moves.end(), rng); + } + + bool play(enum hex_player player, u32 x, u32 y) { + Cell &cell = this->cells.at(y * this->size + x); + if (cell != Cell::EMPTY) return false; + + switch (player) { + case HEX_PLAYER_BLACK: + cell = Cell::BLACK; + break; + + case HEX_PLAYER_WHITE: + cell = Cell::WHITE; + break; + } + + Move move{x, y}; + auto it = std::find(this->moves.begin(), this->moves.end(), move); + if (it != std::end(this->moves)) { + ::swap(*it, this->moves.back()); + this->moves.pop_back(); + } + + return true; + } + + template <class URBG> + void swap(URBG &&rng) { + this->moves.clear(); + + for (u32 j = 0; j < this->size; j++) { + for (u32 i = 0; i < this->size; i++) { + Cell &cell = this->cells.at(j * this->size + i); + + switch (cell) { + case Cell::BLACK: cell = Cell::WHITE; break; + case Cell::WHITE: cell = Cell::BLACK; break; + case Cell::EMPTY: + Move move{i, j}; + this->moves.push_back(move); + break; + } + } + } + + std::shuffle(this->moves.begin(), this->moves.end(), rng); + } + + bool next(Move &out) { + if (this->moves.empty()) return false; + + out = this->moves.back(); + this->moves.pop_back(); + + return true; + } +}; + +class Net { + int sockfd; +public: + Net() : sockfd(-1) {} + + bool init(char *host, char *port) { + struct addrinfo hints, *addrinfo, *ptr; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int res; + if ((res = getaddrinfo(host, port, &hints, &addrinfo))) { + std::cerr << "getaddrinfo: " << gai_strerror(res) << std::endl; + return false; + } + + int sockfd; + for (ptr = addrinfo; ptr; ptr = ptr->ai_next) { + sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); + if (sockfd == -1) continue; + if (connect(sockfd, ptr->ai_addr, ptr->ai_addrlen) != -1) break; + close(sockfd); + } + + freeaddrinfo(addrinfo); + + if (!ptr) { + std::cerr << "Failed to connect to " << host << ":" << port << std::endl; + return false; + } + + this->sockfd = sockfd; + + return true; + } + + bool recv_msg(struct hex_msg &out, const std::vector<enum hex_msg_type> &expected) { + u8 buf[HEX_MSG_SZ]; + + size_t nbytes_recv = 0, len = HEX_MSG_SZ; + + do { + ssize_t curr = recv(this->sockfd, buf + nbytes_recv, len - nbytes_recv, 0); + if (curr <= 0) return false; // error or socket shutdown + nbytes_recv += curr; + } while (nbytes_recv < len); + + struct hex_msg msg; + if (!hex_msg_try_deserialise(buf, &msg)) return false; + + if (std::find(expected.begin(), expected.end(), msg.type) != std::end(expected)) { + out = msg; + return true; + } + + return false; + } + + bool send_msg(const struct hex_msg &msg) { + u8 buf[HEX_MSG_SZ]; + if (!hex_msg_try_serialise(&msg, buf)) return false; + + size_t nbytes_sent = 0, len = HEX_MSG_SZ; + + do { + ssize_t curr = send(this->sockfd, buf + nbytes_sent, len - nbytes_sent, 0); + if (curr <= 0) return false; // error or socket shutdown + nbytes_sent += curr; + } while (nbytes_sent < len); + + return true; + } +}; + +enum class State { + START, + RECV, + SEND, + END, +}; + +std::ostream &operator<<(std::ostream &os, const State &self) { + return os << static_cast<std::underlying_type<State>::type>(self); +} + +int +main(int argc, char *argv[]) +{ + std::minstd_rand rand; + rand.seed(getpid()); + + if (argc < 3) { + std::cerr << "Not enough args: " << argv[0] << " <host> <port>" << std::endl; + exit(EXIT_FAILURE); + } + + char *host = argv[1], *port = argv[2]; + + Net net; + if (!net.init(host, port)) { + std::cerr << "Failed to initialise network" << std::endl; + exit(EXIT_FAILURE); + } + + State state = State::START; + std::unique_ptr<Board> board; + + /* initialised to satisfy GCC's linter and sanitiser */ + enum hex_player player = HEX_PLAYER_BLACK; + enum hex_player opponent = HEX_PLAYER_WHITE; + enum hex_player winner = HEX_PLAYER_BLACK; + + // game parameters (unused) + u32 game_secs, thread_limit, mem_limit_mib; + (void) game_secs; (void) thread_limit; (void) mem_limit_mib; + + bool game_over = false, first_round = true; + while (!game_over) { + switch (state) { + case State::START: { + std::vector<enum hex_msg_type> expected_msg_types = {HEX_MSG_START}; + + struct hex_msg msg; + if (!net.recv_msg(msg, expected_msg_types)) { + std::cerr << "Failed to receive message from hex server" << std::endl; + exit(EXIT_FAILURE); + } + + player = static_cast<enum hex_player>(msg.data.start.player); + opponent = hexopponent(player); + game_secs = msg.data.start.game_secs; + thread_limit = msg.data.start.thread_limit; + mem_limit_mib = msg.data.start.mem_limit_mib; + + u32 board_size = msg.data.start.board_size; + + board = std::make_unique<Board>(board_size, rand); + + std::cout << "[" << hexplayerstr(player) << "] Starting game: " + << board_size << "x" << board_size << ", " + << game_secs << "secs" << std::endl; + + switch (player) { + case HEX_PLAYER_BLACK: state = State::SEND; break; + case HEX_PLAYER_WHITE: state = State::RECV; break; + } + } break; + + case State::RECV: { + std::vector<enum hex_msg_type> expected_msg_types = {HEX_MSG_MOVE, HEX_MSG_SWAP, HEX_MSG_END}; + + struct hex_msg msg; + if (!net.recv_msg(msg, expected_msg_types)) { + std::cerr << "Failed to receive message from hex server" << std::endl; + exit(EXIT_FAILURE); + } + + switch (msg.type) { + case HEX_MSG_MOVE: + board->play(opponent, msg.data.move.board_x, msg.data.move.board_y); + + if (first_round && rand() % 2) { + board->swap(rand); + + msg.type = HEX_MSG_SWAP; + if (!net.send_msg(msg)) { + std::cerr << "Failed to send message to hex server" << std::endl; + exit(EXIT_FAILURE); + } + + state = State::RECV; + } else { + state = State::SEND; + } + break; + + case HEX_MSG_SWAP: + board->swap(rand); + state = State::SEND; + break; + + case HEX_MSG_END: + winner = static_cast<enum hex_player>(msg.data.end.winner); + state = State::END; + break; + } + + first_round = false; + } break; + + case State::SEND: { + struct hex_msg msg; + msg.type = HEX_MSG_MOVE; + + Move move; + if (!board->next(move)) { + std::cerr << "Failed to generate next board move" << std::endl; + exit(EXIT_FAILURE); + } + + board->play(player, move.x, move.y); + + msg.data.move.board_x = move.x; + msg.data.move.board_y = move.y; + + if (!net.send_msg(msg)) { + std::cerr << "Failed to send message to hex server" << std::endl; + exit(EXIT_FAILURE); + } + + state = State::RECV; + first_round = false; + } break; + + case State::END: { + std::cout << "[" << hexplayerstr(player) << "] Player " << hexplayerstr(winner) << " has won the game" << std::endl; + game_over = true; + } break; + + default: + std::cerr << "Unknown game state: " << state << std::endl; + exit(EXIT_FAILURE); + break; + } + } + + exit(EXIT_SUCCESS); +} diff --git a/agents/example_cpp_agent/build.sh b/agents/example_cpp_agent/build.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +CXX="${CXX:-c++}" + +WARNINGS="-Wall -Wextra -Wpedantic -Werror" + +CFLAGS="-std=c++14 -Og -g" +CPPFLAGS="-UNDEBUG -Iinclude -I../../server/include" +LDFLAGS="" + +TARGET="agent" + +SOURCES=" + agent.cpp +" + +set -ex + +mkdir -p bin + +$CXX -o bin/$TARGET $SOURCES $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS diff --git a/agents/example_cpp_agent/clean.sh b/agents/example_cpp_agent/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin diff --git a/agents/example_cpp_agent/include/agent.hpp b/agents/example_cpp_agent/include/agent.hpp @@ -1,33 +0,0 @@ -#ifndef AGENT_HPP -#define AGENT_HPP - -#ifdef _XOPEN_SOURCE -#undef _XOPEN_SOURCE -#endif - -#define _XOPEN_SOURCE 700 - -#include "hex/types.h" -#include "hex/proto.h" - -#include <cassert> -#include <cerrno> -#include <cstdint> -#include <cstdio> -#include <cstdlib> -#include <cstring> - -#include <arpa/inet.h> -#include <netdb.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> -#include <array> -#include <iostream> -#include <memory> -#include <random> -#include <vector> - -#endif /* AGENT_HPP */ diff --git a/agents/example_cpp_agent/run.sh b/agents/example_cpp_agent/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -exec $(dirname $0)/agent $@ +exec $(dirname $0)/bin/agent $@ diff --git a/agents/example_cpp_agent/src/agent.cpp b/agents/example_cpp_agent/src/agent.cpp @@ -1,330 +0,0 @@ -#include "agent.hpp" - -enum class Cell { - BLACK = HEX_PLAYER_BLACK, - WHITE = HEX_PLAYER_WHITE, - EMPTY, -}; - -struct Move { - u32 x, y; - - bool operator==(const Move &rhs) { - return this->x == rhs.x && this->y == rhs.y; - } -}; - -void swap(Move &lhs, Move &rhs) { - std::swap(lhs.x, rhs.x); - std::swap(lhs.y, rhs.y); -} - -class Board { - u32 size; - std::vector<Cell> cells; - std::vector<Move> moves; - -public: - template <class URBG> - Board(u32 size, URBG &&rng) : size(size) { - cells.reserve(size * size); - moves.reserve(size * size); - - for (u32 j = 0; j < this->size; j++) { - for (u32 i = 0; i < this->size; i++) { - this->cells.push_back(Cell::EMPTY); - - Move move{i, j}; - this->moves.push_back(move); - } - } - - std::shuffle(this->moves.begin(), this->moves.end(), rng); - } - - bool play(enum hex_player player, u32 x, u32 y) { - Cell &cell = this->cells.at(y * this->size + x); - if (cell != Cell::EMPTY) return false; - - switch (player) { - case HEX_PLAYER_BLACK: - cell = Cell::BLACK; - break; - - case HEX_PLAYER_WHITE: - cell = Cell::WHITE; - break; - } - - Move move{x, y}; - auto it = std::find(this->moves.begin(), this->moves.end(), move); - if (it != std::end(this->moves)) { - ::swap(*it, this->moves.back()); - this->moves.pop_back(); - } - - return true; - } - - template <class URBG> - void swap(URBG &&rng) { - this->moves.clear(); - - for (u32 j = 0; j < this->size; j++) { - for (u32 i = 0; i < this->size; i++) { - Cell &cell = this->cells.at(j * this->size + i); - - switch (cell) { - case Cell::BLACK: cell = Cell::WHITE; break; - case Cell::WHITE: cell = Cell::BLACK; break; - case Cell::EMPTY: - Move move{i, j}; - this->moves.push_back(move); - break; - } - } - } - - std::shuffle(this->moves.begin(), this->moves.end(), rng); - } - - bool next(Move &out) { - if (this->moves.empty()) return false; - - out = this->moves.back(); - this->moves.pop_back(); - - return true; - } -}; - -class Net { - int sockfd; -public: - Net() : sockfd(-1) {} - - bool init(char *host, char *port) { - struct addrinfo hints, *addrinfo, *ptr; - - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - int res; - if ((res = getaddrinfo(host, port, &hints, &addrinfo))) { - std::cerr << "getaddrinfo: " << gai_strerror(res) << std::endl; - return false; - } - - int sockfd; - for (ptr = addrinfo; ptr; ptr = ptr->ai_next) { - sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); - if (sockfd == -1) continue; - if (connect(sockfd, ptr->ai_addr, ptr->ai_addrlen) != -1) break; - close(sockfd); - } - - freeaddrinfo(addrinfo); - - if (!ptr) { - std::cerr << "Failed to connect to " << host << ":" << port << std::endl; - return false; - } - - this->sockfd = sockfd; - - return true; - } - - bool recv_msg(struct hex_msg &out, const std::vector<enum hex_msg_type> &expected) { - u8 buf[HEX_MSG_SZ]; - - size_t nbytes_recv = 0, len = HEX_MSG_SZ; - - do { - ssize_t curr = recv(this->sockfd, buf + nbytes_recv, len - nbytes_recv, 0); - if (curr <= 0) return false; // error or socket shutdown - nbytes_recv += curr; - } while (nbytes_recv < len); - - struct hex_msg msg; - if (!hex_msg_try_deserialise(buf, &msg)) return false; - - if (std::find(expected.begin(), expected.end(), msg.type) != std::end(expected)) { - out = msg; - return true; - } - - return false; - } - - bool send_msg(const struct hex_msg &msg) { - u8 buf[HEX_MSG_SZ]; - if (!hex_msg_try_serialise(&msg, buf)) return false; - - size_t nbytes_sent = 0, len = HEX_MSG_SZ; - - do { - ssize_t curr = send(this->sockfd, buf + nbytes_sent, len - nbytes_sent, 0); - if (curr <= 0) return false; // error or socket shutdown - nbytes_sent += curr; - } while (nbytes_sent < len); - - return true; - } -}; - -enum class State { - START, - RECV, - SEND, - END, -}; - -std::ostream &operator<<(std::ostream &os, const State &self) { - return os << static_cast<std::underlying_type<State>::type>(self); -} - -int -main(int argc, char *argv[]) -{ - std::minstd_rand rand; - rand.seed(getpid()); - - if (argc < 3) { - std::cerr << "Not enough args: " << argv[0] << " <host> <port>" << std::endl; - exit(EXIT_FAILURE); - } - - char *host = argv[1], *port = argv[2]; - - Net net; - if (!net.init(host, port)) { - std::cerr << "Failed to initialise network" << std::endl; - exit(EXIT_FAILURE); - } - - State state = State::START; - std::unique_ptr<Board> board; - - /* initialised to satisfy GCC's linter and sanitiser */ - enum hex_player player = HEX_PLAYER_BLACK; - enum hex_player opponent = HEX_PLAYER_WHITE; - enum hex_player winner = HEX_PLAYER_BLACK; - - // game parameters (unused) - u32 game_secs, thread_limit, mem_limit_mib; - (void) game_secs; (void) thread_limit; (void) mem_limit_mib; - - bool game_over = false, first_round = true; - while (!game_over) { - switch (state) { - case State::START: { - std::vector<enum hex_msg_type> expected_msg_types = {HEX_MSG_START}; - - struct hex_msg msg; - if (!net.recv_msg(msg, expected_msg_types)) { - std::cerr << "Failed to receive message from hex server" << std::endl; - exit(EXIT_FAILURE); - } - - player = static_cast<enum hex_player>(msg.data.start.player); - opponent = hexopponent(player); - game_secs = msg.data.start.game_secs; - thread_limit = msg.data.start.thread_limit; - mem_limit_mib = msg.data.start.mem_limit_mib; - - u32 board_size = msg.data.start.board_size; - - board = std::make_unique<Board>(board_size, rand); - - std::cout << "[" << hexplayerstr(player) << "] Starting game: " - << board_size << "x" << board_size << ", " - << game_secs << "secs" << std::endl; - - switch (player) { - case HEX_PLAYER_BLACK: state = State::SEND; break; - case HEX_PLAYER_WHITE: state = State::RECV; break; - } - } break; - - case State::RECV: { - std::vector<enum hex_msg_type> expected_msg_types = {HEX_MSG_MOVE, HEX_MSG_SWAP, HEX_MSG_END}; - - struct hex_msg msg; - if (!net.recv_msg(msg, expected_msg_types)) { - std::cerr << "Failed to receive message from hex server" << std::endl; - exit(EXIT_FAILURE); - } - - switch (msg.type) { - case HEX_MSG_MOVE: - board->play(opponent, msg.data.move.board_x, msg.data.move.board_y); - - if (first_round && rand() % 2) { - board->swap(rand); - - msg.type = HEX_MSG_SWAP; - if (!net.send_msg(msg)) { - std::cerr << "Failed to send message to hex server" << std::endl; - exit(EXIT_FAILURE); - } - - state = State::RECV; - } else { - state = State::SEND; - } - break; - - case HEX_MSG_SWAP: - board->swap(rand); - state = State::SEND; - break; - - case HEX_MSG_END: - winner = static_cast<enum hex_player>(msg.data.end.winner); - state = State::END; - break; - } - - first_round = false; - } break; - - case State::SEND: { - struct hex_msg msg; - msg.type = HEX_MSG_MOVE; - - Move move; - if (!board->next(move)) { - std::cerr << "Failed to generate next board move" << std::endl; - exit(EXIT_FAILURE); - } - - board->play(player, move.x, move.y); - - msg.data.move.board_x = move.x; - msg.data.move.board_y = move.y; - - if (!net.send_msg(msg)) { - std::cerr << "Failed to send message to hex server" << std::endl; - exit(EXIT_FAILURE); - } - - state = State::RECV; - first_round = false; - } break; - - case State::END: { - std::cout << "[" << hexplayerstr(player) << "] Player " << hexplayerstr(winner) << " has won the game" << std::endl; - game_over = true; - } break; - - default: - std::cerr << "Unknown game state: " << state << std::endl; - exit(EXIT_FAILURE); - break; - } - } - - exit(EXIT_SUCCESS); -} diff --git a/agents/example_java_agent/Makefile b/agents/example_java_agent/Makefile @@ -1,35 +0,0 @@ -.PHONY: all build clean deps - -JAVAC ?= javac -JAR ?= jar - -SRC := src -OBJ := obj - -JLINT := -deprecation -Xlint:unchecked -JCFLAGS := -d $(OBJ) -cp $(SRC) -g $(JLINT) - -TARGET := agent.jar -MANIFEST := MANIFEST.txt -SOURCES := $(SRC)/Agent.java -OBJECTS := $(SOURCES:$(SRC)/%.java=$(OBJ)/%.class) - -all: build - -build: $(TARGET) - -clean: - rm -rf $(TARGET) $(OBJ) - -deps: $(MANIFEST) - -$(TARGET): deps $(OBJECTS) - $(JAR) -cfmv $@ $(MANIFEST) $(wordlist 2,$(words $^),$^) - -$(OBJ)/%.class: $(SRC)/%.java - @mkdir -p $(dir $@) - $(JAVAC) $(JCFLAGS) $< - -$(OBJ): - mkdir -p $@ - diff --git a/agents/example_java_agent/build.sh b/agents/example_java_agent/build.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +JAVAC="${JAVAC:-javac}" +JAR="${JAR:-jar}" + +JLINT="-deprecation -Xlint:unchecked" +JFLAGS="-d obj -cp src -g $JLINT" + +TARGET="agent.jar" + +MANIFEST="MANIFEST.txt" + +SOURCES=" + src/Agent.java +" + +OBJECTS=" + obj/Agent.class +" + +set -ex + +mkdir -p bin obj + +$JAVAC $JFLAGS $SOURCES +$JAR -cfmv bin/$TARGET $MANIFEST $OBJECTS diff --git a/agents/example_java_agent/clean.sh b/agents/example_java_agent/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin obj diff --git a/agents/example_java_agent/run.sh b/agents/example_java_agent/run.sh @@ -12,4 +12,4 @@ JVM_OPTS=" -XX:+DisableAttachMechanism " -exec java ${JVM_OPTS} -jar $(dirname $0)/agent.jar $@ +exec java ${JVM_OPTS} -jar $(dirname $0)/bin/agent.jar $@ diff --git a/agents/example_python3_agent/Makefile b/agents/example_python3_agent/Makefile @@ -1,7 +0,0 @@ -.PHONY: all build clean - -all: build - -build: - -clean: diff --git a/agents/example_python3_agent/build.sh b/agents/example_python3_agent/build.sh @@ -0,0 +1 @@ +#!/bin/sh diff --git a/agents/example_python3_agent/clean.sh b/agents/example_python3_agent/clean.sh @@ -0,0 +1 @@ +#!/bin/sh diff --git a/agents/hexes/Makefile b/agents/hexes/Makefile @@ -1,48 +0,0 @@ -.PHONY: all build clean - -CC ?= cc -TAR ?= tar - -SRC := src -INC := include -DEPINC := ../../include -OBJ := obj - -WARN := -Wall -Wextra -Wpedantic -Werror - -CFLAGS := -std=c17 $(WARN) -Og -g -flto -CPPFLAGS := -I$(INC) -I$(DEPINC) -LDFLAGS := -lm -flto - -TARGET := hexes -SOURCES := $(SRC)/hexes.c \ - $(SRC)/agent.c \ - $(SRC)/agent/mcts.c \ - $(SRC)/agent/random.c \ - $(SRC)/board.c \ - $(SRC)/log.c \ - $(SRC)/network.c \ - $(SRC)/threadpool.c \ - $(SRC)/utils.c - -OBJECTS := $(SOURCES:$(SRC)/%.c=$(OBJ)/%.o) -OBJDEPS := $(OBJECTS:%.o=%.d) - -all: build - -build: $(TARGET) - -clean: - rm -rf $(TARGET) $(OBJ) - -$(TARGET): $(OBJECTS) - $(CC) -o $@ $^ $(LDFLAGS) - -$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) - @mkdir -p $(dir $@) - $(CC) -MMD -o $@ -c $< $(CFLAGS) $(CPPFLAGS) - --include $(OBJDEPS) - -$(OBJ): - mkdir -p $@ diff --git a/agents/hexes/build.sh b/agents/hexes/build.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +CC="${CC:-cc}" + +WARNINGS="-Wall -Wextra -Wpedantic -Werror" + +CFLAGS="-std=c11 -Og -g" +CPPFLAGS="-UNDEBUG -Iinclude -I../../server/include" +LDFLAGS="" + +TARGET="hexes" + +SOURCES=" + src/hexes.c + src/agent.c + src/agent/mcts.c + src/agent/random.c + src/board.c + src/log.c + src/network.c + src/threadpool.c + src/utils.c +" + +set -ex + +mkdir -p bin + +$CC -o bin/$TARGET $SOURCES $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS diff --git a/agents/hexes/clean.sh b/agents/hexes/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin diff --git a/agents/hexes/run.sh b/agents/hexes/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -exec $(dirname $0)/hexes -amcts $HEXES_OPTS $@ +exec $(dirname $0)/bin/hexes -a mcts $HEXES_OPTS $@ diff --git a/build.sh b/build.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -ex + +cd server +./build.sh +cd .. + +for dir in agents/*; do + cd $dir + ./build.sh + cd ../.. +done diff --git a/clean.sh b/clean.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -ex + +cd server +./clean.sh +cd .. + +for dir in agents/*; do + cd $dir + ./clean.sh + cd ../.. +done diff --git a/install.sh b/install.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +for i in $(seq 1 16); do + id "hex-agent-$i" -u >/dev/null 2>&1 || \ + useradd -M -N -e '' "hex-agent-$i" +done diff --git a/server/build.sh b/server/build.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +CC="${CC:-cc}" + +WARNINGS="-Wall -Wextra -Wpedantic -Werror" + +CFLAGS="-std=c11 -Og -g" +CPPFLAGS="-UNDEBUG -Iinclude" +LDFLAGS="" + +TARGET="hex-server" + +SOURCES=" + src/hex.c + src/server.c + src/proto.c + src/board.c + src/utils.c +" + +set -ex + +mkdir -p bin + +$CC -o bin/$TARGET $SOURCES $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS diff --git a/server/clean.sh b/server/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin diff --git a/include/hex.h b/server/include/hex.h diff --git a/include/hex/proto.h b/server/include/hex/proto.h diff --git a/include/hex/types.h b/server/include/hex/types.h diff --git a/src/board.c b/server/src/board.c diff --git a/src/hex.c b/server/src/hex.c diff --git a/src/proto.c b/server/src/proto.c diff --git a/src/server.c b/server/src/server.c diff --git a/src/utils.c b/server/src/utils.c diff --git a/test-server.sh b/test-server.sh @@ -4,4 +4,4 @@ AGENT1="$1" AGENT2="$2" shift 2 -./hex-server -a $AGENT1 -ua 1001 -b $AGENT2 -ub 1002 -t 16 $@ +./server/bin/hex-server -a $AGENT1 -ua 1001 -b $AGENT2 -ub 1002 -t 16 $@ diff --git a/tournament-host.py b/tournament-host.py @@ -11,12 +11,15 @@ import sys import time -HEX_SERVER_PROGRAM = os.path.abspath('hex-server') +HEX_SERVER_PROGRAM = os.path.abspath('./server/bin/hex-server') HEX_AGENT_USERS = [ ent.pw_name for ent in pwd.getpwall() if re.match('hex-agent-\d+$', ent.pw_name) ] +if not HEX_AGENT_USERS: + raise Exception('No hex agent runners found. Please run install.sh first') + HEX_AGENT_UIDS = [ str(pwd.getpwnam(user).pw_uid) for user in HEX_AGENT_USERS ] diff --git a/uninstall.sh b/uninstall.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +for i in $(seq 1 16); do + id "hex-agent-$i" -u >/dev/null 2>&1 && \ + userdel "hex-agent-$i" +done