Tocar Antichess!

19

https://en.wikipedia.org/wiki/Losing_chess

Este é basicamente o Torneio de Xadrez , mas para o antichess;)

Antichess é uma das muitas variantes de xadrez que foram inventadas. O objetivo é perder todas as suas peças (isso pode parecer um pouco estranho, mas é chamado de antichess por um motivo).

As regras

As regras do antichess são muito semelhantes ao xadrez padrão - mas com algumas diferenças bastante pequenas. O objetivo, como mencionei acima, é perder todas as suas peças. Para que isso aconteça, se o seu oponente tiver a oportunidade de capturar uma de suas peças, esse é o único movimento que ele pode fazer. Se você lhe der várias chances em um turno, o outro jogador poderá escolher o seu turno. Outra coisa que mudou é que o rei não tem poderes especiais - já que você não pode xeitar seu oponente e não pode forçá-lo a fazer check.

As seguintes alterações no jogo padrão também serão aplicadas (elas ajudam a simplificar o jogo):

  • En passant será ignorado.
  • Castling não é possível.
  • A regra dos cinquenta movimentos se aplica automaticamente (o que significa que o jogo termina em empate).
  • Os peões poderão escolher o que promovem.
  • Se um jogador precisar de mais de 2 segundos para se mover, ele perderá o jogo.
  • Retornar uma jogada inválida resultará na perda do jogo.
  • Para vencer, seus oponentes devem capturar todas as suas peças .
  • As brancas começam o jogo.
  • O branco é colocado "na parte inferior" do campo (y = 0), o preto está localizado na parte superior (y = 7).
  • É proibido acessar outros recursos além do seu bot (internet, arquivos, outros bots, ...).

Pontuação

  • Ganhar concede a você 3 pontos, um empate 1 ponto e a perda de 0 pontos.
  • Cada envio jogará um contra o outro 10 vezes (5 vezes branco, 5 preto).

Escrevendo seu bot

O código do controlador está aqui: https://github.com/JJ-Atkinson/SimpleAntichessKOTH

Você pode escrever seu bot em Java ou Groovy. Para escrever um bot, você deve estender a Playerclasse. A classe de jogador tem um método abstrato Move getMove(Board board, Player enemy, Set<Move> validMoves).

Aqui está um rápido resumo dos métodos úteis:

Player:

  • List<Piece> getPieces(Board board): Devolva todas as suas peças que estão no tabuleiro.
  • PieceUpgradeType pieceUpgradeType: Se / quando um de seus peões chegar ao final do tabuleiro, você precisará definir isso para o tipo de peça para a qual deseja atualizar. Você tem a opção de ROOK, KNIGHT, QUEEN, BISHOP, e KING.

Board:

  • Field getFieldAtLoc(Location loc): Retorne o Fieldno local. Isso tem um getAtmétodo correspondente para que, se você estiver usando o groovy, possa escrever board[loc].
  • Field getFieldAtLoc(int x, int y): Retorne o Fieldno local. Isso tem um getAtmétodo correspondente para que, se você estiver usando o groovy, possa escrever board[x, y].
  • Board movePiece(Player player, Move move): Faça uma jogada no tabuleiro para ver como isso se desenrolaria. Retorna o novo quadro.

Se você quiser ver as peças dos seus oponentes, basta escrever enemy.getPieces(board). Para adicionar seu bot à programação, adicione a seguinte linha em PlayerFactory:

put(YourBot.class, { new YourBot() } )

Depurando seu bot:

Incluí algumas ferramentas para ajudar na depuração de seus bots. Para ver seu jogo ao vivo, defina a Game#DEBUGbandeira como verdadeira. Você obterá uma saída semelhante a esta:

Game started. Players: [OnePlayBot(WHITE), SacrificeBot(BLACK)]
...
BLACKs turn.
validMoves: [Move(Piece(BLACK, PAWN, Loc(0, 6)), Loc(0, 5)), ...]
board:
RKBQIBKR
PPPPPPPP
--------
--------
--------
p-------
-ppppppp
rkbqibkr

captureless turns: 1
chosen move: Move(Piece(BLACK, PAWN, Loc(7, 6)), Loc(7, 4))
Game over? false

==============================

WHITEs turn.
validMoves: [Move(Piece(WHITE, ROOK, Loc(0, 0)), Loc(0, 1)), ...]
board:
RKBQIBKR
PPPPPPP-
--------
-------P
--------
p-------
-ppppppp
rkbqibkr

...

(Branco é maiúsculo, o rei é mostrado com i)

Se o seu console suporta caracteres especiais utf-8, você pode até mostrar o tabuleiro com os caracteres de xadrez usando Board#USE_UTF8_TO_STRING:

♜♞♝♛♚♝—♜
♟—♟♟♟♟♟♟
————————
—♟——————
————————
♙———————
—♙♙♙♙♔♙♙
♖♘♗♕—♗♘♖

(fica melhor com uma fonte espaçada em mono)

Para evitar uma inundação de resultados indesejados, você deve alterar a Main#mainfunção para algo como isto:

new Game(new MyBot(), new SacrificeBot()).run()

Coloque seu bot à esquerda para jogar como branco, à direita para jogar como preto.

Construindo o controlador:

