Antichess를하십시오! 있다면 그것이 그가 할 수있는 유일한 움직임입니다.

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

이것은 기본적으로 체스 토너먼트입니다 .

Antichess는 발명 된 많은 체스 변형 중 하나입니다 . 목표는 모든 조각을 잃는 것입니다 (조금 이상하게 보일 수 있지만 이유를 위해 antichess라고합니다).

규칙

Antichess의 규칙은 표준 체스와 매우 유사하지만 약간의 차이가 있습니다. 위에서 언급 한 목표는 모든 조각을 잃는 것입니다. 이를 위해 상대방이 당신의 작품 중 하나를 포착 할 기회가 있다면 그것이 그가 할 수있는 유일한 움직임입니다. 한 차례에 여러 번 기회를 주면 다른 플레이어가 자신의 차례를 선택할 수 있습니다. 또 다른 변화는 왕이 특별한 힘을 가지고 있지 않다는 것입니다. 상대를 검열 할 수없고 그를 강제로 강요 할 수 없기 때문입니다.

표준 게임에 대한 다음 변경 사항도 적용됩니다 (게임 단순화에 도움이 됨).

  • 참가자 는 무시됩니다.
  • 캐슬 링 은 불가능합니다.
  • 51 이동 규칙은 자동으로 (무승부로 경기 종료를 의미 함)에 적용됩니다.
  • 폰은 프로모션 대상을 선택할 수 있습니다.
  • 이동하는 데 2 ​​초 이상이 걸리면 게임에서 패배합니다.
  • 유효하지 않은 이동을 반환하면 게임이 중단됩니다.
  • 승리하려면 상대방이 모든 조각을 포착해야합니다 .
  • 화이트가 게임을 시작합니다.
  • 흰색은 필드의 “맨 아래”에 배치되고 (y = 0), 검은 색은 상단에 있습니다 (y = 7).
  • 봇 이외의 다른 리소스 (인터넷, 파일, 다른 봇 등)에 액세스하는 것은 금지되어 있습니다.

채점

  • 이기면 3 점, 무승부 1 점, 0 점을 잃습니다.
  • 각 제출물은 서로 다른 제출물에 대해 10 회 (흰색은 5 배, 검은 색은 5 배) 재생됩니다.

봇 작성

컨트롤러 코드는 다음과 같습니다. https://github.com/JJ-Atkinson/SimpleAntichessKOTH

봇은 Java 또는 Groovy로 작성할 수 있습니다. 봇을 작성하려면 Player클래스 를 확장해야합니다 . 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

항상 새로운 제출물을 기꺼이 받겠습니다.



답변

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

답변

희생 봇

이 봇은 다른 플레이어가 가지고있는 모든 동작을 확인하고 그 중 하나가 교차하는지 확인합니다 (예 : 조각이 죽을 것). (이것은 내가 예상했던 것보다 훨씬 나아졌습니다.)

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

답변

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

}

답변

랜덤 봇

이것은 필수 임의 봇입니다. 항상 루크로 업그레이드됩니다.

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

}

답변

측정 봇

이것은 내가 시작한 봇입니다. 확장을 위해 노력하고 있었지만 딥 클론 버그를 발견 한 후, “이 봇을 이미 제출해 봅시다. 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

폰이 승격 될 때, 그것은 가장 가치가 낮은 부분이기 때문에 항상 왕에게 승격됩니다.