Juega Antichess!

19

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

Esto es básicamente un torneo de ajedrez , pero por antichess;)

Antichess es una de las muchas variantes de ajedrez que se han inventado. El objetivo es perder todas tus piezas (esto puede parecer un poco extraño, pero se llama antichess por alguna razón).

Las normas

Las reglas de antichess son muy similares al ajedrez estándar, pero con algunas diferencias bastante menores. El objetivo como mencioné anteriormente es perder todas tus piezas. Para que esto suceda, si tu oponente tiene la oportunidad de capturar una de tus piezas, ese es el único movimiento que puede hacer. Si le das múltiples oportunidades en un turno, el otro jugador puede elegir su turno. Otra cosa que cambia es que el rey no tiene poderes especiales, ya que no puedes jaquear a tu oponente y no puedes obligarlo a jalar.

También se aplicarán los siguientes cambios en el juego estándar (ayudan a simplificar el juego):

  • En passant será ignorado.
  • El enroque no es posible.
  • La regla de los cincuenta movimientos se aplica automáticamente (lo que significa que el juego termina en empate).
  • Los peones podrán elegir a qué promocionar.
  • Si un jugador necesita más de 2 segundos para moverse, perderá el juego.
  • Devolver un movimiento no válido dará como resultado la pérdida del juego.
  • Para ganar, tus oponentes deben capturar todas tus piezas .
  • Las blancas comienzan el juego.
  • El blanco se coloca "en la parte inferior" del campo (y = 0), el negro se encuentra en la parte superior (y = 7).
  • Está prohibido acceder a otros recursos que no sean su bot (internet, archivos, otros bots, ...).

Puntuación

  • Ganar te otorga 3 puntos, un empate de 1 punto y perder 0 puntos.
  • Cada presentación jugará una contra la otra 10 veces (5 veces como blanco, 5 como negro).

Escribiendo tu bot

El código del controlador está aquí: https://github.com/JJ-Atkinson/SimpleAntichessKOTH

Puedes escribir tu bot en Java o Groovy. Para escribir un bot debes extender la Playerclase. La clase de jugador tiene un método abstracto Move getMove(Board board, Player enemy, Set<Move> validMoves).

Aquí hay un resumen rápido de métodos útiles:

Player:

  • List<Piece> getPieces(Board board): Devuelve todas tus piezas que están en el tablero.
  • PieceUpgradeType pieceUpgradeType: Si / cuando uno de tus peones llega al final del tablero, deberás definir esto para el tipo de pieza a la que deseas actualizar. Usted tiene la opción de ROOK, KNIGHT, QUEEN, BISHOP, y KING.

Board:

  • Field getFieldAtLoc(Location loc): Devuelva el Fielden la ubicación. Esto tiene un getAtmétodo de correspondencia para que si estás usando groovy puedas escribir board[loc].
  • Field getFieldAtLoc(int x, int y): Devuelva el Fielden la ubicación. Esto tiene un getAtmétodo de correspondencia para que si estás usando groovy puedas escribir board[x, y].
  • Board movePiece(Player player, Move move): Haz un movimiento en el tablero para que puedas ver cómo se desarrollaría. Devuelve el nuevo tablero.

Si quieres ver las piezas de tus oponentes, solo escribe enemy.getPieces(board). Para agregar su bot a la alineación agregue la siguiente línea a PlayerFactory:

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

Depuración de su bot:

He incluido un par de herramientas para ayudar a depurar sus bots. Para ver tu juego en vivo, puedes configurar la Game#DEBUGbandera como verdadera. Obtendrá un resultado similar a este:

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

...

(El blanco es mayúscula, el rey se muestra con i)

Si su consola admite caracteres especiales utf-8, incluso puede mostrar el tablero con los caracteres de ajedrez utilizando Board#USE_UTF8_TO_STRING:

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

(se ve mejor con una fuente monoespaciada)

Para evitar una avalancha de resultados no deseados, debe cambiar la Main#mainfunción a algo como esto:

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

Pon tu bot a la izquierda para jugar como blanco, ponlo a la derecha para jugar como negro.

Construyendo el controlador:

El controlador está escrito en groovy, por lo que debe tener java y groovy instalados. Si no desea instalar Groovy, puede usar el archivo de compilación Gradle que viene con el controlador (esto no ha sido probado). Si no desea usar groovy o gradle, puede usar el último jar de lanzamiento ( https://github.com/JJ-Atkinson/SimpleAntichessKOTH/releases ). Si haces esto, necesitas crear tu propio mainmétodo y agregar tu bot manualmente a la fábrica de reproductores. Ejemplo:

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

(Tenga en cuenta que aún puede configurar los indicadores de depuración y esas cosas)

¡Se aprecian todos los errores encontrados!

Puntuaciones:

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

¡Tenga en cuenta que siempre estoy dispuesto a recibir nuevas presentaciones!

J Atkin
fuente
Si te gusta Groovy e IntelliJ ... deberías echar un vistazo a Kotlin
TheNumberOne
He visto a Kotlin antes, pero nunca lo miré a fondo. Parece un poco mashup scala / groovy (pero eso está bien - groovy y scala son mis idiomas favoritos;)
J Atkin
Nunca he usado scala antes ... pero es mucho más fácil llamar al código Kotlin desde java que llamar al código goovy desde java.
TheNumberOne
1
¿Puedes ascender a rey? Seguramente no ...
wizzwizz4
1
@ wizzwizz4 En antichess, puedes.
ProgramFOX

Respuestas:

6

SearchBot

El bot más lento hasta ahora, pero aún más rápido que 2 segundos por movimiento y supera a todos los bots publicados actualmente. Analiza lo que sucede después de cualquiera de los movimientos válidos y lo que podría suceder después de cualquier movimiento después de esos movimientos y decide cuál sería el mejor resultado. Desafortunadamente, no puede buscar más profundo porque tomaría más de 2 segundos entonces.

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
fuente
4

SacrificioBot

Este bot verificará todos los movimientos que tiene el otro jugador y verificará si alguno de ellos se cruza (es decir, la pieza será eliminada). (Esto es muchísimo mejor de lo que esperaba;)

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
fuente
3

OnePlayBot

Bot simple muerto con una sola jugada. Se actualizará a una 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
fuente
3

RandomBot

Este es el bot aleatorio obligatorio. Siempre se actualizará a una 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
fuente
3

MeasureBot

Este es el bot con el que comencé; Estaba trabajando para expandirlo, pero luego me encontré con el error de clones profundos, y luego pensé "bueno, simplemente enviemos este bot ya, funciona mejor que RandomBot y OnePlayBot, y siempre puedo enviar un nuevo bot más tarde" , asi que aqui esta:

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

MeasureBot busca si necesita capturar algo: si no lo hace, simplemente realiza un movimiento aleatorio. Si lo hace, decidirá qué pieza tomar: elegirá una con un valor de pieza más bajo porque esas pueden capturar menos piezas propias. Y si hay varias formas de tomar una pieza con el valor más bajo posible, la capturará con la pieza con el valor más alto posible: si lo hace, acercará la pieza de captura a otras piezas (al comienzo del juego, al menos) y preferirías perder una pieza de mayor valor que una de menor valor.

Esta es una lista de los valores de pieza que utilicé:

  • Rey: 1
  • Peón: 1.5
  • Caballero: 2.5
  • Obispo: 3
  • Torre: 5
  • Reina: 9

Cuando un peón asciende, siempre se convertirá en rey, porque es la pieza de menor valor.

ProgramFOX
fuente