Commit 17980573 authored by Gaurav Kukreja's avatar Gaurav Kukreja

PV-Split Interactive Code

Signed-off-by: 's avatarGaurav Kukreja <mailme.gaurav@gmail.com>
parent bdd2968e
<h1>Aufgabe 5: Parallelisierung Spielsuche</h2>
<h2>Am Beispiel des Brettspiels Abalone<br>
HPC-Praktikum SS07</h2>
<P>
<h3>1. Minimax</h3>
Zur Einarbeitung in die f&uuml;r die sp&auml;tere Parallelisierung
ben&ouml;tigten Codeteile soll zun&auml;chst eine sequentielle
Minimax-Suchestrategie entwickelt werden.
Implementieren sie die Minimax-Suchestrategie f&uuml;r Abalone in einer
Klasse MinimaxStrategy mit OneLevelStrategy als Vorlage und
SearchStrategy::_maxDepth als Tiefe, bis zu der Minimax suchen soll;
Aufrufbeispiele und Details zum Code sind in der README-Datei zum Code
zu finden.
<P>
Welche Leistung (Einheit "Bewertete Stellungen pro Sekunde") erreichen Sie
bei Nutzung "bester" Compileroptionen auf der Altix
bei verschiedenen Maximal-Suchtiefen (2,3,4,5) bezogen auf
<UL>
<LI>die Anfangsposition des Spiels
<LI>die Position in "position-midgame1"
<LI>"position-midgame2" und
<LI>"position-endgame"?
</UL>
Notieren sie sich auch die absolute Anzahl durchgef&uuml;hrter Bewertungen.
Hinweis: "Bewertungen/s" gibt "player -v ..." aus.
<h3>2. Parallelisierung Minimax</h3>
Parallelisieren Sie die Minimaxsuche entweder mit OpenMP
(eventuell mit Workqueuing-Konstrukten; siehe Manual des Intel
Compilers) oder MPI
mit einer geeigneten Parallelisierungsstrategie
(f&uuml;r MPI m&uuml;ssen Sie player.c:main() entsprechend ab&auml;ndern).
<P>
Welchen Speedup
bekommen Sie f&uuml;r die in (1) beschriebenen Parameter, also bezogen
auf der Performance-Wert "Bewertungen/s" f&uuml;r 2,4,6,8 Prozessoren?
Wann kann der erreichbare Speedup von der aktuellen Stellung abh&auml;ngen?
<P>
Hinweis:<UL>
<LI>&Uuml;berlegen Sie, welche Variablen bei der Parallelisierung mit
OpenMP als privat deklariert werden m&uuml;ssen. Dazu geh&ouml;ren
bei C++ auch Objekte, die in jedem Thread privat vorhanden sein
m&uuml;ssen, damit sich die Threads keine Daten &uuml;berschreiben
(z.B. das Objekt f&uuml;r die Spielposition).
</UL>
<h3>3. Einfache Parallelisierung Alpha-Beta </h3>
Der vorgegebene Alpha-Beta-Alogrithmus (ABID) l&auml;sst unn&ouml;tige
Bewertungen aus. Um wieviel Prozent sinkt die absolute Anzahl
bewerteter Stellungen in den in Teilaufgabe (1) notierten F&auml;llen?
<P>
Was w&auml;re eine einfache Parallelisierung des Alpha-Beta-Algorithmus?
Implementieren Sie Ihre einfache Strategie. Der Suchparallelisierungs-Overhead
ist der Prozentsatz an unn&ouml;tig durchgef&uuml;hrten Bewertungen. Wie hoch
ist dieser Overhead in ihrer einfachen Strategie bei 2,3,4,6 Prozessoren und
den in Teilaufgabe (1) vorgegebenen Stellungen?
Welche Probleme hat die Parallelisierung ausserdem? Hinweise kann eine
Speedup-Kurve geben (und bei MPI-Code der Traceanalyzer!).
<h3>4. Effiziente Parallelisierung Alpha-Beta</h3>
Um zu einer besseren Parallelisierung zu gelangen, ist eine gut ausbalancierte
Partitionierung des erwarteten Berechnungsaufwandes wichtig. Man kann davon
ausgehen, dass der Alpha-Beta Suchbaum mit passender Heuristik der minimalen
Form sehr nahe kommt. Wie sieht diese aus?
<I>F&uuml;ndig wird man in "Lu: Parallel
Search of Narrow Game Trees", siehe HPC-Webseite</I>. In der Arbeit sind
auch Parallelisierungsstrategien beschrieben.
Eine wichtige Strategie ist
PVSplit, die in einem minimalen Alpha-Beta-Suchbaum die Kinder der sogenannten
ALL-Knoten in einer Tiefensuche parallel behandelt.
Implementieren Sie PVSplit in OpenMP oder MPI f&uuml;r Abalone.
Geben Sie entsprechend zu Aufgabe 3 Speedupwerte f&uuml;r ihre
Parallelisierung von PVSplit an.
<P>
Hinweise:<UL>
<LI>Bei Nutzung von OpenMP sollten Sie hier Workqueuing-Konstrukte
einsetzen
<LI>Welche Bedingung und Codestelle bezeichnet einen ALL-Knoten in PVSplit?
Genau an diesen Stellen muss parallelisiert werden.
</UL>
### Compiler
### Chosen compiler has to be in path (use "module" to setup environment)
# Use this for GCC (warning: GCC < 4.2.x does not support OpenMP)
#CXX = g++
# Use this for the Intel C++ compiler ("icc" is only the C version)
#CXX = g++
# Use this for MPI
CXX = mpiCC
### Options
# Useful options for GCC
#CXXFLAGS=-O3
#LDFLAGS=
# Useful options for Intel C++
CXXFLAGS=-O3
LDFLAGS=
LIB_OBJS = move.o board.o network.o search.o eval.o
SEARCH_OBJS = $(LIB_OBJS) search-abid-pvsplit.o
#SEARCH_OBJS = $(LIB_OBJS) search-abid.o search-onelevel.o search-minimax.o
#SEARCH_OBJS = $(LIB_OBJS) search-parallel-minimax.o search-parallel-abid.cpp
all: player start referee
player: player.o $(SEARCH_OBJS)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(SEARCH_OBJS)
start: start.o $(LIB_OBJS)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LIB_OBJS)
referee: referee.o $(LIB_OBJS)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LIB_OBJS)
clean:
rm -rf *.o *~ player start referee networktest
networktest: tests/networktest.o network.o
$(CXX) -o networktest tests/networktest.o network.o
board.o: board.h board.cpp search.cpp
move.o: move.h move.cpp
network.o: network.h network.cpp
player.o: player.cpp
search.o: search.cpp board.cpp move.cpp
eval.o: eval.cpp board.cpp
start.o: start.cpp board.cpp move.cpp
referee.o: referee.cpp board.cpp move.cpp
search-onelevel.o: search.h board.h eval.h
search-abid.o: search.h board.h
search-minimax.o: search.h board.h eval.h
#search-parallel-minimax.o: search.h board.h eval.h
Sequentieller Computerspieler fuer Abalone
==========================================
Abalone ist ein Brettspiel der Kategorie "2-Personen-Nullsummen-Spiel
mit vollstaendiger Information." Dabei bedeutet "Nullsummen-Spiel",
dass der Vorteil des einen Spielers der Nachteil des anderen ist, und
"Vollstaendige Information", dass es keine Zufallskomponente gibt.
Die Regeln gibt es unter
http://uk.abalonegames.com/rules/basic_rules/official_rules.html
und eine GUI-Version fuer Linux gibt es bei den KDE3 Spielen
("Kenolaba"). Der "Netwerk-Modus" dieser GUI benutzt denselben
Kommunikationsbus (siehe unten) wie die Programme hier.
Technische Beschreibung
========================
Die Kommunikation zwischen den Spielern basiert auf dem Austausch von
Spielpositionen auf einem Kommunikationsbus, an den sich mehrere
Prozesse anschliessen koennen. Auf dem Bus versandte Spielpositionen
werden an alle angeschlossenen Prozesse weitergeleitet.
Ein Kommunikationsbus wird identifiziert ueber eine Nummer und einen
Rechnernamen, auf dem der Kommunikationsbus existiert (die Implementierung
benutzt fuer den Kommunikationsbus einen TCP-Port auf dem Rechner, der
frei sein muss; Standard-Busnummer ist 23412).
Bei den folgenden Programmen laesst sich der Kommunikationsbus mit
Parameter "-p" angeben. Debuginformationen werden mit "-v" oder "-vv"
(mehr) ausgegeben. Hilfe zu Kommandozeilenoptionen mit "-h".
Teil der Spielposition ist uebrigens die Zugnummer und eine globale Zeit
bis zum erzwungenen Ende der Partie in Sekunden (wichtig fuer den
Turniermodus bei Benutzung eines Schiedsrichters - siehe unten).
Programm "player"
-----------------
Dies ist ein Computerspieler, der eine vorgegebene Farbe spielt, die man
optional als Argument übergibt. Farben sind entweder "X" oder "O" (Standard).
Farbe X spielt z.B. das Kommando "player X". Eine Spielstärke als Zahl kann
in der Kommandozeile als zweites Argument angegeben werden. Wie dieser
Wert genutzt wird, hängt von der im Computerspieler genutzten Strategie
ab; bei Spiel auf Zeit beispielsweise ist eine Spielstärke ohne Bedeutung.
Der Computerspieler schliesst sich einem Kommunikationsbus an
(Standardbus: "localhost:23412"), und wartet auf Spielpositionen.
Bekommt er eine Position, in der er spielen soll, berechnet er den besten
Zug und schickt die resultierende Spielposition an den Bus zurueck.
Programm "start"
----------------
Dieses Programm startet Spiele, beobachtet sie, und beendet sich, wenn
ein Spieler gewonnen hat. Allerdings kann es keinen Einfluss darauf nehmen,
wenn ein Spieler betruegt, d.h. Spielpositionen verschickt, die er gar
nicht erreichen kann, oder bei einem Spiel auf Zeit die verfügbaren
Zeiten falsch abändert. Gibt man einen Dateinamen als Argument an,
startet das Spiel an der in der Datei vorgegebenen Position.
Programm "referee" ("Schiedsrichter")
-------------------------------------
Dies hat dieselbe Funktion wie "start", kann aber als Schiedsrichter ein Spiel
überwachen, indem er das Spiel zwischen 2 Kommunikationsbussen vermittelt.
Dazu muss auf dem einen Bus ein Spieler fuer "O", und auf dem anderen ein
Spieler fuer "X" sitzen.
Der Schiedsrichter verschickt die Startposition (bzw. Position aus einer
gegebenen Datei) an beide Busse und wartet auf neue Positionen, die er
nur dann weitergibt an den anderen Bus, wenn sie sich durch einen
gültigen Zug ergeben. Dabei gibt der Schiedsrichter allein die Zeit vor
(seit Start des Spiels oder wie in der Datei vorgegeben). Der Schiedsrichter
entscheidet auch, wann ein Spieler wegen Zeitueberschreitung vorloren hat.
Aufrufbeispiele
================
Kompilieren mit "make".
Ohne Schiedsrichter:
player O &
player X &
start
Mit Schiedsrichter:
player -p 3000 O &
player -p 4000 X &
referee -p 3000 -p 4000
Die Startreihenfolge ist jeweils egal, da "start" and "referee" die
aktuelle Position an neue Teilnehmer im Kommunikationsbus
verschicken. Damit kann man Teilnehmer jederzeit abschiessen ('kill'en) und
neustarten: Sie klinken sich automatisch wieder ein.
Bei einem Spiel auf Zeit startet der Schiedsrichter,
sobald ein einziger (!) weiterer Teilnehmer existiert.
Beispiel fuer Netwerkspiel mit Tunneln
======================================
Auf Rechner "comp1" soll Spieler O laufen, wird sind auf "mycomp".
Zwischen mycomp und comp1 ist eine Firewall, die nur SSH erlaubt.
Auf mycomp soll Spieler X und ein Beobachter/Starter laufen.
Zum Verständnis ist es wichtig zu wissen, dass ein "Kommunikationsbus"
aus Punkt-zu-Punkt TCP-Verbindungen zwischen allen Teilnehmern eines
Busses bestehen, wobei jeweils der nächstfreie TCP-Port benutzt wird.
Beim Start eines Teilnehmers belegt er einen eingehenden Port (LISTEN),
der der Kanalnummer oder einer nächsthöhrem unbelegten Port entspricht.
Da ein Spieler auf dem entfernten "comp1" laufen soll, starten wir
SSH mit einem lokalen Forward auf den entfernten Rechner. Für lokale
Teilnehmer scheint damit der entfernte Teilnehmer lokal zu existieren.
Da die 2 lokalen Teilnehmer vom entfernten Rechner aus erreichbar
sein müssen, müssen 2 weitere Tunnel vom entfernten Rechner auf den
lokalen Rechner eingerichtet werden ("remote forwards").
Einloggen auf comp1 (z.B. Kommunikationskanal 5000):
ssh -L 5000:localhost:5000 -R 5001:localhost:5001 -R 5002:localhost:5002 comp1
comp1> ./player -p 5000 O
mycomp>./player -p 5000 X &
mycomp>./start -p 5000
Implementierung einer eigenen Suchstrategie
===========================================
Der Code ist in relativ einfachem C++ geschrieben. Für ein zur
Implementierung einer eigenen Suchstratie ausreichendes Verständnisses
des Codes sollte es genügen, sich die in "search-onelevel.cpp" implementierte
Strategie genau anzuschauen. Dieser Code ist dazu ausführlich
dokumentiert, und zeigt, welche Schritte für eine eigene Suchstrategie
vorgenommen werden müssen.
Für eine neue sequentielle Strategie (oder Parallelisierung mit OpenMP)
reicht es aus, diese Datei als Vorlage zu nehmen. Bei Parallelisierung
mit MPI muss main() so abgeändert werden, dass "Rank 0" den bisherigen
Code darin ausführt, und jeder andere MPI-Task in eine zu implementierende
Funktion springt, die auf Anfragen von Rank 0 wartet (Master-Slave).
"search-onelevel.cpp" ist ein Beispiel einer einfachen Suchstrategie
mit einer Vorausschau von einem Zug. Im Gegensatz zu dieser einfachen
Strategie sollte immer eine einstellbare Suchstärke
berücksichtigt werden, die als "_maxDepth" in SearchStrategy
erreichbar ist, und über den Kommandozeilenparameter definiert wird.
Beispiel einer komplexen Suchstrategie (Alpha-Beta mit
Tiefensuche: "Alpha/Beta with Iterative Deepening") ist in
search-abid.cpp zu finden.
Die Auswahl einer Suchstrategie erfolgt mit "player -s <Nummer> ...",
wobei die Zuordnung zwischen der Nummer einer Strategie und ihrem
Namen in der Hilfe angegeben wird, erreichbar über "player -h".
Referee:
* Protocol extensions:
- ...
General:
* Network: Multiple messages on one TCP connection
* Network: Real asynch. breaks (really needed?)
* Gameconsole: Send messages
This diff is collapsed.
/*
* Classes
* - Board: represents a game state
* - EvalScheme: evaluation scheme
*
* (c) 1997-2005, Josef Weidendorfer
*/
#ifndef BOARD_H
#define BOARD_H
#include "move.h"
class SearchStrategy;
class Evaluator;
/**
* Class MoveCounter
*
* Helper class for board characteristics:
* Counter for move types and connectivity
* See Board::countFrom().
*/
class MoveCounter
{
public:
enum InARowType { inARow2 = 0, inARow3, inARow4, inARow5, inARowCount };
MoveCounter();
void init();
int moveCount(int t) { return _moveCount[t]; }
void incType(int t) { _moveCount[t]++; }
int moveSum();
int rowCount(int r) { return _rowCount[r]; }
void incRow(int r) { _rowCount[r]++; }
private:
int _moveCount[Move::typeCount];
int _rowCount[inARowCount];
};
/**
* Board: represents a game state
*
* Includes methods for
* - play/take back moves
* - generate allowed moves
* - calculate rating for position
* - search for best move
*/
class Board
{
friend class Evaluator;
public:
Board();
~Board() {};
/* different states of one field */
enum {
out = 10, free = 0,
color1, color2, color1bright, color2bright
};
enum { AllFields = 121, /* visible + ring of unvisible around */
RealFields = 61, /* number of visible fields */
MvsStored = 100 };
enum { empty=0,
valid1, // valid state with color1 to draw
valid2, // valid state with color2 to draw
timeout1, // time out for color1 -> win for color2
timeout2, // time out for color2 -> win for color1
win1, // color1 won
win2, // color2 won
invalid };
/* fill Board with defined values */
void begin(int startColor); /* start of a game */
void clear(); /* empty board */
/* fields can't be changed ! */
int operator[](int no) const;
int actColor() const
{ return color; }
/* helper in evaluation: calculate move type counts */
void countFrom(int startField, int color, MoveCounter&);
/* Generate list of allowed moves for player with <color>
* Returns a calculated value for actual position */
void generateMoves(MoveList& list);
/* Check if a game position is reachable from the current one.
* If <fuzzy> is false, this check includes times and move number.
* Returns move which was played. Returns move of type Move::none if not.
*/
Move moveToReach(Board*, bool fuzzy);
/** Check if another board has same tokens set */
bool hasSameFields(Board*);
/* Play the given move.
* Played moves can be taken back (<MvsStored> moves are remembered)
* Time to play is adjusted if msecs > 0.
*
* Warning: Only moves that are generated with Board::generateMoves()
* should be passed. If the move cannot be played, an assertion is raised.
*/
void playMove(const Move& m, int msecs = 0);
bool takeBack(); /* if not remembered, do nothing */
int movesStored(); /* return how many moves are remembered */
Move& lastMove()
{ return storedMove[storedLast]; }
void showHist();
/* Evaluator to use */
void setEvaluator(Evaluator* ev) { _ev = ev; }
void setActColor(int c) { color=c; }
void setColor1Count(int c) { color1Count = c; }
void setColor2Count(int c) { color2Count = c; }
void setField(int i, int v) { field[i] = v; }
void setSpyLevel(int);
int getColor1Count() { return color1Count; }
int getColor2Count() { return color2Count; }
bool isValid() { return (color1Count>8 && color2Count>8); }
/* Is this position valid, a winner position or invalid? */
int validState();
/* returns a string for the valid state */
static char* stateDescription(int);
/* Check that color1Count & color2Count is consistent with board */
bool isConsistent();
/* Searching best move */
void setSearchStrategy(SearchStrategy* ss);
void setDepth(int d);
Move& bestMove();
/* next move in prinipal variation */
Move& nextMove();
Move randomMove();
void stopSearch();
void setMoveNo(int n) { _moveNo = n; }
void setMSecsToPlay(int c, int s) { _msecsToPlay[c] = s; }
int moveNo() { return _moveNo; }
int msecsToPlay(int c) { return _msecsToPlay[c]; }
/* Readable ASCII representation */
char* getState();
/* Returns true if new state was set */
bool setState(char*);
void setVerbose(int v) { _verbose = v; }
void updateSpy(bool b) { bUpdateSpy = b; }
/* simple terminal view of position */
void print();
static int fieldDiffOfDir(int d) { return direction[d]; }
private:
void setFieldValues();
/* helper function for generateMoves */
void generateFieldMoves(int, MoveList&);
// random seed
int seed;
int field[AllFields]; /* actual board */
int color1Count, color2Count;
int color; /* actual color */
Move storedMove[MvsStored]; /* stored moves */
int storedFirst, storedLast; /* stored in ring puffer manner */
int _moveNo; /* move number in current game */
int _msecsToPlay[3]; /* time in seconds to play */
bool show, bUpdateSpy;
int spyLevel, spyDepth;
SearchStrategy* _ss;
Evaluator* _ev;
int _verbose;
/* constant arrays */
static int startBoard[AllFields];
static int order[RealFields];
static int direction[8];
public:
/* for fast evaluation */
int* fieldArray() { return field; }
};
inline int Board::operator[](int no) const
{
return (no<12 || no>120) ? out : field[no];
}
#endif
/**
* EvalScheme and Evaluator
*/
#include "eval.h"
// Default Values
static int defaultRingValue[] = { 45, 35, 25, 10, 0 };
static int defaultRingDiff[] = { 0, 10, 10, 8, 5 };
static int defaultStoneValue[]= { 0,-800,-1800,-3000,-4400,-6000 };
static int defaultMoveValue[Move::typeCount] = { 40,30,30, 15,14,13,
5,5,5, 2,2,2, 1 };
static int defaultInARowValue[MoveCounter::inARowCount]= { 2, 5, 4, 3 };
/**
* Constructor: Set Default values
*/
EvalScheme::EvalScheme(char* file)
{
setDefaults();
read(file);
}
void EvalScheme::setDefaults()
{
for(int i=0;i<6;i++)
_stoneValue[i] = defaultStoneValue[i];
for(int i=0;i<Move::typeCount;i++)
_moveValue[i] = defaultMoveValue[i];
for(int i=0;i<MoveCounter::inARowCount;i++)
_inARowValue[i] = defaultInARowValue[i];
for(int i=0;i<5;i++)
_ringValue[i] = defaultRingValue[i];
for(int i=0;i<5;i++)
_ringDiff[i] = defaultRingDiff[i];
}
void EvalScheme::read(char* file)
{
if (file == 0 || *file == 0) return;
// TODO
}
void EvalScheme::save(char* file)
{
// TODO
}
void EvalScheme::setRingValue(int ring, int value)
{
if (ring >=0 && ring <5)
_ringValue[ring] = value;
}
void EvalScheme::setRingDiff(int ring, int value)
{
if (ring >=1 && ring <5)
_ringDiff[ring] = value;
}
void EvalScheme::setStoneValue(int stoneDiff, int value)
{
if (stoneDiff>0 && stoneDiff<6)
_stoneValue[stoneDiff] = value;
}
void EvalScheme::setMoveValue(int type, int value)
{
if (type>=0 && type<Move::typeCount)
_moveValue[type] = value;
}
void EvalScheme::setInARowValue(int stones, int value)
{
if (stones>=0 && stones<MoveCounter::inARowCount)
_inARowValue[stones] = value;
}
/// Evaluator
int Evaluator::fieldValue[61];
Evaluator::Evaluator()
{
_evalScheme = 0;
}
void Evaluator::setEvalScheme(EvalScheme* scheme)
{
if (!scheme)
scheme = new EvalScheme(0);
_evalScheme = scheme;
setFieldValues();
}
void Evaluator::setFieldValues()
{
if (!_evalScheme) return;
int i, j = 0, k = 59;
int ringValue[5], ringDiff[5];
for(i=0;i<5;i++) {
ringDiff[i] = _evalScheme->ringDiff(i);
ringValue[i] = _evalScheme->ringValue(i);
if (ringDiff[i]<1) ringDiff[i]=1;
}
fieldValue[0] = ringValue[0];
for(i=1;i<7;i++)
fieldValue[i] = ringValue[1] + ((j+=k) % ringDiff[1]);
for(i=7;i<19;i++)
fieldValue[i] = ringValue[2] + ((j+=k) % ringDiff[2]);
for(i=19;i<37;i++)
fieldValue[i] = ringValue[3] + ((j+=k) % ringDiff[3]);
for(i=37;i<61;i++)
fieldValue[i] = ringValue[4] + ((j+=k) % ringDiff[4]);
}
void Evaluator::setBoard(Board* b)
{
_board = b;
field = b->fieldArray();
}
/* Calculate a evaluation for actual position
*
* A higher value means a better position for opponent
* NB: This means a higher value for better position of
* 'color before last move'
*/
int Evaluator::calcEvaluation(Board* b)
{
setBoard(b);
int color = b->actColor();
if (!_evalScheme) {
// Use default values if not set
setEvalScheme();
}
MoveCounter cColor, cOpponent;
int f,i,j;
/* different evaluation types */
int fieldValueSum=0, stoneValueSum=0;
int moveValueSum=0, inARowValueSum=0;
int valueSum;
/* First check simple winner condition */
int color1Count = _board->getColor1Count();
int color2Count = _board->getColor2Count();
if (color1Count <9)
valueSum = (color==color1) ? 16000 : -16000;
else if (color2Count <9)
valueSum = (color==color2) ? 16000 : -16000;
else {
/* Calculate fieldValueSum and count move types and connectivity */
for(i=0;i<RealFields;i++) {
j=field[f=Board::order[i]];
if (j == free) continue;
if (j == color) {
b->countFrom( f, j, cColor );
fieldValueSum -= fieldValue[i];
}
else {
b->countFrom( f, j, cOpponent );
fieldValueSum += fieldValue[i];
}
}
/* If color can't do any moves, opponent wins... */
if (cColor.moveSum() == 0)
valueSum = 16000;
else {
for(int t=0;t < Move::typeCount;t++)
moveValueSum += _evalScheme->moveValue(t) *
(cOpponent.moveCount(t) - cColor.moveCount(t));
for(int i=0;i < MoveCounter::inARowCount;i++)
inARowValueSum += _evalScheme->inARowValue(i) *
(cOpponent.rowCount(i) - cColor.rowCount(i));
if (color == color2)
stoneValueSum = _evalScheme->stoneValue(14 - color1Count) -
_evalScheme->stoneValue(14 - color2Count);
else
stoneValueSum = _evalScheme->stoneValue(14 - color2Count) -
_evalScheme->stoneValue(14 - color1Count);
valueSum = fieldValueSum + moveValueSum +
inARowValueSum + stoneValueSum;
}
}
#ifdef MYTRACE
if (spyLevel>2) {
indent(spyDepth);
printf("Eval %d (field %d, move %d, inARow %d, stone %d)\n",
valueSum, fieldValueSum, moveValueSum,
inARowValueSum, stoneValueSum );
}
#endif
return valueSum;
}
void Evaluator::changeEvaluation()
{
int i,tmp;
/* innermost ring */
tmp=fieldValue[1];
for(i=1;i<6;i++)
fieldValue[i] = fieldValue[i+1];
fieldValue[6] = tmp;
tmp=fieldValue[7];
for(i=7;i<18;i++)
fieldValue[i] = fieldValue[i+1];
fieldValue[18] = tmp;
tmp=fieldValue[19];
for(i=19;i<36;i++)
fieldValue[i] = fieldValue[i+1];
fieldValue[36] = tmp;
/* the outermost ring */
tmp=fieldValue[37];
for(i=37;i<60;i++)
fieldValue[i] = fieldValue[i+1];
fieldValue[60] = tmp;
}
/**
* EvalScheme and Evaluator
*
* A board evaluation scheme using a combination of 3 evaluations:
* - Token difference
* - Place
* - Movability / Connectivity
*
* Coefficients used for these evaluations are variable.
*
* The constructor gets a name, and tries to read the coefficients
* from a configuration file, if nothing found, use default values
*/
#ifndef EVAL_H
#define EVAL_H
#include "board.h"
class EvalScheme
{
public:
EvalScheme(char*);
~EvalScheme() {}
void setDefaults();
void read(char* file);
void save(char* file);
void setRingValue(int ring, int value);
void setRingDiff(int ring, int value);
void setStoneValue(int stoneDiff, int value);
void setMoveValue(int type, int value);
void setInARowValue(int stones, int value);
int ringValue(int r) { return (r>=0 && r<5) ? _ringValue[r] : 0; }
int ringDiff(int r) { return (r>0 && r<5) ? _ringDiff[r] : 0; }
int stoneValue(int s) { return (s>0 && s<6) ? _stoneValue[s] : 0; }
int moveValue(int t)
{ return (t>=0 && t<Move::typeCount) ? _moveValue[t] : 0;}
int inARowValue(int s)
{ return (s>=0 && s<MoveCounter::inARowCount) ? _inARowValue[s]:0; }
private:
int _ringValue[5], _ringDiff[5];
int _stoneValue[6], _moveValue[Move::none];
int _inARowValue[MoveCounter::inARowCount];
};
class Evaluator
{
public:
/* different states of one field */
enum {
out = 10, free = 0,
color1, color2, color1bright, color2bright
};
enum { AllFields = 121, /* visible + ring of unvisible around */
RealFields = 61, /* number of visible fields */
MvsStored = 100 };
Evaluator();
/* Evaluation Scheme to use */
void setEvalScheme( EvalScheme* scheme = 0);
EvalScheme* evalScheme() { return _evalScheme; }
void setFieldValues();
int minValue() { return -15000; }
int maxValue() { return 15000; }
/* Calculate a value for actual position
* (greater if better for color1) */
int calcEvaluation(Board*);
/* Evalution is based on values which can be changed
* a little (so computer's moves aren't always the same) */
void changeEvaluation();
void setBoard(Board*);
private:
Board* _board;
EvalScheme* _evalScheme;
int* field;
/* ratings; semi constant - are rotated by changeRating() */
static int fieldValue[Board::RealFields];
};
#endif
/*
* Classes
* - Move: a move on the game board
* - MoveTypeCounter, InARowCounter: evaluation helpers
* - MoveList: stores list of moves allowed from a position
* - Variation: stores move sequences
*
* (c) 1997-2005, Josef Weidendorfer
*/
#include <assert.h>
#include <stdio.h>
#include "move.h"
#include "board.h"
static const char* nameOfDir(int dir)
{
dir = dir % 6;
return
(dir == 1) ? "Right" :
(dir == 2) ? "RightDown" :
(dir == 3) ? "LeftDown" :
(dir == 4) ? "Left" :
(dir == 5) ? "LeftUp" :
(dir == 0) ? "RightUp" : "??";
}
static char* nameOfPos(int p)
{
static char tmp[3];
tmp[0] = (char)('A' + (p-12)/11);
tmp[1] = (char)('1' + (p-12)%11);
tmp[2] = 0;
return tmp;
}
/// Move
char* Move::name() const
{
static char s[30];
int pos;
/* sideway moves... */
if (type == left3 || type == right3) {
int f1, f2, df;
f1 = f2 = field;
df = 2* Board::fieldDiffOfDir(direction);
if (df > 0)
f2 += df;
else
f1 += df;
pos = sprintf(s, "%s-", nameOfPos( f1 ));
const char* dir = (type == left3) ? nameOfDir(direction-1): nameOfDir(direction+1);
sprintf(s+pos, "%s/%s", nameOfPos( f2 ), dir);
}
else if ( type == left2 || type == right2) {
int f1, f2, df;
f1 = f2 = field;
df = Board::fieldDiffOfDir(direction);
if (df > 0)
f2 += df;
else
f1 += df;
pos = sprintf(s, "%s-", nameOfPos( f1 ));
const char* dir = (type == left2) ? nameOfDir(direction-1): nameOfDir(direction+1);
sprintf(s+pos, "%s/%s", nameOfPos( f2 ), dir);
}
else if (type == none) {
return "??";
}
else {
int p = sprintf(s, "%s/%s",
nameOfPos( field ), nameOfDir(direction));
if (type<3) sprintf(s+p,"/Out");
else if (type<6) sprintf(s+p,"/Push");
}
return s;
}
char* Move::typeName() const
{
switch(type) {
case out2: return "Out, pushing 2 opponents with 3";
case out1with3: return "Out, pushing 1 opponent with 3";
case out1with2: return "Out, pushing 1 opponent with 2";
case push2: return "Pushing 2 opponents with 3";
case push1with3: return "Pushing 1 opponent with 3";
case push1with2: return "Pushing 1 opponent with 2";
case move3: return "Moving 3 in a row";
case left3: return "Moving 3 left sideways";
case right3: return "Moving 3 right sideways";
case left2: return "Moving 2 left sideways";
case right2: return "Moving 2 right sideways";
case move2: return "Moving 2 in a row";
case move1: return "Moving 1";
default:
break;
}
return "Invalid type";
}
void Move::print() const
{
printf("%s", name() );
}
/// MoveList
MoveList::MoveList()
{
clear();
}
void MoveList::clear()
{
int i;
for(i=0;i<Move::typeCount;i++)
first[i] = actual[i] = -1;
nextUnused = 0;
actualType = -1;
}
void MoveList::insert(Move m)
{
int t = m.type;
/* valid and possible ? */
if (t <0 || t >= Move::typeCount) return;
//if (nextUnused == MaxMoves) return;
assert( nextUnused < MaxMoves );
/* adjust queue */
if (first[t] == -1) {
first[t] = last[t] = nextUnused;
}
else {
assert( last[t] < nextUnused );
next[last[t]] = nextUnused;
last[t] = nextUnused;
}
next[nextUnused] = -1;
move[nextUnused] = m;
nextUnused++;
}
bool MoveList::isElement(int f)
{
int i;
for(i=0; i<nextUnused; i++)
if (move[i].field == f)
return true;
return false;
}
bool MoveList::isElement(Move &m, int startType, bool del)
{
int i;
for(i=0; i<nextUnused; i++) {
Move& mm = move[i];
if (mm.field != m.field)
continue;
/* if direction is supplied it has to match */
if ((m.direction > 0) && (mm.direction != m.direction))
continue;
/* if type is supplied it has to match */
if ((m.type != Move::none) && (m.type != mm.type))
continue;
if (m.type == mm.type) {
/* exact match; eventually supply direction */
m.direction = mm.direction;
if (del) mm.type = Move::none;
return true;
}
switch(mm.type) {
case Move::left3:
case Move::right3:
if (startType == start3 || startType == all) {
m.type = mm.type;
m.direction = mm.direction;
if (del) mm.type = Move::none;
return true;
}
break;
case Move::left2:
case Move::right2:
if (startType == start2 || startType == all) {
m.type = mm.type;
m.direction = mm.direction;
if (del) mm.type = Move::none;
return true;
}
break;
default:
if (startType == start1 || startType == all) {
/* unexact match: supply type */
m.type = mm.type;
m.direction = mm.direction;
if (del) mm.type = Move::none;
return true;
}
}
}
return false;
}
bool MoveList::getNext(Move& m, int maxType)
{
if (actualType == Move::typeCount) return false;
while(1) {
while(actualType < 0 || actual[actualType] == -1) {
actualType++;
if (actualType == Move::typeCount) return false;
actual[actualType] = first[actualType];
if (actualType > maxType) return false;
}
m = move[actual[actualType]];
actual[actualType] = next[actual[actualType]];
if (m.type != Move::none) break;
}
return true;
}
int MoveList::count(int maxType)
{
int c = 0;
int type = actualType;
int act = 0;
if (type>=0) act = actual[type];
while(1) {
while(type < 0 || act == -1) {
type++;
if (type == Move::typeCount) return c;
act = first[type];
if (type > maxType) return c;
}
c++;
act = next[act];
}
return c;
}
/// Variation
void Variation::clear(int d)
{
int i,j;
for(i=0;i<maxDepth;i++)
for(j=0;j<maxDepth;j++) {
move[i][j].type = Move::none;
}
actMaxDepth = (d<maxDepth) ? d:maxDepth-1;
}
void Variation::update(int d, Move& m)
{
int i;
if (d>actMaxDepth) return;
for(i=d+1;i<=actMaxDepth;i++) {
move[d][i]=move[d+1][i];
move[d+1][i].type = Move::none;
}
move[d][d]=m;
}
/*
* Classes
* - Move: a move on the game board
* - MoveTypeCounter, InARowCounter: evaluation helpers
* - MoveList: stores list of moves allowed from a position
* - Variation: stores move sequences
*
* (c) 1997-2005, Josef Weidendorfer
*/
#ifndef MOVE_H
#define MOVE_H
/**
* Class Move
*
* A move on the game board.
* A move is given by a start position number on the board
* (for board numbering, see board.h), a direction (out of 6),
* and the type of the move. Type includes the number of own
* and opponents tokens taken part in the move, and side-way
* moves.
*/
class Move
{
public:
/* Directions */
enum { Right=1, RightDown, LeftDown, Left, LeftUp, RightUp };
/* Type of move: Moves are searched in this order */
enum MoveType { out2 = 0, out1with3, out1with2, push2,
push1with3, push1with2, move3, left3, right3,
left2, right2, move2, move1, none };
/* Constants to specify move type ranges. For this to work,
* the enum order in MoveType must not be changed!
*/
enum { typeCount = none,
maxOutType = out1with2,
maxPushType = push1with2,
maxMoveType = move1 };
/* Constructor for an invalid move */
Move() { type = none; field = 0; direction = 0; }
/* Move starting at f, direction d, and Type t */
Move(short f, char d, MoveType t)
{ field = f; direction = d, type = t; }
/* Does this move push out an opponents token? */
bool isOutMove() const { return type <= out1with2; }
/* Does this move push opponent tokens? */
bool isPushMove() const { return type <= push1with2; }
/* for communication to outer world */
char* name() const;
char* typeName() const;
/* debugging output */
void print() const;
/* Allow public R/W access... */
short field;
unsigned char direction;
MoveType type;
};
/**
* Class MoveList
*
* Stores a fixed number of moves (for efficince)
* <getNext> returns reference of next move ordered according to type
* <insert> does nothing if there isn't enough free space
*
* Recommend usage (* means 0 or more times):
* [ clear() ; insert() * ; isElement() * ; getNext() * ] *
*/
class MoveList
{
public:
MoveList();
enum { MaxMoves = 150 };
/* for isElement: search for moves starting with 1/2/3 fields */
enum { all , start1, start2, start3 };
void clear();
void insert(Move);
bool isElement(int f);
bool isElement(Move&, int startType, bool del=false);
void insert(short f, char d, Move::MoveType t)
{ insert( Move(f,d,t) ); }
int getLength()
{ return nextUnused; }
int count(int maxType = Move::typeCount);
/**
* Get next move from the list into the Move instance
* given by first arg which is passed by reference.
*
* Move types are sorted, and you can specify the maximal
* type allowed to be returned. Default is to return all moves.
*
* Return false if no more moves (with given type constrain)
*/
bool getNext(Move&, int maxType = Move::none);
private:
Move move[MaxMoves];
int next[MaxMoves];
int first[Move::typeCount];
int last[Move::typeCount];
int actual[Move::typeCount];
int nextUnused, actualType;
};
/**
* Stores best move sequence = principal variation
*
* Implementation uses upper left triangle of a matrix,
* with row 0 holding best sequence found so far from
* real current game position, row 1 holding best sequence
* starting from currently searched depth 1 in game tree,
* row 2 from depth 2 and so on.
*
* Uses:
* - A new best move sequence at search depth d was found.
* copy current move of depth d into [d][0], and the rest from row d+1.
* - Read principal variation on starting a new alpha/beta search.
* This heuristic is assumed to lead to a lot of cutoffs happening.
* - Read finally found best move from [0][0]
* - Allow for computer hints to the opponent by looking at [0][1]
*/
class Variation
{
public:
enum { maxDepth = 10 };
Variation() { clear(1); }
/* Does the best sequence have a move for depth d ? */
bool hasMove(int d)
{ return (d>actMaxDepth) ?
false : (move[0][d].type != Move::none); }
/* Get move at index i from best move chain */
Move& operator[](int i)
{ return (i<0 || i>=maxDepth) ? move[0][0] : move[0][i]; }
/* Get move chain at depth d */
Move* chain(int d)
{ return (d<0 || d>=maxDepth) ? &(move[0][0]) : &(move[d][d]); }
/* Update best move from depth d, starting with move m at this depth */
void update(int d, Move& m);
/* Clear sequence storage for moves from depth d */
void clear(int d);
/* Set maximum supported depth */
void setMaxDepth(int d)
{ actMaxDepth = (d>maxDepth) ? maxDepth-1 : d; }
private:
Move move[maxDepth][maxDepth];
int actMaxDepth;
};
#endif /* _MOVE_H_ */
This diff is collapsed.
/*
* Simple Network Communication via event-driven loop
*
* (C) 2005, Josef Weidendorfer
*
* Install a communication domain which is a listening socket
* on a port and a set of remote connection to this port.
* You can register callbacks for incoming ASCII data on
* communcation domains and broadcast your own data into a domain.
*/
#ifndef NETWORK_H
#define NETWORK_H
#include <sys/types.h>
#include <netinet/in.h>
class NetworkDomain;
class NetworkTimer;
/**
* Event loop for network communication
*/
class NetworkLoop
{
public:
NetworkLoop();
/**
* Install a listening socket on the port given by the NetworkDomain.
* Incoming connections are accepted, and incoming
* data will lead to callbacks specified in the NetworkDomain.
* Returns true if successfull.
*/
bool install(NetworkDomain*);
/**
* Install a oneshot timer object. Install the timer again in
* timeout() for regular calls.
*/
bool install(NetworkTimer*);
/**
* Remove a NetworkDomain. Existing connections will be closed.
*/
void remove(NetworkDomain*);
/**
* Blocks in a select() on network connections and
* calls registered callbacks on incoming data.
* Call exit() to leave the event loop with given exit value.
*/
int run();
/**
* While running inside of a event loop via run(),
* call this function the trigger quitting the loop, i.e.
* returning from run().
*/
void exit(int value = 1);
/**
* Are incoming data pending?
*/
bool pending();
/**
* Process pending data
*/
void processPending();
private:
NetworkDomain* domainList;
NetworkTimer* timerList;
bool exit_loop;
int exit_value;
/* Set of file descriptors used in select.
* This includes listening and data sockets
*/
fd_set readfds;
int max_readfd;
};
/**
* A timer to be used with NetworkLoop.
*
* Install an instance into the loop to get timeout() called
* after <secs> seconds.
*/
class NetworkTimer
{
public:
NetworkTimer(int msecs);
virtual ~NetworkTimer() {}
int msecs() { return _msecs; }
virtual void timeout(NetworkLoop*);
/**
* Internal.
* Helpers for NetworkLoop
*/
void reset();
void set(struct timeval*);
void minLeft(struct timeval*);
bool subLeft(struct timeval*);
NetworkTimer* next;
private:
int _msecs;
struct timeval _left;
};
/**
* An active connection in a communication domain
*/
class Connection {
public:
Connection(NetworkDomain*, Connection*,
const char*, int,
struct sockaddr_in, bool);
~Connection();
void setHost(const char* h);
/**
* Send a string to this connection.
*/
bool sendString(const char* str, int len);
/* Get string for remote end */
char* addr();
/**
* Internal.
* Called from NetworkDomain to send registration string
*/
bool start();
char host[100];
int port;
struct sockaddr_in sin;
bool reachable;
NetworkDomain* domain;
Connection* next;
};
/**
* The Network domainclass can be subclassed to send and to
* receive multicasts from other Network instances in
* other processes/machines.
*/
class NetworkDomain
{
public:
enum { defaultPort = 23412 };
/* install listening TCP socket on port */
NetworkDomain(int port = defaultPort);
virtual ~NetworkDomain();
bool isListening() { return (fd>=0); }
/**
* Returns the file descriptor for the listening socket
*/
int startListening(NetworkLoop*);
/**
* Close all connections and listening socket
*/
void close();
int ID() { return myID; }
int listeningPort() { return myPort; }
int listeningFD() { return fd; }
void addConnection(const char* host, int port);
void broadcast(const char* str);
/* return number of connections */
int count();
/* For list of domains used in a NetworkLoop.
* Can be done this way as a NetworkDomain can only be used
* in one NetworkLoop at a time
*/
NetworkDomain* next;
/**
* Internal use.
* NetworkLoop asks to check for pending connections and data
*/
void check(fd_set*);
/**
* Internal use.
* Overwrite this to react on new connections
*/
virtual void newConnection(Connection*);
protected:
/* overwrite this in your subclass to receive
* strings broadcasted */
virtual void received(char* str);
Connection* prepareConnection(const char* host, int port);
private:
void gotConnection();
/* factory for connections to not get multiply connections to one target */
Connection* getNewConnection(const char* h, struct sockaddr_in sin);
NetworkLoop* loop;
Connection* connectionList;
struct sockaddr_in mySin;
int fd, myID, myPort;
};
#endif
/**
* Computer player
*
* (1) Connects to a game communication channel,
* (2) Waits for a game position requiring to draw a move,
* (3) Does a best move search, and broadcasts the resulting position,
* Jump to (2)
*
* (C) 2005, Josef Weidendorfer
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <mpi.h>
#include "board.h"
#include "search.h"
#include "eval.h"
#include "network.h"
#define pretty_print(name, val) printf("%s = %d: %s: %s: %d\n", name, val, __FILE__,__FUNCTION__,__LINE__);
int thread_rank;
int num_threads;
/* Global, static vars */
NetworkLoop l;
Board b;
Evaluator ev;
/* Which color to play? */
int myColor = Board::color1;
/* Which search strategy to use? */
int strategyNo = 0;
/* Max search depth */
int maxDepth = 0;
/* Maximal number of moves before terminating (negative for infinity) */
int maxMoves = -1;
/* to set verbosity of NetworkLoop implementation */
extern int verbose;
/* remote channel */
char* host = 0; /* not used on default */
int rport = 23412;
/* local channel */
int lport = 13133;
/* change evaluation after move? */
bool changeEval = true;
/**
* MyDomain
*
* Class for communication handling for player:
* - start search for best move if a position is received
* in which this player is about to draw
*/
class MyDomain: public NetworkDomain
{
public:
MyDomain(int p) : NetworkDomain(p) {}
void sendBoard();
protected:
void received(char* str);
};
void MyDomain::sendBoard()
{
static char tmp[500];
sprintf(tmp, "pos %s\n", b.getState());
if (verbose) printf(tmp+4);
broadcast(tmp);
}
void MyDomain::received(char* str)
{
for(int i=1;i<num_threads;i++)
MPI_Send (str, 1024, MPI_CHAR, i, 10,
MPI_COMM_WORLD);
if (strncmp(str, "quit", 4)==0) {
l.exit();
return;
}
if (strncmp(str, "pos ", 4)!=0) return;
b.setState(str+4);
if (verbose) {
printf("\n\n==========================================\n");
printf(str+4);
}
int state = b.validState();
if ((state != Board::valid1) &&
(state != Board::valid2)) {
printf("%s\n", Board::stateDescription(state));
switch(state) {
case Board::timeout1:
case Board::timeout2:
case Board::win1:
case Board::win2:
l.exit();
default:
break;
}
return;
}
if (b.actColor() & myColor) {
struct timeval t1, t2;
gettimeofday(&t1,0);
Move m = b.bestMove();
gettimeofday(&t2,0);
int msecsPassed =
(1000* t2.tv_sec + t2.tv_usec / 1000) -
(1000* t1.tv_sec + t1.tv_usec / 1000);
printf("%s ", (myColor == Board::color1) ? "O":"X");
if (m.type == Move::none) {
printf(" can not draw any move ?! Sorry.\n");
return;
}
printf("draws '%s' (after %d.%03d secs)...\n",
m.name(), msecsPassed/1000, msecsPassed%1000);
b.playMove(m, msecsPassed);
sendBoard();
if (changeEval)
ev.changeEvaluation();
/* stop player at win position */
int state = b.validState();
if ((state != Board::valid1) &&
(state != Board::valid2)) {
printf("%s\n", Board::stateDescription(state));
switch(state) {
case Board::timeout1:
case Board::timeout2:
case Board::win1:
case Board::win2:
l.exit();
default:
break;
}
}
maxMoves--;
if (maxMoves == 0) {
printf("Terminating because given number of moves drawn.\n");
broadcast("quit\n");
l.exit();
}
}
}
/*
* Main program
*/
void printHelp(char* prg, bool printHeader)
{
if (printHeader)
printf("Computer player V 0.1 - (C) 2005 Josef Weidendorfer\n"
"Search for a move on receiving a position in which we are expected to draw.\n\n");
printf("Usage: %s [options] [X|O] [<strength>]\n\n"
" X Play side X\n"
" O Play side O (default)\n"
" <strength> Playing strength, depending on strategy\n"
" A time limit can reduce this\n\n" ,
prg);
printf(" Options:\n"
" -h / --help Print this help text\n"
" -v / -vv Be verbose / more verbose\n"
" -s <strategy> Number of strategy to use for computer (see below)\n"
" -n Do not change evaluation function after own moves\n"
" -<integer> Maximal number of moves before terminating\n"
" -p [host:][port] Connection to broadcast channel\n"
" (default: 23412)\n\n");
printf(" Available search strategies for option '-s':\n");
char** strs = SearchStrategy::strategies();
for(int i = 0; strs[i]; i++)
printf(" %2d : Strategy '%s'%s\n", i, strs[i],
(i==strategyNo) ? " (default)":"");
printf("\n");
exit(1);
}
void parseArgs(int argc, char* argv[])
{
int arg=0;
while(arg+1<argc) {
arg++;
if (strcmp(argv[arg],"-h")==0 ||
strcmp(argv[arg],"--help")==0) printHelp(argv[0], true);
if (strncmp(argv[arg],"-v",2)==0) {
verbose = 1;
while(argv[arg][verbose+1] == 'v') verbose++;
continue;
}
if (strcmp(argv[arg],"-n")==0) {
changeEval = false;
continue;
}
if ((strcmp(argv[arg],"-s")==0) && (arg+1<argc)) {
arg++;
if (argv[arg][0]>='0' && argv[arg][0]<='9')
strategyNo = argv[arg][0] - '0';
continue;
}
if ((argv[arg][0] == '-') &&
(argv[arg][1] >= '0') &&
(argv[arg][1] <= '9')) {
int pos = 2;
maxMoves = argv[arg][1] - '0';
while((argv[arg][pos] >= '0') &&
(argv[arg][pos] <= '9')) {
maxMoves = maxMoves * 10 + argv[arg][pos] - '0';
pos++;
}
continue;
}
if ((strcmp(argv[arg],"-p")==0) && (arg+1<argc)) {
arg++;
if (argv[arg][0]>'0' && argv[arg][0]<='9') {
lport = atoi(argv[arg]);
continue;
}
char* c = strrchr(argv[arg],':');
int p = 0;
if (c != 0) {
*c = 0;
p = atoi(c+1);
}
host = argv[arg];
if (p) rport = p;
continue;
}
if (argv[arg][0] == 'X') {
myColor = Board::color2;
continue;
}
if (argv[arg][0] == 'O') {
myColor = Board::color1;
continue;
}
int strength = atoi(argv[arg]);
if (strength == 0) {
printf("ERROR - Unknown option %s\n", argv[arg]);
printHelp(argv[0], false);
}
maxDepth = strength;
}
}
int main(int argc, char* argv[])
{
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &num_threads);
MPI_Comm_rank(MPI_COMM_WORLD, &thread_rank);
parseArgs(argc, argv);
#if 0
if(thread_rank < 4)
myColor = Board::color1;
else {
myColor = Board::color2;
thread_rank = thread_rank - 4;
}
#endif
SearchStrategy* ss = SearchStrategy::create(strategyNo);
if (verbose)
printf("Using strategy '%s' ...\n", ss->name());
ss->setMaxDepth(maxDepth);
b.setSearchStrategy( ss );
ss->setEvaluator(&ev);
ss->registerCallbacks(new SearchCallbacks(verbose));
if(thread_rank == 0) {
MyDomain d(lport);
if (host) d.addConnection(host, rport);
l.install(&d);
l.run();
}
else
{
while (1)
{
MPI_Status mpi_st;
Slave_Input slave_input;
Slave_Output slave_output;
MPI_Recv (&slave_input, sizeof(Slave_Input), MPI_BYTE, 0, 10, MPI_COMM_WORLD, &mpi_st);
// depth= -1 is a signal for the slave to quit
if (slave_input.depth == -1)
break;
ss->_board = &b;
ss->_board->setState(slave_input.boardstate);
ss->_board->playMove(slave_input.move);
((ABIDStrategy*)ss)->_currentMaxDepth = slave_input.currentMaxDepth;
ss->_sc->_leavesVisited = 0;
ss->_sc->_nodesVisited = 0;
/* check for a win position first */
if (!ss->_board->isValid())
{
slave_output.eval = (14999-(slave_input.depth-1));
}
else
{
if ((slave_input.depth == slave_input.currentMaxDepth) && (slave_input.move.type > Move::maxPushType ))
slave_output.eval = ss->evaluate();
else
slave_output.eval = -((ABIDStrategy*)ss)->alphabeta(slave_input.depth, slave_input.alpha, slave_input.beta);
}
((ABIDStrategy*)ss)->_pv.update(slave_input.depth-1, slave_input.move);
slave_output.pv = ((ABIDStrategy*)ss)->_pv;
slave_output.num_leaves = ss->_sc->_leavesVisited;
slave_output.num_nodes = ss->_sc->_nodesVisited;
MPI_Send(&slave_output, sizeof(Slave_Output), MPI_BYTE, 0, 10, MPI_COMM_WORLD);
}
}
MPI_Finalize();
}
/**
* Computer player
*
* (1) Connects to a game communication channel,
* (2) Waits for a game position requiring to draw a move,
* (3) Does a best move search, and broadcasts the resulting position,
* Jump to (2)
*
* (C) 2005, Josef Weidendorfer
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <mpi.h>
#include "board.h"
#include "search.h"
#include "eval.h"
#include "network.h"
#define pretty_print(name, val) printf("%s = %d: %s: %s: %d\n", name, val, __FILE__,__FUNCTION__,__LINE__);
int thread_rank;
int num_threads;
FILE *file;
/* Global, static vars */
NetworkLoop l;
Board b;
Evaluator ev;
/* Which color to play? */
int myColor = Board::color1;
/* Which search strategy to use? */
int strategyNo = 0;
/* Max search depth */
int maxDepth = 0;
/* Maximal number of moves before terminating (negative for infinity) */
int maxMoves = -1;
/* to set verbosity of NetworkLoop implementation */
extern int verbose;
/* remote channel */
char* host = 0; /* not used on default */
int rport = 23412;
/* local channel */
int lport = 13133;
/* change evaluation after move? */
bool changeEval = true;
char global_tmp[500];
/**
* MyDomain
*
* Class for communication handling for player:
* - start search for best move if a position is received
* in which this player is about to draw
*/
class MyDomain: public NetworkDomain
{
public:
MyDomain(int p) : NetworkDomain(p) {}
void sendBoard();
// protected:
void received(char* str);
};
void MyDomain::sendBoard()
{
static char tmp[500];
sprintf(tmp, "pos %s\n", b.getState());
if (verbose) printf(tmp+4);
broadcast(tmp);
}
void MyDomain::received(char* str)
{
if (strncmp(str, "quit", 4)==0) {
l.exit();
return;
}
if (strncmp(str, "pos ", 4)!=0) return;
b.setState(str+4);
if (verbose) {
printf("\n\n==========================================\n");
printf(str+4);
}
int state = b.validState();
if ((state != Board::valid1) &&
(state != Board::valid2)) {
printf("%s\n", Board::stateDescription(state));
switch(state) {
case Board::timeout1:
case Board::timeout2:
case Board::win1:
case Board::win2:
l.exit();
default:
break;
}
return;
}
if (b.actColor() & myColor) {
struct timeval t1, t2;
gettimeofday(&t1,0);
Move m = b.bestMove();
gettimeofday(&t2,0);
int msecsPassed =
(1000* t2.tv_sec + t2.tv_usec / 1000) -
(1000* t1.tv_sec + t1.tv_usec / 1000);
printf("%s ", (myColor == Board::color1) ? "O":"X");
if (m.type == Move::none) {
printf(" can not draw any move ?! Sorry.\n");
return;
}
printf("draws '%s' (after %d.%03d secs)...\n",
m.name(), msecsPassed/1000, msecsPassed%1000);
b.playMove(m, msecsPassed);
sendBoard();
if (changeEval)
ev.changeEvaluation();
/* stop player at win position */
int state = b.validState();
if ((state != Board::valid1) &&
(state != Board::valid2)) {
printf("%s\n", Board::stateDescription(state));
switch(state) {
case Board::timeout1:
case Board::timeout2:
case Board::win1:
case Board::win2:
l.exit();
default:
break;
}
}
maxMoves--;
if (maxMoves == 0) {
printf("Terminating because given number of moves drawn.\n");
broadcast("quit\n");
l.exit();
}
}
}
/*
* Main program
*/
void printHelp(char* prg, bool printHeader)
{
if (printHeader)
printf("Computer player V 0.1 - (C) 2005 Josef Weidendorfer\n"
"Search for a move on receiving a position in which we are expected to draw.\n\n");
printf("Usage: %s [options] [X|O] [<strength>]\n\n"
" X Play side X\n"
" O Play side O (default)\n"
" <strength> Playing strength, depending on strategy\n"
" A time limit can reduce this\n\n" ,
prg);
printf(" Options:\n"
" -h / --help Print this help text\n"
" -v / -vv Be verbose / more verbose\n"
" -s <strategy> Number of strategy to use for computer (see below)\n"
" -n Do not change evaluation function after own moves\n"
" -<integer> Maximal number of moves before terminating\n"
" -p [host:][port] Connection to broadcast channel\n"
" (default: 23412)\n\n");
printf(" Available search strategies for option '-s':\n");
char** strs = SearchStrategy::strategies();
for(int i = 0; strs[i]; i++)
printf(" %2d : Strategy '%s'%s\n", i, strs[i],
(i==strategyNo) ? " (default)":"");
printf("\n");
exit(1);
}
void parseArgs(int argc, char* argv[])
{
int arg=0;
while(arg+1<argc) {
arg++;
if (strcmp(argv[arg],"-h")==0 ||
strcmp(argv[arg],"--help")==0) printHelp(argv[0], true);
if (strncmp(argv[arg],"-v",2)==0) {
verbose = 1;
while(argv[arg][verbose+1] == 'v') verbose++;
continue;
}
if (strcmp(argv[arg],"-n")==0) {
changeEval = false;
continue;
}
if ((strcmp(argv[arg],"-s")==0) && (arg+1<argc)) {
arg++;
if (argv[arg][0]>='0' && argv[arg][0]<='9')
strategyNo = argv[arg][0] - '0';
continue;
}
if ((argv[arg][0] == '-') &&
(argv[arg][1] >= '0') &&
(argv[arg][1] <= '9')) {
int pos = 2;
maxMoves = argv[arg][1] - '0';
while((argv[arg][pos] >= '0') &&
(argv[arg][pos] <= '9')) {
maxMoves = maxMoves * 10 + argv[arg][pos] - '0';
pos++;
}
continue;
}
if ((strcmp(argv[arg],"-p")==0) && (arg+1<argc)) {
arg++;
if (argv[arg][0]>'0' && argv[arg][0]<='9') {
lport = atoi(argv[arg]);
continue;
}
char* c = strrchr(argv[arg],':');
int p = 0;
if (c != 0) {
*c = 0;
p = atoi(c+1);
}
host = argv[arg];
if (p) rport = p;
continue;
}
if ((strcmp(argv[arg],"-f")==0) && (arg+1<argc)) {
arg++;
file = fopen(argv[arg], "r");
//arg++;
continue;
}
if (argv[arg][0] == 'X') {
myColor = Board::color2;
continue;
}
if (argv[arg][0] == 'O') {
myColor = Board::color1;
continue;
}
int strength = atoi(argv[arg]);
if (strength == 0) {
printf("ERROR - Unknown option %s\n", argv[arg]);
printHelp(argv[0], false);
}
maxDepth = strength;
}
}
extern int avg_kleavesPerSec;
extern int _msecs;
Move m;
int main(int argc, char* argv[])
{
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &num_threads);
MPI_Comm_rank(MPI_COMM_WORLD, &thread_rank);
parseArgs(argc, argv);
if (file){
int len = 0, c;
while( len<499 && (c=fgetc(file)) != EOF)
global_tmp[len++] = (char) c;
global_tmp[len++]=0;
//printf("Gaurav\n");
//printf("%s",global_tmp);
//printf("Gaurav\n");
}
SearchStrategy* ss = SearchStrategy::create(strategyNo);
if (verbose)
printf("Using strategy '%s' ...\n", ss->name());
ss->setMaxDepth(maxDepth);
b.setSearchStrategy( ss );
ss->setEvaluator(&ev);
ss->registerCallbacks(new SearchCallbacks(verbose));
if(thread_rank == 0) {
MyDomain d(lport);
// d.received(global_tmp);
if (host) d.addConnection(host, rport);
l.install(&d);
l.run();
// Send a signal to the slaves, that they should quit
Slave_Input slave_input;
slave_input.depth = -1;
int slave_id;
for (slave_id = 0; slave_id < num_threads-1; slave_id++)
MPI_Send(&slave_input, sizeof(Slave_Input), MPI_BYTE, slave_id + 1, 10, MPI_COMM_WORLD);
//printf("Average leaves visited per sec = %d k/s\n", avg_kleavesPerSec);
}
else
{
while (1)
{
MPI_Status mpi_st;
Slave_Input slave_input;
Slave_Output slave_output;
MPI_Recv (&slave_input, sizeof(Slave_Input), MPI_BYTE, 0, 10, MPI_COMM_WORLD, &mpi_st);
// depth= -1 is a signal for the slave to quit
if (slave_input.depth == -1)
break;
ss->_board = &b;
ss->_board->setState(slave_input.boardstate);
ss->_board->playMove(slave_input.move);
((ABIDStrategy*)ss)->_currentMaxDepth = slave_input.currentMaxDepth;
ss->_sc->_leavesVisited = 0;
ss->_sc->_nodesVisited = 0;
/* check for a win position first */
if (!ss->_board->isValid())
{
slave_output.eval = (14999-(slave_input.depth-1));
}
else
{
if ((slave_input.depth == slave_input.currentMaxDepth) && (slave_input.move.type > Move::maxPushType ))
slave_output.eval = ss->evaluate();
else
slave_output.eval = -((ABIDStrategy*)ss)->alphabeta(slave_input.depth, slave_input.alpha, slave_input.beta);
}
((ABIDStrategy*)ss)->_pv.update(slave_input.depth-1, slave_input.move);
slave_output.pv = ((ABIDStrategy*)ss)->_pv;
slave_output.num_leaves = ss->_sc->_leavesVisited;
slave_output.num_nodes = ss->_sc->_nodesVisited;
MPI_Send(&slave_output, sizeof(Slave_Output), MPI_BYTE, 0, 10, MPI_COMM_WORLD);
}
}
/*
int *avg_list;
avg_list = (int*)malloc(sizeof(int)*num_threads);
MPI_Gather (&avg_kleavesPerSec, 1, MPI_INT,
avg_list, 1, MPI_INT, 0, MPI_COMM_WORLD);
if(thread_rank == 0)
{
int average;
for(int i=0;i<num_threads;i++) {
average += avg_list[i];
}
printf("\n\n\n%d, %d, %d, %f, %s\n", num_threads, maxDepth, average, _msecs/1000.0, ss->_bestMove.name());
}*/
MPI_Finalize();
}
pos #69 O: 14, X: 10
-----------
/ . . . . . \
/ . O . . . . \
/ . . O . . . . \
/ . . X . O . . . \
| . X X O O O O . . |
\ . X . O O O . . /
\ . O O O O . . /
\ . X X X X . /
\ . . X . X /
-----------
X about to move...
=========================================================
Start from this position with
./start position-endgame
Results for "./player <Strength> X"
(Sequential search, Pentium M, 1.6 GHz)
Strength | Move | Evaluated | Time [s] | Rate
| | | -O3 | [Evals/s]
1 I9/RightUp 384
2 I7/LeftUp/Psh 1 796 .042 43 k
3 I7/LeftUp/Psh 17 823 .169 105 k
4 I7/LeftUp/Psh 23 266 .255 91 k
5 I7/LeftUp/Psh 1 520 313 5.715 266 k
6 I7/LeftUp/Psh 11 196 668 39.575 283 k
pos #29 O: 14, X: 14
-----------
/ . . . . . \
/ . O O O O . \
/ . O O . O . . \
/ . . O O O . . . \
| . . O O O . . . . |
\ . X O X X X . . /
\ . X X X X X . /
\ X X X X X . /
\ . . . . . /
-----------
X about to move...
=========================================================
Start from this position with
./start position-midgame1
Results for "./player <Strength> X"
(Sequential search, Pentium M, 1.6 GHz)
Strength | Move | Evaluated | Time [s] | Rate
| | | -O3 | [Evals/s]
1 F3/LeftDown 71
2 F3-G4/Left 5 199 .064 81 k
3 F3-G4/Left 120 718 .649 186 k
4 G4/LeftUp 2 485 542 11.067 225 k
5 G4/RightDown 15 018 100 70.446 213 k
pos #120 O: 14, X: 12
-----------
/ . . . . . \
/ O O O O O . \
/ . O O X O . . \
/ . O X O O . . . \
| . O O X X X . . . |
\ . . O X X X . . /
\ . . X . X . . /
\ . X . X . . /
\ . . . . . /
-----------
O about to move...
=========================================================
Start from this position with
./start position-midgame2
Results for "./player <Strength> O"
(Sequential search, Pentium M, 1.6 GHz)
Strength | Move | Evaluated | Time [s] | Rate
| | | -O3 | [Evals/s]
1 E3-F4/LeftDown 257
2 B1/RightUp 4 358 .062 70 k
3 C2-E2/Left 97 908 .505 194 k
4 B1/LeftDown 1 519 234 5.007 303 k
5 C2-D2/Left 4 411 737 15.265 289 k
pos #1 O: 14, X: 14
-----------
/ O O O O O \
/ O O O O O O \
/ . . O O O . . \
/ . . . . . . . . \
| . . . . . . . . . |
\ . . . . . . . . /
\ . . X X X . . /
\ X X X X X X /
\ X X X X X /
-----------
X about to move...
=========================================================
/**
* Referee
*
* Send a start position into 2 game communication channels,
* observe positions sent in one channel, and if valid, broadcast
* them into other channel.
* For a time limited game, times are updated by referee itself.
*
* (C) 2005, Josef Weidendorfer
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include "board.h"
#include "network.h"
/* Global, static vars */
static NetworkLoop l;
static Board b;
/* Time of last draw */
static struct timeval t1;
class MyDomain;
static MyDomain *d1 = 0, *d2 = 0;
/*
* Do other members in the channels exist at all?
* Only start time if there are players.
*
* Note: We can not distinguish between observers and players,
* so game always start when another member is seen.
*/
static bool d1MemberExists = false;
static bool d2MemberExists = false;
/* time limit in seconds (0: no time limit) */
static int secsToPlay = 0;
static int msecsToPlay[3] = {0,0,0};
/* to set verbosity of NetworkLoop implementation */
extern int verbose;
#define DEFAULT_DOMAIN_PORT 13375
#define DEFAULT_DOMAIN_DIFF 50
/* remote channel */
static char* host[2] = {0,0}; /* not used per default */
static int rport[2] = {DEFAULT_DOMAIN_PORT, DEFAULT_DOMAIN_PORT + DEFAULT_DOMAIN_DIFF};
/* local channel */
static int lport[2] = {DEFAULT_DOMAIN_PORT, DEFAULT_DOMAIN_PORT + DEFAULT_DOMAIN_DIFF};
/* Where to read position to broadcast from? (0: start position) */
static FILE* file = 0;
class MyDomain: public NetworkDomain
{
public:
MyDomain(int p) : NetworkDomain(p) {}
void sendBoard();
protected:
void received(char* str);
void newConnection(Connection*);
};
void MyDomain::sendBoard()
{
static char tmp[500];
sprintf(tmp, "pos %s\n", b.getState());
if (verbose) {
printf(tmp+4);
int state = b.validState();
printf("%s\n", Board::stateDescription(state));
}
broadcast(tmp);
}
void MyDomain::received(char* str)
{
if (strncmp(str, "quit", 4)==0) {
l.exit();
return;
}
if (strncmp(str, "pos ", 4)!=0) return;
if (b.validState() != Board::empty) {
Board newBoard;
newBoard.setState(str+4);
Move m = b.moveToReach(&newBoard, false);
if (m.type == Move::none) {
printf("WARNING: Got a board which is not reachable via a valid move !?\n");
return;
}
else {
struct timeval t2;
gettimeofday(&t2,0);
int msecsPassed =
(1000* t2.tv_sec + t2.tv_usec / 1000) -
(1000* t1.tv_sec + t1.tv_usec / 1000);
t1 = t2;
int* pMSecs;
if (b.actColor() == Board::color1) {
pMSecs = &(msecsToPlay[Board::color1]);
printf("O");
}
else {
pMSecs = &(msecsToPlay[Board::color2]);
printf("X");
}
printf(" draws '%s' (after %d.%03d secs)...\n",
m.name(), msecsPassed/1000, msecsPassed%1000);
if (*pMSecs > msecsPassed)
*pMSecs -= msecsPassed;
else
*pMSecs = 0;
}
}
b.setState(str+4);
/* force our objective view regarding time */
b.setMSecsToPlay(Board::color1, msecsToPlay[Board::color1] );
b.setMSecsToPlay(Board::color2, msecsToPlay[Board::color2] );
printf(b.getState());
int state = b.validState();
printf("%s\n", Board::stateDescription(state));
switch(state) {
case Board::timeout1:
case Board::timeout2:
case Board::win1:
case Board::win2:
l.exit();
default:
break;
}
/* send to other domain */
if (d1 == this) if (d2) d2->sendBoard();
if (d2 == this) if (d1) d1->sendBoard();
}
void MyDomain::newConnection(Connection* c)
{
NetworkDomain::newConnection(c);
static char tmp[500];
int len = sprintf(tmp, "pos %s\n", b.getState());
c->sendString(tmp, len);
/* adjust time if this is first connection for the channel */
if (!d1MemberExists && (this == d1)) {
d1MemberExists = true;
gettimeofday(&t1,0);
}
if (!d2MemberExists && (this == d2)) {
d2MemberExists = true;
gettimeofday(&t1,0);
}
}
static void printHelp(char* prg, bool printHeader)
{
if (printHeader)
printf("Referee V 0.1 - (C) 2005 Josef Weidendorfer\n"
"Broadcast a game position into 2 domains, observe moves played in one domain,\n"
"and if valid, broadcast to other domain. Stops playing times itself.\n\n");
printf("Usage: %s [options] [<file>|-]\n\n"
" <file> File containing game position (default: start position)\n"
" - Position is read from standard input\n\n",
prg);
printf(" Options:\n"
" -h / --help Print this help text\n"
" -v / -vv Be verbose / more verbose\n"
" -t <timeToPlay> Start in tournament modus (limited time)\n"
" -p [host:][port] Connection to first (second) broadcast channel\n"
" (default: %d / %d)\n\n",
DEFAULT_DOMAIN_PORT, DEFAULT_DOMAIN_PORT + DEFAULT_DOMAIN_DIFF);
exit(1);
}
static void parseArgs(int argc, char* argv[])
{
int domainsSet = 0;
int arg=0;
while(arg+1<argc) {
arg++;
if (argv[arg][0] == '-') {
if (strcmp(argv[arg],"-h")==0 ||
strcmp(argv[arg],"--help")==0) printHelp(argv[0], true);
if (strcmp(argv[arg],"-v")==0) {
verbose = 1;
continue;
}
if (strcmp(argv[arg],"-vv")==0) {
verbose = 2;
continue;
}
if ((strcmp(argv[arg],"-t")==0) && (arg+1<argc)) {
arg++;
secsToPlay = atoi(argv[arg]);
if (secsToPlay == 0) {
printf("%s: WARNING - Ignoring tournament; %d secs to play\n",
argv[0], secsToPlay);
}
continue;
}
if ((strcmp(argv[arg],"-p")==0) && (arg+1<argc)) {
arg++;
if (domainsSet>1) {
printf("%s: WARNING - Domain specification %s ignored.\n",
argv[0], argv[arg]);
continue;
}
if (argv[arg][0]>'0' && argv[arg][0]<='9') {
lport[domainsSet] = atoi(argv[arg]);
domainsSet++;
continue;
}
char* c = strrchr(argv[arg],':');
int p = 0;
if (c != 0) {
*c = 0;
p = atoi(c+1);
}
host[domainsSet] = argv[arg];
if (p) rport[domainsSet] = p;
domainsSet++;
continue;
}
if (strcmp(argv[arg],"-")==0) {
file = stdin;
continue;
}
printf("%s: ERROR - Unknown option %s\n", argv[0], argv[arg]);
printHelp(argv[0], false);
}
file = fopen(argv[arg], "r");
if (!file) {
printf("%s: ERROR - Can not open '%s' for reading start position\n",
argv[0], argv[arg]);
printHelp(argv[0], false);
}
break;
}
if (lport[0] == lport[1]) {
lport[1] = lport[0] + DEFAULT_DOMAIN_DIFF;
printf("Local port for domain 2 set to %d\n", lport[1]);
}
}
int main(int argc, char* argv[])
{
parseArgs(argc, argv);
b.setVerbose(verbose);
if (file) {
char tmp[500];
int len = 0, c;
while( len<499 && (c=fgetc(file)) != EOF)
tmp[len++] = (char) c;
tmp[len++]=0;
if (!b.setState(tmp)) {
printf("%s: WARNING - Can not parse given position; using start position\n", argv[0]);
b.begin(Board::color1);
}
}
else
b.begin(Board::color1);
if (secsToPlay >= 0) {
msecsToPlay[Board::color1] = 1000 * secsToPlay;
msecsToPlay[Board::color2] = 1000 * secsToPlay;
}
else {
msecsToPlay[Board::color1] = b.msecsToPlay(Board::color1);
msecsToPlay[Board::color2] = b.msecsToPlay(Board::color2);
}
b.setMSecsToPlay(Board::color1, msecsToPlay[Board::color1] );
b.setMSecsToPlay(Board::color2, msecsToPlay[Board::color2] );
/*
* Register domains at NetworkLoop. Existing members will
* get sent the board via MyDomain::newConnection
*/
d1 = new MyDomain(lport[0]);
if (host[0]) d1->addConnection(host[0], rport[0]);
d2 = new MyDomain(lport[1]);
if (host[1]) d2->addConnection(host[1], rport[1]);
l.install(d1);
l.install(d2);
d1MemberExists = d1->count() >0;
d2MemberExists = d2->count() >0;
gettimeofday(&t1,0);
return l.run();
}
/**
* A real world, sequential strategy:
* Alpha/Beta with Iterative Deepening (ABID)
*
* (c) 2005, Josef Weidendorfer
*/
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <string.h>
#include "search.h"
#include "board.h"
extern int thread_rank;
extern int num_threads;
/**
* Entry point for search
*
* Does iterative deepening and alpha/beta width handling, and
* calls alpha/beta search
*/
void ABIDStrategy::searchBestMove()
{
int alpha = -15000, beta = 15000;
int nalpha, nbeta, currentValue = 0;
_pv.clear(_maxDepth);
_currentBestMove.type = Move::none;
_currentMaxDepth=1;
/* iterative deepening loop */
do {
/* searches on same level with different alpha/beta windows */
while(1) {
nalpha = alpha, nbeta = beta;
_inPV = (_pv[0].type != Move::none);
if (_sc && _sc->verbose()) {
char tmp[100];
sprintf(tmp, "Alpha/Beta [%d;%d] with max depth %d", alpha, beta, _currentMaxDepth);
_sc->substart(tmp);
}
currentValue = pv_split(alpha, beta);
printf("Subsearch finished with currentValue = %d\n", currentValue);
/* stop searching if a win position is found */
if (currentValue > 14900 || currentValue < -14900)
_stopSearch = true;
/* Don't break out if we haven't found a move */
if (_currentBestMove.type == Move::none)
_stopSearch = false;
if (_stopSearch) break;
/* if result is outside of current alpha/beta window,
* the search has to be rerun with widened alpha/beta
*/
if (currentValue <= nalpha) {
alpha = -15000;
if (beta<15000) beta = currentValue+1;
continue;
}
if (currentValue >= nbeta) {
if (alpha > -15000) alpha = currentValue-1;
beta=15000;
continue;
}
break;
}
/* Window in both directions cause of deepening */
alpha = currentValue - 200, beta = currentValue + 200;
if (_stopSearch) break;
_currentMaxDepth++;
}
while(_currentMaxDepth <= _maxDepth);
_bestMove = _currentBestMove;
}
int ABIDStrategy::pv_split(int alpha0, int beta0)
{
bool cutoff;
int depth;
int value;
int currentValue = -15000;
int slave_id;
int num_slaves;
int pending_jobs;
Slave_Input slave_input;
Slave_Output *slave_output;
MPI_Request *rcv_rq;
MoveList list;
Move m, *movechain;
int *alpha, *beta;
rcv_rq = (MPI_Request *) malloc (num_threads * sizeof(MPI_Request));
slave_output = (Slave_Output *) malloc (num_threads * sizeof(Slave_Output));
movechain = (Move*) malloc ((_currentMaxDepth+1)* sizeof (Move));
alpha = (int*) malloc((_currentMaxDepth+2)*sizeof(int));
beta = (int*) malloc((_currentMaxDepth+2)*sizeof(int));
alpha[0] = alpha0;
beta[0] = beta0;
_currentBestMove.type = Move::none;
// Play moves from the pv until you reach the lowest level
// If pv-moves are not possible use random moves
// Store the sequence of moves in movechain
depth = 0;
while (depth < (_currentMaxDepth-1))
{
list.clear();
_board->generateMoves(list);
m = _pv[depth];
// check if pv-move is possible
if ((m.type != Move::none) && (!list.isElement(m, 0, false)))
m.type = Move::none;
// get random move if pv-move is not possible
if (m.type == Move::none)
list.getNext(m, Move::none);
_board->playMove(m);
movechain[depth] = m;
alpha[depth+1] = -beta[depth];
beta[depth+1] = -alpha[depth];
depth++;
}
// Start at the second lowest level and move back up the gametree
// Devide the work at each level
depth = _currentMaxDepth-1;
while (depth >= 0)
{
slave_id = 0;
list.clear();
_board->generateMoves(list);
// delete the move we already checked from the list
if (depth < _currentMaxDepth-1)
list.isElement(movechain[depth], 0, true);
strcpy(slave_input.boardstate,_board->getState());
slave_input.alpha = -beta[depth];
slave_input.beta = -alpha[depth];
slave_input.depth = depth+1;
slave_input.currentMaxDepth = _currentMaxDepth;
printf("Thread 0 testing %d moves at depth = %d\n",list.getLength(), depth);
while ( list.getNext(m, Move::none) )
{
slave_input.move = m;
MPI_Send(&slave_input, sizeof(Slave_Input), MPI_BYTE, slave_id+1, 10, MPI_COMM_WORLD);
MPI_Irecv(&slave_output[slave_id], sizeof(Slave_Output), MPI_BYTE, slave_id+1,
10, MPI_COMM_WORLD, &rcv_rq[slave_id]);
slave_id++;
if (slave_id >= (num_threads-1))
break;
}
num_slaves = slave_id;
pending_jobs = num_slaves;
cutoff = false;
while (pending_jobs > 0)
{
MPI_Waitany(num_slaves, rcv_rq, &slave_id, MPI_STATUS_IGNORE);
_sc->_leavesVisited += slave_output[slave_id].num_leaves;
_sc->_nodesVisited += slave_output[slave_id].num_nodes;
value = slave_output[slave_id].eval;
if (value > currentValue)
{
currentValue = value;
_pv = slave_output[slave_id].pv;
if (_sc)
_sc->foundBestMove(depth, _pv[depth], currentValue);
if (currentValue > alpha[depth])
{
alpha[depth] = currentValue;
slave_input.beta = -alpha[depth];
}
/* alpha/beta cut off or win position ... */
if (currentValue>14900 || currentValue >= beta[depth])
cutoff = true;
}
if ((list.getNext(m, Move::none)) && (cutoff == false))
{
slave_input.move = m;
MPI_Send(&slave_input, sizeof(Slave_Input), MPI_BYTE, slave_id+1, 10, MPI_COMM_WORLD);
MPI_Irecv(&slave_output[slave_id], sizeof(Slave_Output), MPI_BYTE, slave_id+1,
10, MPI_COMM_WORLD, &rcv_rq[slave_id]);
}
else
pending_jobs--;
}
if (depth > 0)
{
_board->takeBack();
_pv.update(depth-1, movechain[depth-1]);
currentValue = -currentValue;
if (currentValue > alpha[depth -1])
alpha[depth-1] = currentValue;
}
if (depth == 0)
_currentBestMove = _pv[0];
if (_sc)
_sc->finishedNode(depth, _pv.chain(depth));
depth--;
}
free(slave_output);
free(rcv_rq);
free(movechain);
free(alpha);
free(beta);
return currentValue;
}
/*
* Alpha/Beta search
*
* - first, start with principal variation
* - depending on depth, we only do depth search for some move types
*/
int ABIDStrategy::alphabeta(int depth, int alpha, int beta)
{
int currentValue = -14999+depth, value;
Move m;
MoveList list;
bool depthPhase, doDepthSearch;
/* We make a depth search for the following move types... */
int maxType = (depth < _currentMaxDepth-1) ? Move::maxMoveType :
(depth < _currentMaxDepth) ? Move::maxPushType :
Move::maxOutType;
_board->generateMoves(list);
if (_sc && _sc->verbose()) {
char tmp[100];
sprintf(tmp, "Alpha/Beta [%d;%d], %d moves (%d depth)", alpha, beta,
list.count(Move::none), list.count(maxType));
_sc->startedNode(depth, tmp);
}
// don't use moves from the principal variation
m.type = Move::none;
// first, play all moves with depth search
depthPhase = true;
while (1) {
// get next move
if (m.type == Move::none) {
if (depthPhase)
depthPhase = list.getNext(m, maxType);
if (!depthPhase)
if (!list.getNext(m, Move::none)) break;
}
// we could start with a non-depth move from principal variation
doDepthSearch = depthPhase && (m.type <= maxType);
_board->playMove(m);
/* check for a win position first */
if (!_board->isValid()) {
/* Shorter path to win position is better */
value = 14999-depth;
}
else {
if (doDepthSearch) {
/* opponent searches for its maximum; but we want the
* minimum: so change sign (for alpha/beta window too!)
*/
value = -alphabeta(depth+1, -beta, -alpha);
}
else {
value = evaluate();
}
}
_board->takeBack();
/* best move so far? */
if (value > currentValue) {
currentValue = value;
_pv.update(depth, m);
/* alpha/beta cut off or win position ... */
if (currentValue>14900 || currentValue >= beta) {
if (_sc) _sc->finishedNode(depth, _pv.chain(depth));
return currentValue;
}
/* maximize alpha */
if (currentValue > alpha) alpha = currentValue;
}
if (_stopSearch) break; // depthPhase=false;
m.type = Move::none;
}
if (_sc) _sc->finishedNode(depth, _pv.chain(depth));
return currentValue;
}
// register ourselve
ABIDStrategy abidStrategy;
This diff is collapsed.
/*
* Very simple example strategy:
* Search all possible positions reachable via one move,
* and return the move leading to best position
*
* (c) 2006, Josef Weidendorfer
*/
#include <stdio.h>
#include "search.h"
#include "board.h"
#include "eval.h"
#define pretty_print(name, val) printf("%s = %d: %s: %s: %d\n", name, val, __FILE__,__FUNCTION__,__LINE__);
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
/**
* To create your own search strategy:
* - copy this file into another one,
* - change the class name one the name given in constructor,
* - adjust clone() to return an instance of your class
* - adjust last line of this file to create a global instance
* of your class
* - adjust the Makefile to include your class in SEARCH_OBJS
* - implement searchBestMove()
*
* Advises for implementation of searchBestMove():
* - call foundBestMove() when finding a best move since search start
* - call finishedNode() when finishing evaluation of a tree node
* - Use _maxDepth for strength level (maximal level searched in tree)
*/
class MinimaxStrategy: public SearchStrategy
{
public:
// Defines the name of the strategy
MinimaxStrategy(): SearchStrategy("MINIMAX") {}
// Factory method: just return a new instance of this class
SearchStrategy* clone() { return new MinimaxStrategy(); }
private:
/**
* Implementation of the strategy.
*/
void searchBestMove();
int minimax();
};
int current_depth=0;
#define MAX_DEPTH 3
int MinimaxStrategy::minimax()
{
Move m;
MoveList list;
// int maxEval, minEval;
int bestEval;
int eval;
int sign;
if(current_depth == MAX_DEPTH)
return evaluate();
bestEval = -17000;
// maxEval = -17000;
// minEval = 17000;
generateMoves(list);
if(evaluate() == 16000)
{
if(current_depth == 0)
finishedNode(0,0);
pretty_print("current_depth", current_depth);
return ((current_depth % 2) ==0) ? -16000 : 16000;
}
if((MAX_DEPTH-current_depth)%2 == 1)
sign = 1;
else
sign = -1;
while(list.getNext(m))
{
if(current_depth < MAX_DEPTH)
{
current_depth++;
playMove(m);
eval=minimax();
takeBack();
current_depth--;
}
if(sign*eval > bestEval)
{
bestEval = sign*eval;
if(unlikely(current_depth == 0)) {
pretty_print("Eval", bestEval);
foundBestMove(0, m, eval);
}
}
#if 0
if((MAX_DEPTH - current_depth +1) % 2 == 0)
{
if(eval > maxEval)
{
maxEval=eval;
if(current_depth == 0) {
pretty_print("Eval", eval);
foundBestMove(0, m, eval);
}
}
}
else
{
if(eval < minEval)
{
minEval=eval;
if(current_depth == 0) {
pretty_print("Eval2", eval);
foundBestMove(0, m, eval);
}
}
}
#endif
}
bestEval = sign*bestEval;
if(current_depth == 0)
finishedNode(0,0);
#if 0
if((MAX_DEPTH - current_depth +1) % 2 == 0)
return maxEval;
else
return minEval;
#endif
return bestEval;
}
void MinimaxStrategy::searchBestMove()
{
// KUKU : Here we have to implement the minimax strategy
// Minimax strategy tries to minimize the maximum possible outcome of opponent.
// At each turn, we check for each move the max positive outcome for opponent.
// We choose the move for which the max is least.
// To check this, we look at more than one levels in the Game Tree.
// we try to maximize bestEvaluation
/* int bestEval = minEvaluation();
int eval;
Move m;
MoveList list;
// generate list of allowed moves, put them into <list>
generateMoves(list);
// loop over all moves
while(list.getNext(m)) {
// draw move, evalute, and restore position
playMove(m);
eval = evaluate();
takeBack();
if (eval > bestEval) {
bestEval = eval;
foundBestMove(0, m, eval);
}
}
finishedNode(0,0);
*/
minimax();
}
// register ourselve as a search strategy
MinimaxStrategy minimaxStrategy;
/**
* Very simple example strategy:
* Search all possible positions reachable via one move,
* and return the move leading to best position
*
* (c) 2006, Josef Weidendorfer
*/
#include "search.h"
#include "board.h"
#include "eval.h"
/**
* To create your own search strategy:
* - copy this file into another one,
* - change the class name one the name given in constructor,
* - adjust clone() to return an instance of your class
* - adjust last line of this file to create a global instance
* of your class
* - adjust the Makefile to include your class in SEARCH_OBJS
* - implement searchBestMove()
*
* Advises for implementation of searchBestMove():
* - call foundBestMove() when finding a best move since search start
* - call finishedNode() when finishing evaluation of a tree node
* - Use _maxDepth for strength level (maximal level searched in tree)
*/
class OneLevelStrategy: public SearchStrategy
{
public:
// Defines the name of the strategy
OneLevelStrategy(): SearchStrategy("OneLevel") {}
// Factory method: just return a new instance of this class
SearchStrategy* clone() { return new OneLevelStrategy(); }
private:
/**
* Implementation of the strategy.
*/
void searchBestMove();
};
void OneLevelStrategy::searchBestMove()
{
// we try to maximize bestEvaluation
int bestEval = minEvaluation();
int eval;
Move m;
MoveList list;
// generate list of allowed moves, put them into <list>
generateMoves(list);
// loop over all moves
while(list.getNext(m)) {
// draw move, evalute, and restore position
playMove(m);
eval = evaluate();
takeBack();
if (eval > bestEval) {
bestEval = eval;
foundBestMove(0, m, eval);
}
}
finishedNode(0,0);
}
// register ourselve as a search strategy
OneLevelStrategy oneLevelStrategy;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -23,7 +23,8 @@ LDFLAGS= ...@@ -23,7 +23,8 @@ LDFLAGS=
LIB_OBJS = move.o board.o network.o search.o eval.o LIB_OBJS = move.o board.o network.o search.o eval.o
SEARCH_OBJS = $(LIB_OBJS) search-abid.o search-onelevel.o search-minimax.o SEARCH_OBJS = $(LIB_OBJS) search-abid-pvsplit.o
#SEARCH_OBJS = $(LIB_OBJS) search-abid.o search-onelevel.o search-minimax.o
#SEARCH_OBJS = $(LIB_OBJS) search-parallel-minimax.o search-parallel-abid.cpp #SEARCH_OBJS = $(LIB_OBJS) search-parallel-minimax.o search-parallel-abid.cpp
all: player all: player
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment