Browse Source

initial commit

master
Trolli Schmittlauch 5 years ago
commit
61aa14398a
15 changed files with 4628 additions and 0 deletions
  1. +86
    -0
      Makefile
  2. +133
    -0
      README
  3. +3273
    -0
      asm.S
  4. +47
    -0
      board.cc
  5. +391
    -0
      board.h
  6. +135
    -0
      mcp.cc
  7. +10
    -0
      mcp.h
  8. +257
    -0
      mcp_shared.cc
  9. +72
    -0
      othello.cc
  10. +69
    -0
      players/example-player.cc
  11. +57
    -0
      players/my-player.cc
  12. +15
    -0
      players/playerlib.cc
  13. +8
    -0
      players/playerlib.h
  14. +62
    -0
      players/random-player.cc
  15. +13
    -0
      players/tags

+ 86
- 0
Makefile View File

@@ -0,0 +1,86 @@
CXX?=clang++
CC?=clang
TARGETS = othello \
example-player \
random-player \
my-player

SRC_mcp = othello.cc mcp.cc
SRC_common = board.cc
ASM_common = asm.S

OBJECTS = $(SRC_mcp:.cc=.o) $(SRC_common:.cc=.o) \
$(ASM_common:.S=.o) \
players/example-player.o \
players/random-player.o \
players/my-player.o

CXXFLAGS += -std=c++11 -I. -g

ifeq ($(CXX),clang++)
CXXFLAGS += -Wno-disabled-macro-expansion
endif

ifeq ($(CXX),g++)
CXXFLAGS += -MMD -Wall
else
CXXFLAGS += -Weverything -Wno-padded -Wno-weak-vtables
endif

all : $(TARGETS)

othello : $(SRC_common:.cc=.o) $(ASM_common:.S=.o)

othello : $(SRC_mcp:.cc=.o)
$(CXX) $^ -o $@ $(LDFLAGS)

example-player: players/example-player.o
$(CXX) $^ -o $@ $(LDFLAGS)

my-player : players/my-player.o
$(CXX) $^ -o $@ $(LDFLAGS)

random-player : players/random-player.o
$(CXX) $^ -o $@ $(LDFLAGS)

PLAYER1 ?= my-player
PLAYER2 ?= random-player

.PHONY : clean cleanall help

demo : $(TARGETS)
MCP_OPTS="-d" PLAYER1=example-player PLAYER2=example-player make execute

debug : $(TARGETS)
MCP_OPTS="-d" make execute

fight : $(TARGETS)
MCP_OPTS="-t 60 -T 61 -m 1024 -M 1024" make execute

execute : $(TARGETS)
./othello $(MCP_OPTS) $(PLAYER1) $(PLAYER2)
help :
@echo "Benutzung: [PLAYER1=<player1>] [PLAYER2=<player2>] make <Ziel>"
@echo ""
@echo "PLAYER1, PLAYER2 -> Dateien mit zu verwendenden Spielern."
@echo " Standardwerte: PLAYER1 = my-player"
@echo " PLAYER2 = random-player"
@echo ""
@echo "Mögliche Ziele:"
@echo ""
@echo "demo - startet ein Spiel mit dem example-player"
@echo "debug - startet ein Spiel im Debug-Modus (keine Zeitbeschränkung)"
@echo "fight - startet ein Turnier-Spiel"


clean :
$(RM) $(OBJECTS)

cleanall : clean
$(RM) $(TARGETS)
$(RM) $(OBJECTS:.o=.d)

-include $(OBJECTS:.o=.d)
-include Makefile.priv


+ 133
- 0
README View File

@@ -0,0 +1,133 @@
Othello
=======

Regeln
------
Wir spielen Othello (nicht Reversi!) nach den in der Wikipedia
zu findenden Regeln: http://de.wikipedia.org/wiki/Othello_(Spiel)


Code
----

Interessant sind:
* players/example-player.cc -> Beispiel-Spieler für
manuelle Zugeingabe
* players/random-player.cc -> Dein Zufalls-Spieler
* players/my-player.cc -> Dein Spieler mit Strategie

Der Vollständigkeit halber:
* Makefile
* mcp.cc, mcp.h, othello.cc -> Master Control Program


Programmieren
-------------

players/my-player.cc enthält ein Code-Grundgerüst. Dieses sorgt dafür, dass
der aktuelle Spielzustand vom MCP geholt wird und am Ende das Ergebnis Deines
Zuges ans MCP zurück geschickt wird. Dazwischen fehlt jetzt noch Dein cleverer
Code, der bestimmt, welches momentan der beste Zug ist.

Der Zustand wird als String der Länge 65 übergeben. Das erste Zeichen gibt an,
welcher Spieler am Zug ist. Die nächsten 64 Zeichen enthalten *zeilenweise*
den Zustand des Spielfeldes. Hierbei gibt es 3 Zeichen:

