initial commit
This commit is contained in:
commit
61aa14398a
86
Makefile
Normal file
86
Makefile
Normal 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
README
Normal file
133
README
Normal 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.
|
47
board.cc
Normal file
47
board.cc
Normal 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
board.h
Normal file
391
board.h
Normal 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
mcp.cc
Normal file
135
mcp.cc
Normal 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
mcp.h
Normal file
10
mcp.h
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/* -*- Mode: C -*- */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CHILD_IN_FD = 3,
|
||||||
|
CHILD_OUT_FD = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
/* EOF */
|
257
mcp_shared.cc
Normal file
257
mcp_shared.cc
Normal 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
othello.cc
Normal file
72
othello.cc
Normal 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
players/example-player.cc
Normal file
69
players/example-player.cc
Normal 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
players/my-player.cc
Normal file
57
players/my-player.cc
Normal 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
players/playerlib.cc
Normal file
15
players/playerlib.cc
Normal 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
players/playerlib.h
Normal file
8
players/playerlib.h
Normal 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
players/random-player.cc
Normal file
62
players/random-player.cc
Normal 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
players/tags
Normal file
13
players/tags
Normal 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…
Reference in a new issue