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