O -> Spieler 1
X -> Spieler 2
. -> leeres Feld

Deine Aufgabe ist es, einen gültigen Zug zu finden und an das MCP zurück zu
geben. Dies geschieht über zwei Variablen (turn_row und turn_col) die jeweils
die Zeile und Spalte des Ziel-Zuges enthalten sollen. Gültig ist jeder Zug,
der in der aktuellen Situation den Othello-Regeln entspricht.

Ein Sonderfall, der in Othello auftreten kann, ist das Aussetzen. Wenn Du
feststellst, dass Deine Seite momentan keinen gültigen Zug machen kann (auch
wenn bspw. das Feld noch nicht komplett voll ist), dann setze turn_row und
turn_col auf 0, um dem MCP zu signalisieren, dass Du aussetzt. Setzt Du aus
obwohl ein Zug möglich gewesen wäre, so gilt Dein Zug als ungültig.

Bitte füge allen eigenen Code in players/my-player.cc und
players/random-player.cc hinzu.


Kompilieren
-----------
$> make

* Erzeugt werden:
- othello -> das MCP
- example-player -> der Beispiel-Client
- my-player und random-player -> Deine noch zu implementierenden Clients

* Zum Aufräumen gibt es zwei weitere Make-Ziele:
$> make clean -> löscht alle .o-Files
$> make cleanall -> löscht alle generierten Files (.o, Programmdateien, ...)


Spielen
-------

Gespielt wird, indem man das MCP via "othello" startet und ihm als
Parameter zwei beliebige Client-Programme übergibt:

$> ./othello <prog1> <prog2>

Für den Beispiel-Client wäre das also:

$> ./othello example-player example-player

Aus Einfachheitsgründen kann der Spielstart auch über vorbereitete
Make-Ziele vorgenommen werden. Hierzu sind beim Aufruf von make zwei
Umgebungsvariablen zu setzen:

PLAYER1 (default = my-player) -> Spieler 1
PLAYER2 (default = example-player) -> Spieler 2

Der generelle Aufruf sieht so aus:
$> PLAYER1=<prog1> PLAYER2=<prog2> make execute

Es gibt drei vorbereitete Ziele:

1) Start der Beispiel-Clients:
$> make demo
2) Start beliebiger Clients im *Debug-Modus*, welcher nicht sofort
nach einem falschen Zug das Spiel beendet:
$> PLAYER1=<prog1> PLAYER2=<prog2> make debug
3) Start der Clients im Battle Mode, der zur Auswertung benutzt wird:
$> PLAYER1=<prog1> PLAYER2=<prog2> make fight

Zum Testen gibt es außerdem die Möglichkeit, das Spiel mit einer
vordefinierten Spielsituation zu starten. Das hat dann Vorteile, wenn
Euer Spieler nur in bestimmten Randfällen abstürzt. Dazu gibt man beim Starten
von ./othello einen Startzustand mit der Option '-s' an. Dieser Startzustand
sieht genauso aus wie der 65-Zeichen-String, den Euer Spieler sonst vom MCP
bekommt.

Beispiel-Absturz:

$> ./othello player1 player2
[..]
P1's turn. State: XOOOOO...OO.OO.X.OOOXO.X..OOOXOX...OXXX.....XX......XX.......X...
No move from player 1.

CHILD signal: 2 (killed) status 11 (as signal: SIGSEGV)


(Das SIGSEGV deutet an, dass der Spieler hier einen fehlerhaften
Speicherzugriff gemacht hat. Das 'No move from player 1' zeigt an, dass kein
Zug ausgegeben wurde.) Ihr könnt nun im selben Zustand weiter machen, indem
Ihr Othello wie folgt startet:

$> ./othello -s XOOOOO...OO.OO.X.OOOXO.X..OOOXOX...OXXX.....XX......XX.......X... player1 player2


Erfolgreiche Abgabe
-------------------
Für die erfolgreiche Abgabe musst Du 2 Clients programmieren:

1) Zufallsspieler - ein Spieler, der sich alle gültigen Züge sucht und
dann per Zufall einen davon auswählt.
2) Strategie-Spieler - ein Spieler, der eine bestimmte Strategie verfolgt
und dabei den Random Player in mehr als 50% der Fälle besiegt.

+ 3273
- 0
asm.S
File diff suppressed because it is too large
View File


+ 47
- 0
board.cc View File

@@ -0,0 +1,47 @@
#include <board.h>
#include <cstdio>

