github.com/gorgonia/agogo@v0.1.1/game/mnk/mnk.go (about)

     1  package mnk
     2  
     3  import (
     4  	"fmt"
     5  	"hash/fnv"
     6  	"math/rand"
     7  	"sync"
     8  
     9  	"github.com/gorgonia/agogo/game"
    10  )
    11  
    12  var (
    13  	Pass = game.Single(-1)
    14  
    15  	Cross  = game.Player(game.Black)
    16  	Nought = game.Player(game.White)
    17  
    18  	r = rand.New(rand.NewSource(1337))
    19  )
    20  
    21  var _ game.State = &MNK{}
    22  
    23  // MNK is a representation of M,N,K games - a game is played on a MxN board. K moves to win.
    24  type MNK struct {
    25  	sync.Mutex
    26  	board   []game.Colour
    27  	m, n, k int
    28  
    29  	nextToMove game.Player
    30  	history    []game.PlayerMove
    31  	historical [][]game.Colour
    32  	histPtr    int
    33  }
    34  
    35  // New creates a new MNK game
    36  func New(m, n, k int) *MNK {
    37  	return &MNK{
    38  		board:      make([]game.Colour, m*n),
    39  		history:    make([]game.PlayerMove, 0, m*n),
    40  		historical: make([][]game.Colour, 0, m*n),
    41  		m:          m,
    42  		n:          n,
    43  		k:          k,
    44  	}
    45  }
    46  
    47  // TicTacToe creates a new MNK game for Tic Tac Toe
    48  func TicTacToe() *MNK {
    49  	return &MNK{
    50  		board:      make([]game.Colour, 9),
    51  		history:    make([]game.PlayerMove, 0, 9),
    52  		historical: make([][]game.Colour, 0, 9),
    53  		m:          3,
    54  		n:          3,
    55  		k:          3,
    56  	}
    57  }
    58  
    59  func (g *MNK) Format(s fmt.State, c rune) {
    60  	for i, c := range g.board {
    61  		if i%g.n == 0 {
    62  			fmt.Fprint(s, "⎢ ")
    63  		}
    64  		fmt.Fprintf(s, "%s ", c)
    65  		if (i+1)%g.n == 0 && i != 0 {
    66  			fmt.Fprint(s, "⎥\n")
    67  		}
    68  	}
    69  }
    70  
    71  func (g *MNK) BoardSize() (int, int) { return g.m, g.n }
    72  func (g *MNK) Board() []game.Colour  { return g.board }
    73  
    74  func (g *MNK) Historical(i int) []game.Colour { return g.historical[i] }
    75  
    76  func (g *MNK) Hash() game.Zobrist {
    77  	h := fnv.New32a()
    78  	for _, v := range g.board {
    79  		fmt.Fprintf(h, "%v", v)
    80  	}
    81  	return game.Zobrist(h.Sum32())
    82  }
    83  
    84  func (g *MNK) ActionSpace() int { return g.m * g.n }
    85  
    86  func (g *MNK) SetToMove(p game.Player) { g.Lock(); g.nextToMove = p; g.Unlock() }
    87  
    88  func (g *MNK) ToMove() game.Player { return g.nextToMove }
    89  
    90  func (g *MNK) LastMove() game.PlayerMove {
    91  	if len(g.history) > 0 {
    92  		return g.history[g.histPtr-1]
    93  	}
    94  	return game.PlayerMove{game.Player(game.None), Pass}
    95  }
    96  
    97  // Passes always returns -1. You can't pass in tic-tac-toe
    98  func (g *MNK) Passes() int { return -1 }
    99  
   100  func (g *MNK) MoveNumber() int { return len(g.history) }
   101  
   102  func (g *MNK) Check(m game.PlayerMove) bool {
   103  	if m.Single.IsResignation() {
   104  		return true
   105  	}
   106  
   107  	// Pass not allowed!
   108  	if m.Single.IsPass() {
   109  		return false
   110  	}
   111  
   112  	if int(m.Single) >= len(g.board) {
   113  		return false
   114  	}
   115  
   116  	if g.board[int(m.Single)] != game.None {
   117  		return false
   118  	}
   119  	return true
   120  }
   121  
   122  func (g *MNK) Apply(m game.PlayerMove) game.State {
   123  	if !g.Check(m) {
   124  		return g // no change to the state
   125  	}
   126  
   127  	hb := make([]game.Colour, len(g.board))
   128  	g.Lock()
   129  	copy(hb, g.board)
   130  	g.board[int(m.Single)] = game.Colour(m.Player)
   131  	g.histPtr++
   132  	if len(g.history) < g.histPtr {
   133  		g.history = append(g.history, m)
   134  	} else {
   135  		g.history[g.histPtr-1] = m
   136  	}
   137  	g.historical = append(g.historical, hb)
   138  	g.nextToMove = opponent(m.Player)
   139  
   140  	g.Unlock()
   141  	return g
   142  }
   143  
   144  // Handicap always returns 0.
   145  func (g *MNK) Handicap() int { return 0 }
   146  
   147  func (g *MNK) Score(p game.Player) float32 {
   148  	if g.isWinner(p) {
   149  		return 1
   150  	}
   151  	if g.isWinner(opponent(p)) {
   152  		return -2
   153  	}
   154  	return 0 // draw or incomplete
   155  }
   156  
   157  // AdditionalScore returns 0 always. No Komi
   158  func (g *MNK) AdditionalScore() float32 { return 0 }
   159  
   160  // Ended checks if the game has ended. If it has, who is the winner?
   161  func (g *MNK) Ended() (ended bool, winner game.Player) {
   162  	if g.isWinner(Cross) {
   163  		return true, Cross
   164  	}
   165  	if g.isWinner(Nought) {
   166  		return true, Nought
   167  	}
   168  	for _, c := range g.board {
   169  		if c == game.None {
   170  			return false, game.Player(game.None)
   171  		}
   172  	}
   173  	return true, game.Player(game.None)
   174  }
   175  
   176  func (g *MNK) Reset() {
   177  	for i := range g.board {
   178  		g.board[i] = game.None
   179  	}
   180  	g.history = g.history[:0]
   181  	g.histPtr = 0
   182  }
   183  
   184  func (g *MNK) UndoLastMove() {
   185  	if len(g.history) > 0 {
   186  		g.board[int(g.history[g.histPtr-1].Single)] = game.None
   187  		g.histPtr--
   188  	}
   189  }
   190  
   191  func (g *MNK) Fwd() {
   192  	if len(g.history) > 0 {
   193  		g.histPtr++
   194  	}
   195  }
   196  
   197  func (g *MNK) Eq(other game.State) bool {
   198  	ot, ok := other.(*MNK)
   199  	if !ok {
   200  		return false
   201  	}
   202  	if len(g.board) != len(ot.board) {
   203  		return false
   204  	}
   205  	for i := range g.board {
   206  		if g.board[i] != ot.board[i] {
   207  			return false
   208  		}
   209  	}
   210  	return true
   211  }
   212  
   213  func (g *MNK) Clone() game.State {
   214  	retVal := New(g.m, g.n, g.k)
   215  	g.Lock()
   216  	copy(retVal.board, g.board)
   217  	retVal.history = make([]game.PlayerMove, len(g.history), g.m*g.n)
   218  	copy(retVal.history, g.history)
   219  	copy(retVal.historical, g.historical)
   220  	retVal.nextToMove = g.nextToMove
   221  	retVal.histPtr = g.histPtr
   222  	g.Unlock()
   223  	return retVal
   224  }
   225  
   226  func (g *MNK) isWinner(p game.Player) bool {
   227  	colour := game.Colour(p)
   228  	// check rows
   229  	for i := 0; i < g.m; i++ {
   230  		var rowCount int
   231  		for j := 0; j < g.n; j++ {
   232  			if g.board[i*g.n+j] == colour {
   233  				rowCount++
   234  			} else {
   235  				rowCount--
   236  			}
   237  
   238  		}
   239  		if rowCount >= g.k {
   240  			return true
   241  		}
   242  	}
   243  	// check cols
   244  	for j := 0; j < g.n; j++ {
   245  		var count int
   246  		for i := 0; i*g.n+j < len(g.board); i++ {
   247  			if g.board[i*g.n+j] == colour {
   248  				count++
   249  			} else {
   250  				count = 0
   251  			}
   252  		}
   253  		if count >= g.k {
   254  			return true
   255  		}
   256  	}
   257  
   258  	for i := 0; i < g.m; i++ {
   259  		for j := 0; g.n-j > g.n-g.k && j < g.n; j++ {
   260  			idx := i*g.n + j
   261  			var diagCount int
   262  			for g.board[idx] == colour {
   263  				diagCount++
   264  				if diagCount >= g.k {
   265  					return true
   266  				}
   267  
   268  				idx = idx + g.n + 1
   269  				if idx >= g.m*g.n {
   270  					break
   271  				}
   272  			}
   273  		}
   274  	}
   275  
   276  	for i := 0; i < g.m; i++ {
   277  		for j := g.n - 1; j >= g.k-1; j-- {
   278  			idx := i*g.n + j
   279  			var diagCount int
   280  			for g.board[idx] == colour {
   281  				diagCount++
   282  
   283  				if diagCount >= g.k {
   284  					return true
   285  				}
   286  
   287  				idx = idx + g.n - 1
   288  				if idx >= g.m*g.n {
   289  					break
   290  				}
   291  			}
   292  		}
   293  	}
   294  	return false
   295  }
   296  
   297  func opponent(p game.Player) game.Player {
   298  	switch p {
   299  	case Cross:
   300  		return Nought
   301  	case Nought:
   302  		return Cross
   303  	}
   304  	panic("Unreachable")
   305  }