Играть в антихесс!

19

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

Это в основном шахматный турнир , но для античесс;)

Antichess является одним из многих вариантов шахмат изобретенных . Цель состоит в том, чтобы потерять все ваши фигуры (это может показаться немного странным, но по какой-то причине это называется антишессом).

Правила

Правила античесс очень похожи на стандартные шахматы - но с некоторыми довольно незначительными отличиями. Цель, о которой я говорил выше, - потерять все свои фигуры. Чтобы это произошло, если у вашего противника есть возможность захватить одну из ваших фигур, это единственный ход, который он может сделать. Если вы дадите ему несколько шансов за один ход, другой игрок может выбрать свой ход. Другая вещь, которая изменилась, заключается в том, что у короля нет особых способностей - так как вы не можете поставить своего противника в мат и не можете заставить его проверить.

Также будут применяться следующие изменения в стандартной игре (они помогают упростить игру):

  • En passant будет игнорироваться.
  • Рокировка невозможна.
  • Правило Пятьдесят шаг применяется автоматически ( это означает , игра заканчивается вничью).
  • Пешки смогут выбрать то, что они продвигают.
  • Если игроку требуется больше 2 секунд для движения, он проигрывает игру.
  • Возвращение неверного хода приведет к проигрышу.
  • Чтобы победить, ваши противники должны захватить все ваши фигуры .
  • Белые начинают игру.
  • Белый помещается «внизу» поля (y = 0), черный - вверху (y = 7).
  • Доступ к другим ресурсам, кроме вашего бота (интернет, файлы, другие боты, ...) запрещен.

счет

  • Выигрыш дает вам 3 очка, ничья 1 очко и потеря 0 очков.
  • Каждое представление будет играть друг против друга 10 раз (5 раз как белый, 5 как черный).

Написание вашего бота

Код контроллера находится здесь: https://github.com/JJ-Atkinson/SimpleAntichessKOTH

Вы можете написать свой бот на Java или Groovy. Чтобы написать бота, вы должны расширить Playerкласс. Класс игрока имеет один абстрактный метод Move getMove(Board board, Player enemy, Set<Move> validMoves).

Вот краткое изложение полезных методов:

Player:

  • List<Piece> getPieces(Board board): Верните все свои фигуры, которые есть на доске.
  • PieceUpgradeType pieceUpgradeType: Если / когда одна из ваших пешек достигнет конца доски, вам нужно будет указать тип фигуры, до которой вы хотите перейти. У вас есть выбор ROOK, KNIGHT, QUEEN, BISHOP, и KING.

Board:

  • Field getFieldAtLoc(Location loc): Вернуть Fieldна месте. Это getAtметод сопоставления, так что если вы используете groovy, вы можете написатьboard[loc] .
  • Field getFieldAtLoc(int x, int y): Вернуть Fieldна месте. Это getAtметод сопоставления, так что если вы используете groovy, вы можете написатьboard[x, y] .
  • Board movePiece(Player player, Move move): Сделайте ход на доске, чтобы вы могли видеть, как она будет разыгрываться. Возвращает новую доску.

Если вы хотите увидеть фигуры ваших оппонентов, просто напишите enemy.getPieces(board). Чтобы добавить своего бота в линейку, добавьте следующую строку PlayerFactory:

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

Отладка вашего бота:

Я включил несколько инструментов, чтобы помочь в отладке ваших ботов. Чтобы увидеть игру вживую, вы можете установить Game#DEBUGфлаг в true. Вы получите вывод примерно так:

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

...

(Белый верхний регистр, король показан с i)

Если ваша консоль поддерживает специальные символы utf-8, вы можете даже показать доску с шахматными символами, используя Board#USE_UTF8_TO_STRING:

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

(выглядит лучше с моноширинным шрифтом)

Чтобы предотвратить поток нежелательных выходных данных, вы должны изменить Main#mainфункцию на что-то вроде этого:

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

Поместите своего бота слева, чтобы он играл как белый, а справа - чтобы он играл как черный.

Сборка контроллера:

Контроллер написан на groovy, поэтому у вас должны быть установлены java и groovy. Если вы не хотите устанавливать groovy, вы можете использовать файл сборки gradle, который поставляется с контроллером (это не было проверено). Если вы не хотите использовать groovy или gradle, вы можете использовать последнюю версию jar ( https://github.com/JJ-Atkinson/SimpleAntichessKOTH/releases ). Если вы делаете это, вам нужно создать свой собственный mainметод и добавить своего бота вручную на фабрику игроков. Пример:

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

(Обратите внимание, что вы все еще можете установить флаги отладки и прочее)

Любое обнаружение ошибок приветствуется!

Счет:

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

Пожалуйста, обратите внимание, что я всегда готов получать новые предложения!

Дж Аткин
источник
Если вам нравятся groovy и IntelliJ ... вам стоит взглянуть на Kotlin
TheNumberOne
Я видел Kotlin прежде, но никогда не смотрел на это полностью. Это вроде как scala / groovy mashup (но это нормально - groovy и scala - мои любимые языки;)
J Atkin
Я никогда раньше не использовал scala ... но гораздо проще вызывать код Kotlin из java, чем код goovy из java.
TheNumberOne
1
Вы можете перейти на короля?!? Конечно, нет ...
wizzwizz4
1
@ wizzwizz4 В античесс можно.
ProgramFOX

Ответы:

6

SearchBot

Самый медленный бот на данный момент, но все же быстрее, чем 2 секунды за ход, и он побеждает всех в настоящее время размещенных ботов. Он смотрит на то, что происходит после любого из действительных ходов, и что может произойти после любого хода после этих ходов, и решает, какой будет лучший результат. К сожалению, он не может искать глубже, потому что это займет более 2 секунд.

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
источник
4

SacrificeBot

Этот бот проверит все ходы, которые есть у другого игрока, и проверит, пересекается ли какой-либо из них (то есть часть будет убита). (Это чертовски лучше, чем я ожидал;)

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>
    }
}
Дж Аткин
источник
3

OnePlayBot

Мертвый простой бот с одной игрой. Это улучшится до ладьи.

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

}
Дж Аткин
источник
3

RandomBot

Это обязательный случайный бот. Он всегда будет улучшен до ладьи.

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

}
Дж Аткин
источник
3

MeasureBot

Это бот, с которого я начал; Я работал над его расширением, но потом натолкнулся на глубокую ошибку клонирования, а затем подумал: «Хорошо, давайте просто отправим этого бота, он работает лучше, чем RandomBot и OnePlayBot, и я всегда могу представить нового бота позже» так вот оно:

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 ищет, нужно ли что-то захватить: если нет, просто делает случайный ход. Если это произойдет, он решит, какой кусок взять: он выберет один с меньшим значением, потому что он может захватить меньше своих собственных кусков. И если есть несколько способов взять фрагмент с наименьшим возможным значением, он захватит его с фрагментом с максимально возможным значением: если он это сделает, он приблизит захватывающий фрагмент к другим частям (в начале по крайней мере), и вы бы предпочли потерять более ценный кусок, чем менее ценный.

Это список значений частей, которые я использовал:

  • Король: 1
  • Пешка: 1,5
  • Рыцарь: 2.5
  • Епископ: 3
  • Ладья: 5
  • Королева: 9

Когда пешка продвигается, она всегда продвигается к королю, потому что это самая дешевая фигура.

ProgramFOX
источник