void OthelloBoard::make_move(unsigned row, unsigned col,
direction dir)
{
if (!position_reachable(row, col, dir))
return;

idx_t row_idx = row; // row loop idx
idx_t col_idx = col; // col loop idx
int inc_row = 0; // step width to increment row idx
int inc_col = 0; // step width to increment col idx

setup_directions(row_idx, col_idx, inc_row, inc_col, dir);

FieldType cur = get(row_idx, col_idx);
while ((cur != _player) && (cur != EMPTY)) {
set(row_idx, col_idx, _player);
col_idx = static_cast<idx_t>((int)col_idx + inc_col);
row_idx = static_cast<idx_t>((int)row_idx + inc_row);
cur = get(row_idx, col_idx);
}
}


bool OthelloBoard::move(unsigned row, unsigned col)
{
_last_player_skipped = false;

if (!validate(row, col)) {
::printf("\033[31;1m -> Invalid move.\033[0m\n");
return false;
}

set(row, col, _player);
make_move(row, col, LEFT);
make_move(row, col, RIGHT);
make_move(row, col, UP);
make_move(row, col, DOWN);
make_move(row, col, UP_LEFT);
make_move(row, col, UP_RIGHT);
make_move(row, col, DOWN_LEFT);
make_move(row, col, DOWN_RIGHT);

return true;
}

+ 391
- 0
board.h View File

@@ -0,0 +1,391 @@
#pragma once

#include <cassert>
#include <cstdio>
#include <cstdlib>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

typedef unsigned int idx_t;

/*
* Potential values for the pieces on the board.
*/
enum FieldType
{
EMPTY,
PLAYER_1,
PLAYER_2,
OUT_OF_BOUNDS
};


/*
* direction flags are used by the algorithms below to validate
* and perform moves.
*/
enum direction {
LEFT,
RIGHT,
UP,
DOWN,
UP_LEFT,
UP_RIGHT,
DOWN_LEFT,
DOWN_RIGHT
};