O controlador é gravado em groovy, portanto você deve ter o java e o groovy instalados. Se você não deseja instalar o groovy, pode usar o arquivo gradle build que acompanha o controlador (isso não foi testado). Se você não quiser usar groovy ou gradle, poderá usar o jar de lançamento mais recente ( https://github.com/JJ-Atkinson/SimpleAntichessKOTH/releases ). Se você fizer isso, precisará criar seu próprio mainmétodo e adicionar seu bot manualmente à fábrica do player. Exemplo:

PlayerFactory.players.put(YourBot.class, { new YourBot() } )
new Runner().runGames();

(Observe que você ainda pode definir os sinalizadores de depuração e outras coisas)

Toda e qualquer descoberta de bug é apreciada!

Pontuações:

SearchBot -> 101
SacrificeBot -> 81
MeasureBot -> 37
RandomBot -> 28
OnePlayBot -> 24

Observe que estou sempre disposto a receber novos envios!

J Atkin
fonte
Se você gosta de groovy e IntelliJ ... dê uma olhada no Kotlin
TheNumberOne
Eu já vi Kotlin antes, mas nunca o examinei completamente. É meio que se parece com um mashup scala / Groovy (mas isso é OK - Groovy e Scala são minhas línguas preferidas;)
J Atkin
Eu nunca usei o scala antes ... mas é muito mais fácil chamar o código Kotlin de java do que chamar código goovy do java.
TheNumberOne
11
Você pode atualizar para um rei?!? Certamente não ...
wizzwizz4
11
@ wizzwizz4 No antichess, você pode.
ProgramFOX

Respostas:

6

SearchBot

Bot mais lento até agora, mas ainda mais rápido que 2 segundos por movimento e supera todos os bots atualmente publicados. Ele examina o que acontece após qualquer uma das jogadas válidas e o que poderia acontecer após qualquer jogada depois dessas jogadas e decide qual seria o melhor resultado. Infelizmente, não é possível pesquisar mais profundamente, porque levaria mais de 2 segundos.

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>
    }
 }
ProgramFOX
fonte
4

SacrificeBot

Este bot irá verificar todos os movimentos que o outro jogador possui e verificará se algum deles se cruza (ou seja, a peça será morta). (Isso é muito melhor do que eu esperava;)

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>
    }
}
J Atkin
fonte
3

OnePlayBot

Bot simples e morto com apenas uma jogada. Ele será atualizado para uma torre.

package com.ppcgse.koth.antichess.player

import com.ppcgse.koth.antichess.controller.Move
import com.ppcgse.koth.antichess.controller.PieceUpgradeType
import com.ppcgse.koth.antichess.controller.Player
import com.ppcgse.koth.antichess.controller.ReadOnlyBoard

public class OnePlayBot extends Player {

    {pieceUpgradeType = PieceUpgradeType.ROOK}

    @Override
    public Move getMove(ReadOnlyBoard board, Player enemy, Set<Move> moves) {
        return new ArrayList<Move>(moves).get(0);
    }

}
J Atkin
fonte
3

RandomBot

Este é o bot aleatório obrigatório. Ele sempre será atualizado para uma torre.

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())];
    }

}
J Atkin
fonte
3

MeasureBot

Este é o bot que eu comecei; Eu estava trabalhando para expandi-lo, mas depois me deparei com o bug do deep-clone e pensei "bem, vamos enviar esse bot já, ele tem um desempenho melhor que o RandomBot e o OnePlayBot, e sempre posso enviar um novo bot mais tarde" , Então aqui está:

package com.ppcgse.koth.antichess.player

import com.ppcgse.koth.antichess.controller.Board
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 ProgramFOX on 12/21/15.
 */

 class MeasureBot extends Player {
    {pieceUpgradeType = PieceUpgradeType.KING}

    @Override
    Move getMove(Board board, Player opponent, Set<Move> validMoves) {
        def opponentPieces = opponent.getPieces(board)
        def mustCapture = opponentPieces.find { it.loc == validMoves[0].destination } != null
        def chosen = null
        if (mustCapture) {
            def piecesThatCanBeTaken = opponentPieces.findAll { validMoves.collect { it.getDestination() }.contains(it.loc) }
            def lowestAmount = getPieceValue(piecesThatCanBeTaken.sort { getPieceValue(it.getType()) }[0].getType())
            def piecesWithLowestValue = piecesThatCanBeTaken.findAll { getPieceValue(it.getType()) == lowestAmount }
            def chosenOnes = validMoves.findAll { m -> piecesWithLowestValue.find { it.loc ==  m.destination } != null }
            chosen = chosenOnes.sort { getPieceValue(it.piece.getType()) }.reverse()[0]
        } else {
            chosen = randomMove(validMoves);
        }
        return chosen
    }

    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
        }
    }

    def randomMove(Set<Move> validMoves) {
        return validMoves[ThreadLocalRandom.current().nextInt(validMoves.size())];
    }
 }

O MeasureBot verifica se precisa capturar algo: se não, apenas faz um movimento aleatório. Se isso acontecer, ele decidirá qual peça levar: escolherá uma com um valor mais baixo, porque elas podem capturar menos peças próprias. E se houver várias maneiras de pegar uma peça com o menor valor possível, ela será capturada com a peça com o maior valor possível: se fizer isso, aproximará a peça capturadora de outras peças (no início do pelo menos) e você prefere perder uma peça de maior valor do que uma de menor valor.

Esta é uma lista dos valores das peças que usei:

  • Rei: 1
  • Peão: 1,5
  • Cavaleiro: 2,5
  • Bispo: 3
  • Torre: 5
  • Rainha: 9

Quando um peão promove, ele sempre será promovido a um rei, porque é a peça de menor valor.

ProgramFOX
fonte