Skip to content
Browse files

- allow Play() to be called with the same color twice

(treat as if the other player passed)
- start on genmove; currently just picks a legal move at random.
  • Loading branch information...
1 parent 35c4aa7 commit 5835d868c93550d90048c7e25884a5ed8a72050e @skybrian committed Nov 28, 2009
Showing with 173 additions and 22 deletions.
  1. +21 −3 gongo_gtp.go
  2. +97 −17 gongo_robot.go
  3. +55 −2 gongo_robot_test.go
View
24 gongo_gtp.go
@@ -50,6 +50,9 @@ func Run(robot GoRobot, input io.Reader, out io.Writer) os.Error {
return nil;
}
+// GTP protocol doesn't support larger than 25x25
+const MaxBoardSize = 25;
+
type GoRobot interface {
// Attempts to change the board size. If the robot doesn't support the
// new size, return false. (In any case, board sizes above 25 aren't
@@ -96,13 +99,21 @@ func ParseColor(input string) (c Color, ok bool) {
return Empty, false;
}
+func (c Color) GetOpponent() Color {
+ switch c {
+ case Black: return White;
+ case White: return Black;
+ }
+ panic("can't get opponent for %v", c);
+}
+
func (c Color) String() string {
- switch (c) {
+ switch c {
case White: return "White";
case Black: return "Black";
case Empty: return "Empty";
}
- panic("not reachable");
+ panic("invalid color");
}
type MoveResult int;
@@ -112,7 +123,14 @@ const (
Resigned MoveResult = 2;
)
-const MaxBoardSize = 25;
+func (m MoveResult) String() string {
+ switch m {
+ case Played: return "Played";
+ case Passed: return "Passed";
+ case Resigned: return "Resigned";
+ }
+ panic("invalid move result");
+}
// === driver implementation ===
View
114 gongo_robot.go
@@ -6,18 +6,36 @@ package gongo
// http://groups.google.com/group/computer-go-archive/browse_thread/thread/bda08b9c37f0803e/8cc424b0fb1b6fe0
import (
- "log"
+ "log";
+ "rand";
)
-const DEBUG = true
+const DEBUG = true;
// === public API ===
+type Randomness interface {
+ Intn(n int) int;
+}
+
+var defaultRandomness = rand.New(rand.NewSource(1));
+
+type Config struct {
+ BoardSize int;
+ Randomness Randomness;
+}
+
func NewRobot(boardSize int) GoRobot {
+ config := Config{BoardSize: boardSize, Randomness: defaultRandomness};
+ return NewConfiguredRobot(config);
+}
+
+func NewConfiguredRobot(config Config) GoRobot {
result := new(robot);
result.board = new(board);
- result.SetBoardSize(boardSize);
- return result;
+ result.randomness = config.Randomness;
+ result.SetBoardSize(config.BoardSize);
+ return result;
}
// === implementation of a Go board ===
@@ -99,10 +117,11 @@ type board struct {
dirOffset [4]pt; // amount to add to a pt to move in each cardinal direction
cells []cell;
+ boardPoints []pt; // List of all points on the board.
+
+ // Temporary variables to avoid GC:
- // A return value of markSurroundedChain.
- // (Stored here to avoid allocations and/or copies in loops.)
- chainPoints []pt;
+ chainPoints []pt; // return value of markSurroundedChain
}
func (b *board) clearBoard(newSize int) {
@@ -115,26 +134,37 @@ func (b *board) clearBoard(newSize int) {
rowCount := newSize + 2;
b.cells = make([]cell, rowCount * b.stride + 1); // 1 extra for diagonal move to edge
+ b.boardPoints = make([]pt, b.boardSize * b.boardSize);
// fill entire array with board edge
for i := 0; i < len(b.cells); i++ {
b.cells[i] = EDGE;
}
// set all playable points to empty
+ boardPointsAdded := 0;
for y := 1; y <= b.boardSize; y++ {
for x := 1; x <= b.boardSize; x++ {
- b.cells[b.makePt(x,y)] = EMPTY;
+ thisPt := b.makePt(x,y);
+ b.cells[thisPt] = EMPTY;
+ b.boardPoints[boardPointsAdded] = thisPt;
+ boardPointsAdded++;
}
}
- b.chainPoints = make([]pt, newSize * newSize);
+ b.chainPoints = make([]pt, len(b.boardPoints));
}
func (b *board) makePt(x,y int) pt {
return pt(y * b.stride + x);
}
+func (b *board) getCoords(p pt) (x,y int) {
+ y = int(p) / b.stride;
+ x = int(p) % b.stride;
+ return;
+}
+
// Given any point in a chain with no liberties, marks all the cells in
// the chain with CELL_IN_CHAIN and adds those points to chainPoints.
// Returns the number of points found. If the chain is not surrounded,
@@ -199,7 +229,7 @@ func (b *board) capture(target pt) (chainCount int) {
// Given any occupied point, returns true if it has any liberties.
// (Used for testing suicide.)
// Precondition: same as b.markSurroundedChain
-func (b *board) haveLiberties(target pt) bool {
+func (b *board) hasLiberties(target pt) bool {
chainCount := b.markSurroundedChain(target);
if chainCount == 0 {
return true;
@@ -219,6 +249,9 @@ type robot struct {
// list of moves in this game
moves []pt;
moveCount int;
+
+ candidates []pt; // moves to choose from; used in GenMove.
+ randomness Randomness;
}
// === implementation of GoRobot interface ===
@@ -229,6 +262,8 @@ func (r *robot) SetBoardSize(newSize int) bool {
// assumes no game lasts longer than it would take to fill the board at four times (plus some extra)
r.moves = make([]pt, len(r.cells) * 4);
+ r.candidates = make([]pt, len(r.boardPoints));
+
return true;
}
@@ -240,25 +275,59 @@ func (r *robot) SetKomi(value float) {
}
func (r *robot) Play(color Color, x, y int) bool {
- if x<1 || x>r.boardSize || y<1 || y>r.boardSize || !(color == White || color == Black) {
+ if (x<1 || y <1) && !(x==0 && y==0) {
+ return false;
+ }
+ if x>r.boardSize || y>r.boardSize || !(color == White || color == Black) {
return false;
}
- move := r.makePt(x, y);
friendlyStone := cell(2 - (r.moveCount & 1));
if (friendlyStone != colorToCell(color)) {
- // GTP protocol allows two moves by same side, but this engine doesn't.
- return false;
+ // GTP protocol allows two moves by same side; treat as if the
+ // other player passed.
+ ok := r.Play(color.GetOpponent(), 0, 0);
+ if !ok {
+ return false;
+ }
}
-
- result := r.makeMove(move);
+
+ result := r.makeMove(r.makePt(x, y));
if DEBUG && result > 0 {
log.Stderrf("captured: %v", result)
}
return result >= 0;
}
func (r *robot) GenMove(color Color) (x, y int, result MoveResult) {
+ // find unoccupied points
+ candidateCount := 0;
+ for _, thisPt := range r.boardPoints {
+ if r.cells[thisPt] == EMPTY {
+ r.candidates[candidateCount] = thisPt;
+ candidateCount++;
+ }
+ }
+
+ // try each move at random
+ for triedCount := 0; triedCount < candidateCount; triedCount++ {
+ // choose random move
+ swapIndex := triedCount + r.randomness.Intn(candidateCount - triedCount);
+ thisPt := r.candidates[triedCount];
+ if swapIndex > triedCount {
+ r.candidates[triedCount] = r.candidates[swapIndex];
+ r.candidates[swapIndex] = thisPt;
+ thisPt = r.candidates[triedCount];
+ }
+ if r.isLegalMove(thisPt) {
+ r.makeMove(thisPt);
+ x, y = r.getCoords(thisPt);
+ result = Played;
+ return;
+ }
+ }
+
+ // no move found
return 0, 0, Passed;
}
@@ -272,6 +341,17 @@ func (r *robot) GetCell(x, y int) Color {
// === internal methods ===
+func (r *robot) isLegalMove(move pt) (result bool) {
+ if r.cells[move] != EMPTY {
+ return false;
+ }
+ friendlyStone := cell(2 - (r.moveCount & 1));
+ r.cells[move] = friendlyStone;
+ result = r.hasLiberties(move);
+ r.cells[move] = EMPTY;
+ return result;
+}
+
// Direct translation of move function from Java reference bot:
/* --------------------------------------------------------
make() - tries to make a move and returns a status code.
@@ -319,7 +399,7 @@ func (r *robot) makeMove(move pt) int {
if captures == 0 {
// check for suicide
- if !r.haveLiberties(move) {
+ if !r.hasLiberties(move) {
if DEBUG { log.Stderrf("disallow suicide"); }
// illegal move; undo and return
r.cells[move] = EMPTY;
View
57 gongo_robot_test.go
@@ -101,10 +101,37 @@ func TestDisallowSimpleKo(t *testing.T) {
@O.O`);
}
+func TestPlaySameColorTwice(t *testing.T) {
+ r := NewRobot(3);
+ playLegal(t, r, Black, 1, 1,
+`...
+ ...
+ @..`);
+ playLegal(t, r, Black, 2, 1,
+`...
+ ...
+ @@.`);
+}
+
+func TestPlayByPassing(t *testing.T) {
+ r := NewRobot(3);
+ playLegal(t, r, Black, 0, 0,
+`...
+ ...
+ ...`);
+}
+
+func TestFindPass(t *testing.T) {
+ r := NewRobot(1);
+ checkGenPass(t, r, Black, `.`);
+}
+
func TestFindMove(t *testing.T) {
- //r := NewRobot(1);
+ r := NewRobot(2);
+ checkGenAnyMove(t, r, Black);
}
+
// == end of tests ===
func playLegal(t *testing.T, r GoRobot, c Color, x, y int, expectedBoard string) {
@@ -123,6 +150,32 @@ func playIllegal(t *testing.T, r GoRobot, c Color, x, y int, expectedBoard strin
checkBoard(t, r, expectedBoard);
}
+func checkGenPass(t *testing.T, r GoRobot, c Color, expectedBoard string) {
+ x, y, result := r.GenMove(c);
+ if result != Passed {
+ t.Errorf("didn't generate a pass for %v; got %v (%v,%v)", c, result, x, y);
+ }
+ checkBoard(t, r, expectedBoard);
+}
+
+func checkGenAnyMove(t *testing.T, r GoRobot, colorToPlay Color) {
+ x, y, result := r.GenMove(colorToPlay);
+ if result != Played {
+ t.Errorf("didn't generate a move for %v; got %v", colorToPlay, result);
+ return;
+ }
+ size := r.GetBoardSize();
+ if x<1 || x>size || y<1 || y>size {
+ t.Errorf("didn't generate a move on the board; got: (%v,%v)", x, y);
+ } else {
+ cellColor := r.GetCell(x, y);
+ if cellColor != colorToPlay {
+ t.Errorf("played cell doesn't contain stone; got: %v", cellColor);
+ }
+ }
+}
+
+
func checkBoard(t *testing.T, r GoRobot, expectedBoard string) {
expected := trimBoard(expectedBoard);
actual := loadBoard(r);
@@ -155,4 +208,4 @@ func loadBoard(r GoRobot) string {
}
}
return out.String();
-}
+}

0 comments on commit 5835d86

Please sign in to comment.
Something went wrong with that request. Please try again.