class OthelloBoard
{
protected:
idx_t _rows; // no. of rows
idx_t _cols; // no. of columns

bool _last_player_skipped; // remember if last player skipped
bool _both_skipped; // termination condition

FieldType *_field; // array storing the types of stones set on the board

FieldType _player; // whose turn is it?


/* make object non-assignable */
OthelloBoard& operator=(OthelloBoard&)
{ return *this; }


private:
/*
* Depending on a given direction, we need to setup up to four
* variables in different types of algorithms. This convenience
* function does so.
*/
void setup_directions(unsigned &row_idx, unsigned &col_idx,
int &inc_row, int &inc_col, direction const dir) const;


/*
* Check if a position is reachable from a given direction.
*
* Reachability means, that from the target position given by
* (row, col) there are only non-empty stones of type != _player until
* we find a stone of type _player.
*/
bool position_reachable(unsigned row, unsigned col,
direction const dir) const;

/*
* Really make a move
*/
void make_move(unsigned row, unsigned col,
direction dir);


/*
* Validate correctness of a move.
*
* \param row, col coordinates to move to
*
* \return true if move is valid
*/
bool validate(unsigned row, unsigned col, bool silent=false) const;

public:

/*
* Get the number of rows.
*/
unsigned rows() const { return _rows; }

/*
* Get the number of columns
*/
unsigned columns() const { return _cols; }


/*
* Construct a default OthelloBoard
*/
OthelloBoard(unsigned r = 8,
unsigned c = 8)
: _rows(r),
_cols(c),
_last_player_skipped(false),
_both_skipped(false),
_field(0),
_player(PLAYER_1)
{
_field = new FieldType[rows()*columns()];
for (unsigned idx = 0; idx < rows() * columns(); ++idx)
_field[idx] = EMPTY;

set(rows()/2, columns()/2, PLAYER_1);
set(rows()/2+1, columns()/2+1, PLAYER_1);

set(rows()/2, columns()/2 + 1, PLAYER_2);
set(rows()/2+1, columns()/2, PLAYER_2);
}


/*
* Copy constructor.
*
* XXX: If you plan to use it, go and implement it!
*/
OthelloBoard(const OthelloBoard& copy);


/* Destructor */
virtual ~OthelloBoard() { delete [] _field; }


/* Get current player type */
FieldType player() const { return _player; }

/* Set current player */
void player(int no) {
switch(no) {
case 0: _player = PLAYER_1; break;
case 1: _player = PLAYER_2; break;
default: assert(false); break;
}
}


/*
* Dump the current board state to a file
*/
bool dump_to_fd(int fd, bool fancy = false) const
{
FILE *file = fdopen(dup(fd), "w");
if (!file) return false;

// print head line
if (fancy)
::fprintf(file, " %c ", type_to_str(_player));
else
::fprintf(file, "%c", type_to_str(_player));

if (fancy) {
for (unsigned c = 0; c < columns(); ++c)
::fprintf(file, "%d ", c+1);
::fprintf(file,"\n");
}

for (unsigned r = 0; r < rows(); ++r) {
if (fancy) ::fprintf(file, "%2d ", r+1);
for (unsigned c = 0; c < columns(); ++c) {
if (fancy)
::fprintf(file, "%s ", type_to_str_fancy(get(r+1, c+1)));
else {
::fprintf(file, "%c", type_to_str(get(r+1,c+1)));
}
}
if (fancy) ::fprintf(file,"\n");
}

fclose(file);

return true;
}

/* Special case: dump to stdout */
bool dump_to_stdio() const { return dump_to_fd(STDOUT_FILENO, true); }

/*
* Get element at location.
*
* Location coordinates are
* row with 0 < row <= rows()
* col with 0 < col <= columns()
*
* Otherwise, returns OUT_OF_BOUNDS field type.
*/
FieldType get(unsigned row, unsigned col) const
{
if ((row <= 0) ||
(col <= 0) ||
(row > rows()) ||
(col > columns()))
return OUT_OF_BOUNDS;

return _field[(row-1)*columns() + col - 1];
}


/*
* Set element at certain location.
*
* Location coordinates are
* row with 0 < row <= rows()
* col with 0 < col <= columns()
*/
void set(unsigned row, unsigned col, FieldType type)
{
assert(row > 0);
assert(row <= rows());
assert(col > 0);
assert(col <= columns());

_field[(row-1)*columns() + col - 1] = type;
}


/*
* Perform a move.
*
* First, validates the move. Then performs all necessary
* field type conversions.
*/
bool move(unsigned row, unsigned col);


/*
* Count the number of fields that contain a stone
* of the given type.
*/
unsigned count_of_type(FieldType t) const
{
unsigned res = 0;
for (unsigned i = 0; i < rows() * columns(); ++i)
if (_field[i] == t) res += 1;

return res;
}


void skipped()
{
if (_last_player_skipped)
_both_skipped = true;
else
_last_player_skipped = true;
}


/*
* Determine whether the game is done.
*/
bool finished() const
{
unsigned c1 = count_of_type(PLAYER_1);
unsigned c2 = count_of_type(PLAYER_2);

return ( _both_skipped ||
(c1 == 0) ||
(c2 == 0) ||
(c1 + c2 >= 64)
);
}


/*
* Transform field type into fancy print version (for stdio).
*/
char const *type_to_str_fancy(FieldType t) const
{
switch (t) {
case EMPTY: return "\033[34;1m.\033[0m";
case PLAYER_1: return "\033[31mO\033[0m";
case PLAYER_2: return "\033[33mX\033[0m";
case OUT_OF_BOUNDS: return "/";
default: __builtin_unreachable();
}
}


/*
* Transform field type into print version.
*/
char type_to_str(FieldType t) const
{
switch (t) {
case EMPTY: return '.';
case PLAYER_1: return 'O';
case PLAYER_2: return 'X';
case OUT_OF_BOUNDS: return '/';
default: __builtin_unreachable();
}
}


/*
* Check, whether the given player can make any move at the moment.
*/
bool can_move() const
{
for (unsigned r = 1; r <= rows(); ++r)
for (unsigned c = 1; c <= columns(); ++c)
if (validate(r, c, true))
return true;

return false;
}


/*
* Invalidate all stones belonging to a player.
*
* The engine uses this as a penalty for making more than 3
* illegal moves in a row.
*/
void invalidate_stones(FieldType t)
{
for (unsigned idx = 0; idx < rows() * columns(); ++idx)
if (_field[idx] == t)
_field[idx] = EMPTY;
}
};

struct OthelloMove
{
idx_t row;
idx_t col;

OthelloMove(idx_t r=0, idx_t c=0)
: row(r), col(c)
{ }
};


/***************************************
* MCP adaptor definitions & functions *
***************************************/

typedef OthelloBoard game_state;
typedef OthelloMove game_move;

/**
* Game state is serialized into a human-readable string.
*/
bool serialize_state(int fd, const game_state *state);

/**
* Game state is parsed from a string written by serialize_state.
*/
bool deserialize_state(int fd, game_state *state);


bool serialize_move (int fd, const game_move *move);
bool deserialize_move(int fd, game_move *move);

/**
* Generate the initial board.
*/
void initialize_state(game_state *state, char const *init);

/**
* Returns true, if this is a final game state.
*/
bool is_final_state(game_state const *state);

/**
* Tries to apply a move to the given state. Returns false, if the
* move was invalid.
*/
bool apply_move(game_state *state,
const game_move *move);

+ 135
- 0
mcp.cc View File

@@ -0,0 +1,135 @@
/* -*- Mode: C -*- */

#include "mcp_shared.cc"

