Zagraj w Antichess!

19

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

To w zasadzie Turniej Szachowy , ale dla szachów;)

Antichess to jeden z wielu wymyślonych wariantów szachowych . Celem jest utrata wszystkich elementów (może się to wydawać trochę dziwne, ale z jakiegoś powodu nazywa się to szachami).

Zasady

Zasady gry w szachy są bardzo podobne do standardowych szachów, ale z kilkoma dość niewielkimi różnicami. Celem, jak wspomniałem powyżej, jest utrata wszystkich twoich pionków. Aby tak się stało, jeśli twój przeciwnik ma możliwość złapania jednego z twoich pionków, jest to jedyny ruch, jaki może wykonać. Jeśli dasz mu wiele szans w jednej turze, drugi gracz może wybrać swoją turę. Inną rzeczą, która się zmienia, jest to, że król nie ma specjalnych mocy - ponieważ nie możesz matować przeciwnika i nie możesz zmusić go do kontroli.

Obowiązują również następujące zmiany w standardowej grze (pomagają uprościć grę):

  • En passant zostanie zignorowany.
  • Castling nie jest możliwy.
  • Reguła Pięćdziesiąt ruch zastosowanie automatycznie (czyli gra kończy się remisem).
  • Pionki będą mogły wybrać, co promują.
  • Jeśli gracz potrzebuje więcej niż 2 sekund, aby się poruszyć, przegrywa grę.
  • Zwrócenie nieprawidłowego ruchu spowoduje przegraną.
  • Aby wygrać, twoi przeciwnicy muszą zdobyć wszystkie twoje pionki .
  • Biały rozpoczyna grę.
  • Biały jest umieszczony „na dole” pola (y = 0), czarny znajduje się u góry (y = 7).
  • Dostęp do innych zasobów niż twój bot (internet, pliki, inne boty, ...) jest zabroniony.

Punktacja

  • Wygrana daje 3 punkty, remis 1 punkt i utratę 0 punktów.
  • Każde zgłoszenie będzie rozgrywane przeciwko sobie 10 razy (5 razy w kolorze białym, 5 w kolorze czarnym).

Pisanie bota

Kod kontrolera znajduje się tutaj: https://github.com/JJ-Atkinson/SimpleAntichessKOTH

Możesz napisać swojego bota w Javie lub Groovy. Aby napisać bota, musisz rozszerzyć Playerklasę. Klasa gracza ma jedną abstrakcyjną metodę Move getMove(Board board, Player enemy, Set<Move> validMoves).

Oto krótki przegląd przydatnych metod:

Player:

  • List<Piece> getPieces(Board board): Zwróć wszystkie swoje pionki na planszy.
  • PieceUpgradeType pieceUpgradeType: Jeśli / kiedy jeden z twoich pionów dojdzie do końca planszy, musisz zdefiniować to do rodzaju elementu, który chcesz ulepszyć. Masz wybór ROOK, KNIGHT, QUEEN, BISHOP, i KING.

Board:

  • Field getFieldAtLoc(Location loc): Zwróć Fieldw miejscu. Ma pasującą getAtmetodę, więc jeśli używasz groovy, możesz pisać board[loc].
  • Field getFieldAtLoc(int x, int y): Zwróć Fieldw miejscu. Ma pasującą getAtmetodę, więc jeśli używasz groovy, możesz pisać board[x, y].
  • Board movePiece(Player player, Move move): Wykonaj ruch na planszy, aby zobaczyć, jak to się potoczy. Zwraca nową tablicę.

Jeśli chcesz zobaczyć elementy przeciwników, po prostu napisz enemy.getPieces(board). Aby dodać bota do składu, dodaj następujący wiersz do PlayerFactory:

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

Debugowanie bota:

Dołączyłem kilka narzędzi ułatwiających debugowanie twoich botów. Aby zobaczyć, jak gra jest rozgrywana na żywo, możesz ustawić Game#DEBUGflagę na true. Otrzymasz wynik podobny do tego:

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

...

(Białe to wielkie litery, król jest pokazany z i)

Jeśli twoja konsola obsługuje znaki specjalne utf-8, możesz nawet pokazać tablicę z znakami szachowymi, używając Board#USE_UTF8_TO_STRING:

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

