Spielen Sie Antichess!

19

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

Dies ist im Grunde ein Schachturnier , aber für Antichess;)

Antichess ist eine der vielen erfundenen Schachvarianten . Das Ziel ist es, alle deine Teile zu verlieren (das mag etwas seltsam erscheinen, aber es wird aus einem bestimmten Grund Antichess genannt).

Die Regeln

Die Regeln für Antichess sind denen für Standardschach sehr ähnlich - allerdings mit ein paar kleineren Unterschieden. Das Ziel, wie ich oben erwähnte, ist es, alle Ihre Stücke zu verlieren. Wenn Ihr Gegner die Möglichkeit hat, eine Ihrer Figuren zu erobern, ist dies der einzige Zug, den er ausführen kann. Wenn Sie ihm mehrere Chancen in einer Runde geben, kann der andere Spieler seine Runde wählen. Eine andere Sache, die geändert wird, ist, dass der König keine besonderen Kräfte hat - wie in können Sie Ihren Gegner nicht schachmatt setzen, und Sie können ihn nicht in Schach zwingen.

Die folgenden Änderungen am Standardspiel werden ebenfalls angewendet (sie helfen, das Spiel zu vereinfachen):

  • En passant wird ignoriert.
  • Rochade ist nicht möglich.
  • Die Fünfzig-Züge-Regel gilt automatisch (dh das Spiel endet unentschieden).
  • Bauern können wählen, wofür sie werben.
  • Wenn ein Spieler länger als 2 Sekunden braucht, um sich zu bewegen, verliert er das Spiel.
  • Wenn Sie einen ungültigen Zug wiederholen, verlieren Sie das Spiel.
  • Um zu gewinnen, müssen Ihre Gegner alle Ihre Spielsteine ​​erobern .
  • Weiß beginnt das Spiel.
  • Weiß steht "unten" im Feld (y = 0), Schwarz oben (y = 7).
  • Der Zugriff auf andere Ressourcen als Ihren Bot (Internet, Dateien, andere Bots, ...) ist verboten.

Wertung

  • Das Gewinnen gewährt Ihnen 3 Punkte, ein Unentschieden 1 Punkt und das Verlieren 0 Punkte.
  • Jede Einreichung wird 10 Mal gegeneinander gespielt (5 Mal so weiß, 5 Mal so schwarz).

Schreibe deinen Bot

Der Controller-Code ist hier: https://github.com/JJ-Atkinson/SimpleAntichessKOTH

Sie können Ihren Bot entweder in Java oder Groovy schreiben. Um einen Bot zu schreiben, müssen Sie die PlayerKlasse erweitern. Die Spielerklasse hat eine abstrakte Methode Move getMove(Board board, Player enemy, Set<Move> validMoves).

Hier ein kurzer Überblick über nützliche Methoden:

Player:

  • List<Piece> getPieces(Board board): Gib alle deine Teile zurück, die auf dem Brett liegen.
  • PieceUpgradeType pieceUpgradeType: Wenn / wenn einer Ihrer Bauern das Ende des Bretts erreicht, müssen Sie dies auf den Typ der Spielfigur festlegen, auf die Sie upgraden möchten. Sie haben die Wahl ROOK, KNIGHT, QUEEN, BISHOP, und KING.

Board:

  • Field getFieldAtLoc(Location loc): Geben Sie das Fieldam Standort zurück. Dies hat eine passende getAtMethode, so dass Sie schreiben können, wenn Sie Groovy verwenden board[loc].
  • Field getFieldAtLoc(int x, int y): Geben Sie das Fieldam Standort zurück. Dies hat eine passende getAtMethode, so dass Sie schreiben können, wenn Sie Groovy verwenden board[x, y].
  • Board movePiece(Player player, Move move): Machen Sie eine Bewegung auf dem Brett, damit Sie sehen können, wie es ausgehen würde. Es gibt das neue Board zurück.

Wenn Sie die Teile Ihres Gegners sehen möchten, schreiben Sie einfach enemy.getPieces(board). Um Ihren Bot zur Aufstellung hinzuzufügen, fügen Sie die folgende Zeile hinzu PlayerFactory:

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

Debuggen Ihres Bots:

Ich habe ein paar Tools beigefügt, die Ihnen beim Debuggen Ihrer Bots helfen sollen. Um Ihr Spiel live zu sehen, können Sie das Game#DEBUGFlag auf true setzen. Sie erhalten eine Ausgabe wie diese:

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

...

(Weiß ist Großbuchstabe, der König wird mit angezeigt i)