static bool
fork_player(char *executable, struct player *the_player)
{
enum { READ = 0, WRITE = 1};

int pipe_out[2];
int pipe_in[2];

if (pipe(pipe_out) != 0) return false;
if (pipe(pipe_in) != 0) return false;
if ((the_player->pid = fork()) == -1) abort();

if (the_player->pid == 0) {
/* Child */
close(pipe_out[READ]);
close(pipe_in [WRITE]);

dup2(pipe_in [READ], CHILD_IN_FD);
dup2(pipe_out[WRITE], CHILD_OUT_FD);

setrlimit(RLIMIT_CPU, &cpu_limit);
setrlimit(RLIMIT_AS, &mem_limit);

execl(executable, executable, 0);
perror("execl");
_exit(EXEC_FAILED);
} else {
/* Parent */

/* Stop the player until it is his turn. */
if (kill(the_player->pid, SIGSTOP) < 0)
return false;

/* Close useless ends of pipes */
close(pipe_out[WRITE]);
close(pipe_in [READ]);

the_player->pipe_from_player = pipe_out[READ];
the_player->pipe_to_player = pipe_in[WRITE];
}

return true;
}

int
main(int argc, char **argv)
{
char const *init_state = "O...........................OX......XO...........................";
fprintf(stderr, "Master Control Program\n");

int opt;
while ((opt = getopt(argc, argv, "dT:t:m:M:s:")) != -1) {
switch (opt) {
case 'd': debug = true; break;
case 't': cpu_limit.rlim_cur = strtoul(optarg, NULL, 0); break;
case 'T': cpu_limit.rlim_max = strtoul(optarg, NULL, 0); break;
case 'm': mem_limit.rlim_cur = strtoul(optarg, NULL, 0) << 20; break;
case 'M': mem_limit.rlim_max = strtoul(optarg, NULL, 0) << 20; break;
case 's': init_state = optarg; break;
case ':':
case '?':
goto usage;
}
}

if (optind + 2 > argc) {
usage:
print_usage();
exit(1);
}

setup_signal_handlers();

if (!fork_player(argv[optind], &player[0]) ||
!fork_player(argv[optind + 1], &player[1]))
exit_msg(EXEC_FAILED, "Unable to fork players.\n");

game_state state;
initialize_state(&state, init_state);

idx_t player_no = state.player() == PLAYER_1 ? 0 : 1;
while (!is_final_state(&state)) {
game_move move;
state.player(static_cast<int>(player_no));

char timeBuf[64];
getTimeStr(timeBuf, 64);

printf("[%s] P%u's turn.\nState:\n", timeBuf, player_no);
fflush(stdout);
state.dump_to_stdio();
//serialize_state(STDOUT_FILENO, &state);
printf("\n");

if (!player_move(&player[player_no], &state, &move)) {
if (debug) {
printf("No move from player %d.\n", player_no);
continue;
}
exit_msg(exit_reason(CRASH_0 + player_no),
"No move from player %u.\n", player_no);
}

if (!apply_move(&state, &move)) {
printf("Invalid move from player %u.\n", player_no);
exit_msg(exit_reason(INVALID_MOVE_0 + player_no),
"Invalid move from player %u.\n", player_no);
}

printf("P%u moves.\n", player_no);

player_no = 1 - player_no;
}

printf("\nFinal state:\n");
fflush(stdout);
serialize_state(STDOUT_FILENO, &state);
fprintf(stderr, "\n\nEnd of Line.\n");
printf("\nFinal result: %d : %d\n", state.count_of_type(PLAYER_1),
state.count_of_type(PLAYER_2));

/* Kill players */
for (int i = 0; i < 2; i++) {
int status;
kill(player[i].pid, SIGTERM);
kill(player[i].pid, SIGCONT);
waitpid(player[i].pid, &status, 0);
}

return 0;
}

+ 10
- 0
mcp.h View File

@@ -0,0 +1,10 @@
/* -*- Mode: C -*- */

#pragma once

enum {
CHILD_IN_FD = 3,
CHILD_OUT_FD = 4
};

/* EOF */

+ 257
- 0
mcp_shared.cc View File

@@ -0,0 +1,257 @@
#pragma once

#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <time.h>

#include "board.h"
#include "mcp.h"

enum exit_reason {
/* Invalid moves disqualify the player. */
INVALID_MOVE_0 = 1,
INVALID_MOVE_1,
/* Crashes do, too. */
CRASH_0,
CRASH_1,
/* Couldn't execute a player. This is a configuration error. */
EXEC_FAILED
};

/* Time given to players after softlimit has been exhausted. */
//#define CLEANUP_TIME 1 /* seconds */

static struct rlimit cpu_limit = { RLIM_INFINITY, RLIM_INFINITY };
static struct rlimit mem_limit = { RLIM_INFINITY, RLIM_INFINITY };
static bool debug = false;

static struct player {
volatile pid_t pid;
int pipe_from_player;
int pipe_to_player;

volatile bool soft_timeout;
volatile bool hard_timeout;
} player[2];

static struct player * volatile current_player = NULL;