(wygląda lepiej z czcionką mono-odstępową)

Aby zapobiec zalewowi niechcianych wyników, powinieneś zmienić Main#mainfunkcję na coś takiego:

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

Umieść swojego bota po lewej stronie, aby grać jako biały, a po prawej stronie, aby grać jako czarny.

Budowanie kontrolera:

Kontroler jest napisany groovy, więc musisz mieć zainstalowaną Javę i groovy. Jeśli nie chcesz instalować groovy, możesz użyć pliku kompilacji stopni dołączonego do kontrolera (nie zostało to przetestowane). Jeśli nie chcesz używać groovy ani gradle, możesz użyć najnowszej wersji jar ( https://github.com/JJ-Atkinson/SimpleAntichessKOTH/releases ). Jeśli to zrobisz, musisz stworzyć własną mainmetodę i ręcznie dodać bota do fabryki odtwarzacza. Przykład:

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

(Pamiętaj, że nadal możesz ustawić flagi debugowania i takie tam)

Wszelkie wykrywanie błędów jest mile widziane!

Wyniki:

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

Pamiętaj, że zawsze jestem gotowy na nowe zgłoszenia!

J Atkin
źródło
Jeśli lubisz groovy i IntelliJ ... powinieneś spojrzeć na Kotlin
TheNumberOne
Widziałem wcześniej Kotlin, ale nigdy nie przyjrzałem się mu dokładnie. To trochę przypomina scala / groovy mashup (ale to OK - groovy i scala to moje ulubione języki;)
J Atkin
Nigdy wcześniej nie używałem Scali ... ale o wiele łatwiej jest wywoływać kod Kotlin z Java niż wywoływać goovy kod z Java.
TheNumberOne
1
Możesz ulepszyć do króla?!? Na pewno nie ...
wizzwizz4,
1
@ wizzwizz4 W grze w szachy możesz.
ProgramFOX,

Odpowiedzi:

6

SearchBot

Jak dotąd najwolniejszy bot, ale wciąż szybciej niż 2 sekundy na ruch i pokonuje wszystkich aktualnie opublikowanych botów. Przygląda się, co dzieje się po jednym z ważnych ruchów i co może się stać po każdym ruchu po tych ruchach i decyduje, jaki byłby najlepszy wynik. Niestety nie może szukać głębiej, ponieważ zajęłoby to więcej niż 2 sekundy.

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
źródło
4

SacrificeBot

Ten bot sprawdzi wszystkie ruchy drugiego gracza i sprawdzi, czy któreś z nich się przecinają (tzn. Pionek zostanie zabity). (To o wiele lepsze niż się spodziewałem;)

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
źródło
3

OnePlayBot

Dead prosty bot z tylko jedną grą. Ulepszy się do wieży.

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
źródło
3

RandomBot

Jest to obowiązkowy losowy bot. Zawsze zmienia się w wieżę.

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
źródło
3

MeasureBot

To jest bot, od którego zacząłem; Pracowałem nad jego rozszerzeniem, ale potem natknąłem się na błąd głębokiego klonowania, a potem pomyślałem: „Cóż, po prostu prześlijmy już tego bota, działa on lepiej niż RandomBot i OnePlayBot, i zawsze mogę przesłać nowego bota później” , więc tutaj jest:

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 sprawdza, czy musi coś uchwycić: jeśli nie, po prostu wykonuje losowy ruch. Jeśli tak, zdecyduje, który kawałek wziąć: wybierze ten, który ma niższą wartość, ponieważ może on przechwycić mniej własnych elementów. A jeśli istnieje wiele sposobów, aby wziąć kawałek o najniższej możliwej wartości, przechwyci go kawałkiem o najwyższej możliwej wartości: jeśli to zrobi, zbliży chwytający kawałek do innych kawałków (na początku przynajmniej gra) i wolisz stracić element o wyższej wartości niż element o niższej wartości.

Oto lista użytych wartości sztuk:

  • Król: 1
  • Pion: 1,5
  • Rycerz: 2,5
  • Biskup: 3
  • Gawron: 5
  • Królowa: 9

Kiedy pionek awansuje, zawsze awansuje do króla, ponieważ jest to element o najniższej wartości.

ProgramFOX
źródło