Jouez à Antichess!

19

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

Il s'agit essentiellement d'un tournoi d'échecs , mais pour les antichess;)

Antichess est l'une des nombreuses variantes d'échecs qui ont été inventées. Le but est de perdre toutes vos pièces (cela peut sembler un peu étrange, mais cela s'appelle antichess pour une raison).

Les règles

Les règles de l'antichess sont très similaires aux échecs standard - mais avec quelques différences assez mineures. Le but comme je l'ai mentionné ci-dessus est de perdre toutes vos pièces. Pour y arriver, si votre adversaire a la possibilité de capturer une de vos pièces, c'est le seul mouvement qu'il puisse faire. Si vous lui donnez plusieurs chances en un tour, l'autre joueur peut choisir son tour. Une autre chose qui a changé est que le roi n'a pas de pouvoirs spéciaux - car vous ne pouvez pas mater votre adversaire et vous ne pouvez pas le forcer à échouer.

Les modifications suivantes du jeu standard s'appliqueront également (elles aident à simplifier le jeu):

  • En passant sera ignoré.
  • Le roque n'est pas possible.
  • La règle des cinquante coups s'applique automatiquement (ce qui signifie que le jeu se termine par un match nul).
  • Les pions pourront choisir ce qu'ils promeuvent.
  • Si un joueur a besoin de plus de 2 secondes pour se déplacer, il perdra la partie.
  • Le retour d'un coup invalide entraînera la perte de la partie.
  • Pour gagner, vos adversaires doivent capturer toutes vos pièces .
  • Les blancs commencent le jeu.
  • Le blanc est placé "en bas" du champ (y = 0), le noir est situé en haut (y = 7).
  • L'accès à d'autres ressources que votre bot (internet, fichiers, autres bots, ...) est interdit.

Notation

  • Gagner vous accorde 3 points, un match nul 1 point et perdre 0 points.
  • Chaque soumission sera jouée 10 fois contre l'autre (5 fois blanche, 5 noire).

Écrire votre bot

Le code du contrôleur est ici: https://github.com/JJ-Atkinson/SimpleAntichessKOTH

Vous pouvez écrire votre bot en Java ou en Groovy. Pour écrire un bot, vous devez étendre la Playerclasse. La classe de joueur a une méthode abstraite Move getMove(Board board, Player enemy, Set<Move> validMoves).

Voici un bref aperçu des méthodes utiles:

Player:

  • List<Piece> getPieces(Board board): Retournez toutes vos pièces qui se trouvent sur le plateau.
  • PieceUpgradeType pieceUpgradeType: Si / quand l'un de vos pions atteint la fin du plateau, vous devrez le définir en fonction du type de pièce que vous souhaitez mettre à niveau. Vous avez le choix entre ROOK, KNIGHT, QUEEN, BISHOPet KING.

Board:

  • Field getFieldAtLoc(Location loc): Retournez le Fieldà l'emplacement. Cela a une getAtméthode de correspondance de sorte que si vous utilisez groovy, vous pouvez écrire board[loc].
  • Field getFieldAtLoc(int x, int y): Retournez le Fieldà l'emplacement. Cela a une getAtméthode de correspondance de sorte que si vous utilisez groovy, vous pouvez écrire board[x, y].
  • Board movePiece(Player player, Move move): Faites un pas sur le plateau pour voir comment ça se passerait. Il renvoie la nouvelle carte.

Si vous voulez voir les pièces de vos adversaires, écrivez simplement enemy.getPieces(board). Pour ajouter votre bot à la programmation, ajoutez la ligne suivante à PlayerFactory:

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

Débogage de votre bot:

J'ai inclus quelques outils pour aider au débogage de vos bots. Pour voir votre match joué en direct, vous pouvez définir le Game#DEBUGdrapeau sur true. Vous obtiendrez une sortie quelque chose comme ceci:

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

...

(Le blanc est en majuscules, le roi est montré avec i)

Si votre console prend en charge les caractères spéciaux utf-8, vous pouvez même afficher le tableau avec les caractères d'échecs en utilisant Board#USE_UTF8_TO_STRING:

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

(il semble mieux avec une police à espacement mono)

Pour éviter un flot de sortie indésirable, vous devez remplacer la Main#mainfonction par quelque chose comme ceci:

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

Mettez votre bot à gauche pour jouer en blanc, mettez-le à droite pour jouer en noir.

Construire le contrôleur:

Le contrôleur est écrit en groovy, vous devez donc avoir installé java et groovy. Si vous ne voulez pas installer groovy, vous pouvez utiliser le fichier de construction gradle fourni avec le contrôleur (cela n'a pas été testé). Si vous ne voulez pas utiliser groovy ou gradle, vous pouvez utiliser le dernier pot de version ( https://github.com/JJ-Atkinson/SimpleAntichessKOTH/releases ). Si vous faites cela, vous devez créer votre propre mainméthode et ajouter votre bot manuellement à l'usine du lecteur. Exemple:

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

(Notez que vous pouvez toujours définir les indicateurs de débogage et tout ça)

Toute recherche de bogue est appréciée!

Scores:

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

Veuillez noter que je suis toujours prêt à recevoir de nouvelles soumissions!

J Atkin
la source
Si vous aimez groovy et IntelliJ ... vous devriez jeter un œil à Kotlin
TheNumberOne
J'ai déjà vu Kotlin, mais je ne l'ai jamais regardé à fond. Cela ressemble un peu à un mashup scala / groovy (mais c'est OK - groovy et scala sont mes langues préférées;)
J Atkin
Je n'ai jamais utilisé scala auparavant ... mais il est beaucoup plus facile d'appeler du code Kotlin depuis java que d'appeler du code goovy depuis java.
TheNumberOne
1
Vous pouvez passer à un roi?!? Sûrement pas ...
wizzwizz4
1
@ wizzwizz4 En antichess, vous le pouvez.
ProgramFOX

Réponses:

6

SearchBot

Le robot le plus lent jusqu'à présent, mais toujours plus rapide que 2 secondes par mouvement et il bat tous les robots actuellement publiés. Il examine ce qui se passe après l'un des mouvements valides et ce qui pourrait se produire après n'importe quel mouvement après ces mouvements et décide quel serait le meilleur résultat. Malheureusement, il ne peut pas rechercher plus en profondeur car cela prendrait alors plus de 2 secondes.

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
la source
4

SacrificeBot

Ce bot vérifiera tous les mouvements de l'autre joueur et vérifiera si l'un d'eux se croise (c'est-à-dire que la pièce sera tuée). (Cela fait beaucoup mieux que ce à quoi je m'attendais;)

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
la source
3

OnePlayBot

Bot simple mort avec un seul jeu. Il passera à une tour.

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
la source
3

RandomBot

Ceci est le bot aléatoire obligatoire. Il sera toujours mis à niveau vers une tour.

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
la source
3

MeasureBot

C'est le bot avec lequel j'ai commencé; Je travaillais sur son expansion, mais je suis tombé sur le bogue du clone profond, puis j'ai pensé "eh bien, soumettons déjà ce bot, il fonctionne mieux que RandomBot et OnePlayBot, et je peux toujours soumettre un nouveau bot plus tard" alors voici:

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 regarde s'il a besoin de capturer quelque chose: s'il ne le fait pas, il fait juste un mouvement aléatoire. Si c'est le cas, il décidera de la pièce à prendre: il en choisira une avec une valeur de pièce inférieure car celles-ci peuvent capturer moins de ses propres pièces. Et s'il existe plusieurs façons de prendre un morceau avec la valeur la plus basse possible, il le capturera avec le morceau avec la valeur la plus élevée possible: s'il le fait, il rapprochera le morceau de capture des autres morceaux (au début de la jeu, au moins) et vous préférez perdre une pièce de plus grande valeur qu'une pièce de moindre valeur.

Voici une liste des valeurs des pièces que j'ai utilisées:

  • Roi: 1
  • Pion: 1,5
  • Chevalier: 2,5
  • Évêque: 3
  • Tour: 5
  • Reine: 9

Lorsqu'un pion fait la promotion, il sera toujours promu au roi, car c'est la pièce la moins valorisée.

ProgramFOX
la source