Wenn Ihre Konsole utf-8-Sonderzeichen unterstützt, können Sie das Brett sogar mit den Schachzeichen anzeigen, indem Sie Folgendes verwenden Board#USE_UTF8_TO_STRING:

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

(es sieht besser aus mit einer Mono-Schriften)

Um eine Flut unerwünschter Ausgaben zu vermeiden, sollten Sie die Main#mainFunktion folgendermaßen ändern :

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

Platziere deinen Bot links, um als Weiß zu spielen, und rechts, um als Schwarz zu spielen.

Aufbau des Controllers:

Der Controller ist in Groovy geschrieben, Sie müssen also Java und Groovy installiert haben. Wenn Sie Groovy nicht installieren möchten, können Sie die mit dem Controller gelieferte Gradle-Build-Datei verwenden (diese wurde nicht getestet). Wenn Sie Groovy oder Gradle nicht verwenden möchten, können Sie das neueste Release-Jar verwenden ( https://github.com/JJ-Atkinson/SimpleAntichessKOTH/releases ). In diesem Fall müssen Sie Ihre eigene mainMethode erstellen und Ihren Bot manuell zur Player-Factory hinzufügen. Beispiel:

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

(Beachten Sie, dass Sie immer noch die Debug-Flags und Sachen setzen können)

Jede und jede Fehlersuche ist erwünscht!

Scores:

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

Bitte beachte, dass ich immer bereit bin, neue Beiträge einzureichen!

J Atkin
quelle
Wenn du Groovy und IntelliJ
magst, solltest
Ich habe Kotlin schon einmal gesehen, habe es mir aber nie genau angesehen. Es sieht irgendwie aus wie ein Scala / Groovy Mashup (aber das ist in Ordnung - Groovy und Scala sind meine Lieblingssprachen;)
J Atkin
Ich habe Scala noch nie benutzt ... aber es ist viel einfacher, Kotlin-Code aus Java aufzurufen, als Goovy-Code aus Java aufzurufen.
TheNumberOne
1
Du kannst auf einen König upgraden?!? Sicher nicht ...
wizzwizz4
1
@ wizzwizz4 In Antichess kannst du.
ProgramFOX

Antworten:

6

SearchBot

Bisher langsamster Bot, aber immer noch schneller als 2 Sekunden pro Zug, und übertrifft alle derzeit geposteten Bots. Es wird geprüft, was nach einem der gültigen Züge passiert und was nach jedem Zug nach diesen Zügen passieren könnte, und es wird entschieden, was das beste Ergebnis wäre. Leider kann es nicht tiefer suchen, da es dann mehr als 2 Sekunden dauern würde.

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

SacrificeBot

Dieser Bot überprüft alle Züge des anderen Spielers und prüft, ob sie sich überschneiden (dh die Figur wird getötet). (Das macht eine Menge besser als ich jemals erwartet hatte;)

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

OnePlayBot

Toter einfacher Bot mit nur einem Spiel. Es wird zu einem Turm aufgewertet.

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

RandomBot

Dies ist der obligatorische Zufallsbot. Es wird immer zu einem Turm aufgewertet.

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

MeasureBot

Dies ist der Bot, mit dem ich angefangen habe; Ich habe daran gearbeitet, es zu erweitern, aber dann bin ich auf den Deep-Clone-Bug gestoßen. Dann dachte ich: "Nun, lasst uns diesen Bot einfach schon einsenden, er funktioniert besser als RandomBot und OnePlayBot, und ich kann später immer noch einen neuen Bot einsenden." , hier ist es also:

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 sieht aus, ob etwas erfasst werden muss. Wenn dies nicht der Fall ist, wird nur eine zufällige Bewegung ausgeführt. Wenn dies der Fall ist, entscheidet er, welches Stück er nehmen möchte: Es wird eines mit einem niedrigeren Stückwert ausgewählt, da diese weniger eigene Stücke erfassen können. Und wenn es mehrere Möglichkeiten gibt, ein Teil mit dem niedrigstmöglichen Wert aufzunehmen, wird es mit dem Teil mit dem höchstmöglichen Wert aufgenommen. In diesem Fall wird das aufgenommene Teil näher an andere Teile herangeführt (am Anfang des Abschnitts) Spiel, zumindest) und Sie würden lieber ein höherwertiges als ein niedrigerwertiges Stück verlieren.

Dies ist eine Liste der Stückwerte, die ich verwendet habe:

  • König: 1
  • Bauer: 1.5
  • Ritter: 2.5
  • Bischof: 3
  • Turm: 5
  • Königin: 9

Wenn ein Bauer befördert, wird er immer zu einem König befördert, da es sich um das Stück mit dem niedrigsten Wert handelt.

ProgramFOX
quelle