Play Antichess!
SearchBot
Slowest bot so far, but still faster than 2 seconds per move and it beats all currently posted bots. It looks at what happens after any of the valid moves and what could happen after any move after those moves and decides what would be the best outcome. Unfortunately it cannot search deeper because it would take more than 2 seconds then.
package com.ppcgse.koth.antichess.player
import com.ppcgse.koth.antichess.controller.Board
import com.ppcgse.koth.antichess.controller.Color
import com.ppcgse.koth.antichess.controller.Location
import com.ppcgse.koth.antichess.controller.Move
import com.ppcgse.koth.antichess.controller.Piece
import com.ppcgse.koth.antichess.controller.PieceType
import com.ppcgse.koth.antichess.controller.PieceUpgradeType
import com.ppcgse.koth.antichess.controller.Player
import groovy.lang.Tuple
/**
* Created by ProgramFOX on 12/22/15.
*/
class SearchBot extends Player {
{pieceUpgradeType = PieceUpgradeType.KING}
@Override
Move getMove(Board board, Player opponent, Set<Move> validMoves) {
return getMoveInternal(board, this, opponent, validMoves, 2)[0]
}
Tuple getMoveInternal(Board board, Player whoseTurn, Player opponent, Set<Move> validMoves, Integer depth) {
def bestScore = null
def currentlyChosenMove = null
validMoves.each { m ->
def opponentPiecesValueBefore = opponent.getPieces(board).sum { getPieceValue(it.getType()) }
def newBoard = board.movePiece(whoseTurn, m)
def opponentPiecesValueAfter = opponent.getPieces(newBoard).sum { getPieceValue(it.getType()) }
if (opponentPiecesValueAfter == null) {
opponentPiecesValueAfter = 0
}
def score = opponentPiecesValueAfter - opponentPiecesValueBefore
if (whoseTurn.getTeam() == Color.BLACK) {
score = -score
}
if (depth > 1) {
def validMovesNow = genValidMoves(opponent, whoseTurn, newBoard)
def goDeeper = true
if (validMovesNow == null || validMovesNow.size() == 0) {
def toAdd = -999
if (whoseTurn.getTeam() == Color.BLACK) {
toAdd = -toAdd
}
score += toAdd
goDeeper = false
}
if (goDeeper) {
score += getMoveInternal(newBoard, opponent, whoseTurn, validMovesNow, depth - 1)[1]
}
}
if (bestScore == null) {
bestScore = score
currentlyChosenMove = m
}
if ((whoseTurn.getTeam() == Color.WHITE && score > bestScore) || (whoseTurn.getTeam() == Color.BLACK && score < bestScore)) {
bestScore = score
currentlyChosenMove = m
}
}
return new Tuple(currentlyChosenMove, bestScore)
}
Double getPieceValue(PieceType pieceType) {
switch (pieceType) {
case PieceType.KING:
return 1
case PieceType.PAWN:
return 1.5
case PieceType.KNIGHT:
return 2.5
case PieceType.BISHOP:
return 3
case PieceType.ROOK:
return 5
case PieceType.QUEEN:
return 9
default:
return 0
}
}
// Copied from Game.groovy and a bit modified.
// I actually need this.
Set<Move> genValidMoves(Player player, Player enemy, Board board) {
def allMoves = player.getPieces(board).collect { [it, it.getValidDestinationSet(board)] }
def attackMoves = allMoves
.collect { pair ->
def piece = pair[0]
def dests = pair[1]
[piece, dests.findAll { board.getFieldAtLoc(it as Location)?.piece?.team == enemy.team }]
}.findAll { it[1] }
if (attackMoves.isEmpty())
return allMoves.collect {
Piece piece = it[0] as Piece
return it[1].collect { loc -> new Move(piece, loc as Location) }
}.flatten() as Set<Move>
else
return attackMoves.collect {
Piece piece = it[0] as Piece
return it[1].collect { loc -> new Move(piece, loc as Location) }
}.flatten() as Set<Move>
}
}
SacrificeBot
This bot will check all the moves that the other player has and will check to see if any of them intersect (i.e. the piece will be killed). (This does a heck of a lot better than I ever expected ;)
package com.ppcgse.koth.antichess.player
import com.ppcgse.koth.antichess.controller.Board
import com.ppcgse.koth.antichess.controller.Color
import com.ppcgse.koth.antichess.controller.Location
import com.ppcgse.koth.antichess.controller.Move
import com.ppcgse.koth.antichess.controller.Piece
import com.ppcgse.koth.antichess.controller.PieceType
import com.ppcgse.koth.antichess.controller.PieceUpgradeType
import com.ppcgse.koth.antichess.controller.Player
import java.util.concurrent.ThreadLocalRandom
/**
* Created by Jarrett on 12/19/15.
*/
class SacrificeBot extends Player {
{pieceUpgradeType = PieceUpgradeType.ROOK}
@Override
Move getMove(Board board, Player enemy, Set<Move> validMoves) {
def enemyPieces = enemy.getPieces(board)
def pawnMoves = getPawnsMoves(board, enemyPieces)
def enemyPlayerValidMoves = (enemyPieces
.collect { it.getValidDestinationSet(realBoard) }
.flatten() as List<Location>)
enemyPlayerValidMoves += pawnMoves
def sacrificeMove = validMoves
.find {enemyPlayerValidMoves.contains(it.destination)}
if (sacrificeMove)
return sacrificeMove
else
return randomMove(validMoves)
}
def randomMove(Set<Move> validMoves) {
return validMoves[ThreadLocalRandom.current().nextInt(validMoves.size())];
}
def getPawnsMoves(Board board, List<Piece> allPieces) {
def direction = getTeam() == Color.BLACK ? 1 : -1;
def pawns = allPieces.findAll {it.type == PieceType.PAWN}
def pawnAttacks = (pawns.collect {
[it.loc.plus(-1, direction), it.loc.plus(1, direction)]
}.flatten()
).findAll {
((Location) it).isValid()
}
return pawnAttacks as List<Location>
}
}
RandomBot
This is the manditory random bot. It will always upgrade to a rook.
package com.ppcgse.koth.antichess.player
import com.ppcgse.koth.antichess.controller.PieceUpgradeType
import com.ppcgse.koth.antichess.controller.Player
import com.ppcgse.koth.antichess.controller.Move
import com.ppcgse.koth.antichess.controller.ReadOnlyBoard
import java.util.concurrent.ThreadLocalRandom;
public class TestBot extends Player {
{pieceUpgradeType = PieceUpgradeType.ROOK}
@Override
public Move getMove(ReadOnlyBoard board, Player enemy, Set<Move> moves) {
return moves[ThreadLocalRandom.current().nextInt(moves.size())];
}
}