/* Specify seconds = RLIM_INFINITY to disarm timer. */
static void
arm_timer(time_t seconds)
{
struct itimerval t;

t.it_interval.tv_sec = 0;
t.it_interval.tv_usec = 0;
t.it_value.tv_sec = (seconds == (int)RLIM_INFINITY) ? 0 : seconds;
t.it_value.tv_usec = 0;

if (setitimer(ITIMER_REAL, &t, NULL) < 0)
abort();
}

static void
alarm_handler(int signum, siginfo_t *, void *)
{
assert(signum == SIGALRM);

struct player *p = current_player;
assert(p);

if (!p->soft_timeout) {
p->soft_timeout = true;
kill(p->pid, SIGXCPU);
arm_timer(static_cast<time_t>(cpu_limit.rlim_max - cpu_limit.rlim_cur));
} else {
p->hard_timeout = true;
kill(p->pid, SIGKILL);
}
}


static char const *
si_code_str(int si_code)
{
switch(si_code) {
case CLD_EXITED: return "exited";
case CLD_KILLED: return "killed";
case CLD_DUMPED: return "coredumped";
case CLD_TRAPPED: return "trapped";
default: break;
}
return "unknown";
}

static char const *
signal_str(int sig)
{
switch(sig)
{
#define CASE(x) case x: return #x;
CASE(SIGHUP); CASE(SIGINT); CASE(SIGILL); CASE(SIGABRT);
CASE(SIGSEGV); CASE(SIGFPE); CASE(SIGPIPE); CASE(SIGKILL);
CASE(SIGTERM); CASE(SIGALRM); CASE(SIGUSR1); CASE(SIGUSR2);
CASE(SIGBUS); CASE(SIGCHLD);
#undef CASE
}
return "unknown";
}

static void
child_handler(int signum, siginfo_t *si, void *)
{
/* For any SIGCHLD code other than STOP/CONT, we print additional info:
* si_code contains what happened to the child. If the child was terminated
* with a signal (most common case), then si_status additionally contains
* the signal that caused termination.
*/
if ((si->si_code != CLD_STOPPED) &&
(si->si_code != CLD_CONTINUED)) {
// si_status contains the signal that caused the child to terminate
switch(si->si_status) {
case SIGKILL:
case SIGSEGV:
printf("\n\033[31mCHILD signal: %d ", si->si_status);
printf("(%s,%s)\033[0m\n", si_code_str(si->si_code), signal_str(si->si_status));
printf("killing the other one\n");
for (int i = 0; i < 2; i++) {
int status;
kill(player[i].pid, SIGTERM);
kill(player[i].pid, SIGCONT);
waitpid(player[i].pid, &status, 0);
}
printf("MCP exiting\n");
exit(1);
break;
case SIGTERM:
printf("\n\033[32mCHILD terminated successfully\033[0m\n");
break;
default:
printf(" HANDLEME!\n");
break;
}
}
}


static void __attribute__((noreturn))
exit_msg(enum exit_reason r, const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
vfprintf(stderr, msg, ap);
exit(r);
va_end(ap);
}

static bool
player_move(struct player *the_player,
const game_state *state,
game_move *move)
{
bool succ;

the_player->hard_timeout = false;
the_player->soft_timeout = false;

/* Arm timer with softlimit */
current_player = the_player;
arm_timer(static_cast<time_t>(cpu_limit.rlim_cur));

if (kill(the_player->pid, SIGCONT) < 0) return false;

ssize_t written = serialize_state(the_player->pipe_to_player, state);
if (the_player->hard_timeout) goto timeout;
if (written < 0)
return false;

succ = deserialize_move(the_player->pipe_from_player, move);
if (the_player->hard_timeout) goto timeout;
if (!succ) return false;

/* Stop player and disarm timer. */
if (kill(the_player->pid, SIGSTOP) < 0) return false;
arm_timer(static_cast<time_t>(RLIM_INFINITY));
current_player = NULL;

return true;

timeout:
fprintf(stderr, "Player timeout!\n");
return false;
}

static void
print_usage()
{
fprintf(stderr, "Usage: mcp [-s state_string] [-t soft-player-time] [-m soft-player-mem]\n"
" [-T hard-player-time] [-M hard-player-mem]\n"
" [-d]\n"
" player1 player2\n"
" state_string - 65 character string representing an initial field state\n"
" (this is the string the MCP prints before every move.)\n"
" player-time - CPU time per turn in seconds\n"
" player-mem - Memory limit per player in megabytes\n"
" -d - turn on debugging, program won't exit on invalid move\n");
}


static void
setup_signal_handlers()
{
/* Ignore SIGPIPE. Accessing dead pipes now returns EPIPE. */
struct sigaction pact;
pact.sa_handler = SIG_IGN;
sigemptyset(&pact.sa_mask);
pact.sa_restorer = 0;
pact.sa_sigaction = 0;
pact.sa_flags = 0;
if (sigaction(SIGPIPE, &pact, NULL) != 0)
abort();

/* Handle SIGALRM. */
struct sigaction aact;
aact.sa_handler = 0;
sigemptyset(&aact.sa_mask);
aact.sa_restorer = 0;
aact.sa_sigaction = alarm_handler;
aact.sa_flags = SA_SIGINFO | SA_RESTART;
if (sigaction(SIGALRM, &aact, NULL) != 0)
abort();

// Catch SIGCHLD
struct sigaction cact;
cact.sa_handler = 0;
sigemptyset(&cact.sa_mask);
cact.sa_restorer = 0;
cact.sa_sigaction = child_handler;
cact.sa_flags = SA_SIGINFO | SA_RESTART /*| SA_NOCLDSTOP*/;
if (sigaction(SIGCHLD, &cact, NULL) != 0)
abort();
}


static void getTimeStr(char *timebuf, size_t len)
{
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
if (rawtime == (time_t)-1) {
perror("time()");
}
timeinfo = localtime(&rawtime);
if (!timeinfo) {
perror("localtime()");
}
strftime(timebuf, len, "%H:%M:%S", timeinfo);
}

+ 72
- 0
othello.cc View File

@@ -0,0 +1,72 @@
/*
* Othello engine.
*
* This program keeps track of the current state of the board and renders it
* appropriately. It calls the respective players, validates and implements
* their moves. It furthermore keeps track of the players' move time limits.
*/

#include <cstdio>
#include <cassert>
#include <cstdlib>
#include <cstring>

#include <sys/types.h> // mode_t
#include <sys/stat.h> // mkfifo()
#include <unistd.h> // unlink(), fork()
#include <signal.h> // signal()

#include "board.h"
#include "mcp.h"

bool serialize_state(int fd, const game_state *state) { return state->dump_to_fd(fd); }
void initialize_state(game_state *state, char const *init)
{
if (init[0] == 'O')
state->player(0);
else
state->player(1);

for (unsigned r = 0; r < state->rows(); ++r) {
for (unsigned c = 0; c < state->columns(); ++c) {
char field = init[r * state->columns() + c + 1];
FieldType t;
switch(field) {
case '.': t = EMPTY; break;
case 'O': t = PLAYER_1; break;
case 'X': t = PLAYER_2; break;
default: t = OUT_OF_BOUNDS; break;
}
state->set(r+1,c+1,t);
}
}
}
bool is_final_state(game_state const *state) { return state->finished(); }

bool deserialize_move(int fd, game_move *move)
{
char buf[128];
memset(buf,0,128);
ssize_t r = read(fd, buf, 128);
if (r != 3) return false;

int res = sscanf(buf, "%u,%u", &move->row, &move->col);
if (res != 2) return false;

return true;
}


bool apply_move(game_state *state,
const game_move *move)
{
// skip move only if it's really impossible
if ((move->row == 0) &&
(move->col == 0) &&
!state->can_move()) {
state->skipped();
return true;
}

return state->move(move->row, move->col);
}

+ 69
- 0
players/example-player.cc View File

@@ -0,0 +1,69 @@
/* -*- Mode: C -*- */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>

#include <mcp.h>

static int
send_move(int row, int col)
{
FILE *f = fdopen(dup(CHILD_OUT_FD), "w");
fprintf(f, "%u,%u", row, col);
fclose(f);
return 0;
}

static void
read_move_from_user(int *row, int *col)
{
char input[128];

do {
printf("Zug (<Zeile>,<Spalte>): "); fflush(stdout);
if (fgets(input, sizeof(input), stdin) == NULL) abort();
} while((sscanf(input, "%u,%u", row, col) != 2) ||
(*row > 8) || (*col > 8));
}


int main(void)
{
int done = 0;
int turn_row, turn_col;

while (!done) {
/* BEGIN PLAYER-SPECIFIC CODE */

// 1. Read state
char state_buffer[128];
ssize_t bytes = read(CHILD_IN_FD, state_buffer, sizeof(state_buffer));
if (bytes != 65) // invalid number of chars
abort();

// state_buffer enthält jetzt 65 Zeichen ('.' oder 'X' oder 'O'):
// * Das ERSTE Zeichen gibt an, welcher Spieler an der Reihe ist.
// * Die weiteren 64 Zeichen definieren die Belegung des Feldes.
// * Die Belegung wird reihenweise gegeben, d.h. die Zeichen
// 1 bis 8 definieren die erste Zeile des Feldes, Zeichen 9 bis 17
// geben die zweite Zeile usw.
// * X und O stehen hierbei für die jeweiligen Spieler. Leere Felder
// sind durch einen Punkt (.) gekennzeichnet.
// 2. Input move
read_move_from_user(&turn_row, &turn_col);
// 3. Return result
send_move(turn_row, turn_col);

/* END PLAYER-SPECIFIC CODE */

}

return 0;
}

/* EOF */

+ 57
- 0
players/my-player.cc View File

@@ -0,0 +1,57 @@
/* -*- Mode: C -*- */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>

#include <mcp.h>

static int
send_move(int row, int col)
{
FILE *f = fdopen(dup(CHILD_OUT_FD), "w");
fprintf(f, "%u,%u", row, col);
fclose(f);
return 0;
}


int main(void)
{
int done = 0;

while (!done) {
int turn_row = 0;
int turn_col = 0;
/* BEGIN PLAYER-SPECIFIC CODE */

// 1. Read state
char state_buffer[128];
ssize_t bytes = read(CHILD_IN_FD, state_buffer, sizeof(state_buffer));
if (bytes != 65) // invalid number of chars
abort();

// state_buffer enthält jetzt 65 Zeichen ('.' oder 'X' oder 'O'):
// * Das ERSTE Zeichen gibt an, welcher Spieler an der Reihe ist.
// * Die weiteren 64 Zeichen definieren die Belegung des Feldes.
// * Die Belegung wird reihenweise gegeben, d.h. die Zeichen
// 1 bis 8 definieren die erste Zeile des Feldes, Zeichen 9 bis 17
// geben die zweite Zeile usw.
// * X und O stehen hierbei für die jeweiligen Spieler. Leere Felder
// sind durch einen Punkt (.) gekennzeichnet.
// 2. TODO: Strategie hier einfügen. Resultat in turn_row und turn_col speichern.
// 3. Return result
send_move(turn_row, turn_col);

/* END PLAYER-SPECIFIC CODE */

}

return 0;
}

/* EOF */

+ 15
- 0
players/playerlib.cc View File

@@ -0,0 +1,15 @@
#include <stdio.h>
#include <stdlib.h>

#include "playerlib.h"

char getEnemyChar(char ownc)
{
char enemyc;
switch(ownc){
case "O": enemyc="X"; break;
case "X": enemyc="O";
}
return enemyc;
}


+ 8
- 0
players/playerlib.h View File

@@ -0,0 +1,8 @@
#ifndef PLAYERLIB_H
#define PLAYERLIB_H
char enemyc = 0;

//returns the character used for the enemy's stones
char getEnemyChar(char);

#endif

+ 62
- 0
players/random-player.cc View File

@@ -0,0 +1,62 @@
/* -*- Mode: C -*- */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>

#include <mcp.h>

#include "playerlib.h"

static int
send_move(int row, int col)
{
FILE *f = fdopen(dup(CHILD_OUT_FD), "w");
fprintf(f, "%u,%u", row, col);
fclose(f);
return 0;
}


int main(void)
{
int done = 0;

while (!done) {
int turn_row = 0;
int turn_col = 0;
/* BEGIN PLAYER-SPECIFIC CODE */

// 1. Read state
char state_buffer[128];
ssize_t bytes = read(CHILD_IN_FD, state_buffer, sizeof(state_buffer));
if (bytes != 65) // invalid number of chars
abort();

// state_buffer enthält jetzt 65 Zeichen ('.' oder 'X' oder 'O'):
// * Das ERSTE Zeichen gibt an, welcher Spieler an der Reihe ist.
// * Die weiteren 64 Zeichen definieren die Belegung des Feldes.
// * Die Belegung wird reihenweise gegeben, d.h. die Zeichen
// 1 bis 8 definieren die erste Zeile des Feldes, Zeichen 9 bis 17
// geben die zweite Zeile usw.
// * X und O stehen hierbei für die jeweiligen Spieler. Leere Felder
// sind durch einen Punkt (.) gekennzeichnet.
// 2. TODO: Strategie hier einfügen. Resultat in turn_row und turn_col speichern.
if(!enemyc)
enemyc = getEnemyChar(state_buffer[0]);
printf("%s",enemyc);
// 3. Return result
//send_move(turn_row, turn_col);

/* END PLAYER-SPECIFIC CODE */

}

return 0;
}

/* EOF */

+ 13
- 0
players/tags View File

@@ -0,0 +1,13 @@
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
!_TAG_PROGRAM_VERSION 5.8 //
main example-player.cc /^int main(void)$/;" f
main my-player.cc /^int main(void)$/;" f
main random-player.cc /^int main(void)$/;" f
read_move_from_user example-player.cc /^read_move_from_user(int *row, int *col)$/;" f file:
send_move example-player.cc /^send_move(int row, int col)$/;" f file:
send_move my-player.cc /^send_move(int row, int col)$/;" f file:
send_move random-player.cc /^send_move(int row, int col)$/;" f file:

Loading…
Cancel
Save