GithubHelp home page GithubHelp logo

Three Check Chess variant about sunfish HOT 2 CLOSED

purvi1508 avatar purvi1508 commented on May 29, 2024
Three Check Chess variant

from sunfish.

Comments (2)

thomasahle avatar thomasahle commented on May 29, 2024

Yes, you could probably do that!
Just add a tracker counting the number of checks given so far to the Board object.

from sunfish.

purvi1508 avatar purvi1508 commented on May 29, 2024

I have did the necessary changes but faced an error, could you suggest to me where I am going wrong?

import chess
import chess.engine
import chess.variant

import time, math
from itertools import count
from collections import namedtuple, defaultdict

If we could rely on the env -S argument, we could just use "pypy3 -u"

as the shebang to unbuffer stdout. But alas we have to do this instead:

from functools import partial
print = partial(print, flush=True)

version = "___"

piece = {"P": 100, "N": 280, "B": 320, "R": 479, "Q": 929, "K": 60000}
pst = {
'P': ( 0, 0, 0, 0, 0, 0, 0, 0,
78, 83, 86, 73, 102, 82, 85, 90,
7, 29, 21, 44, 40, 31, 44, 7,
-17, 16, -2, 15, 14, 0, 15, -13,
-26, 3, 10, 9, 6, 1, 0, -23,
-22, 9, 5, -11, -10, -2, 3, -19,
-31, 8, -7, -37, -36, -14, 3, -31,
0, 0, 0, 0, 0, 0, 0, 0),
'N': ( -66, -53, -75, -75, -10, -55, -58, -70,
-3, -6, 100, -36, 4, 62, -4, -14,
10, 67, 1, 74, 73, 27, 62, -2,
24, 24, 45, 37, 33, 41, 25, 17,
-1, 5, 31, 21, 22, 35, 2, 0,
-18, 10, 13, 22, 18, 15, 11, -14,
-23, -15, 2, 0, 2, 0, -23, -20,
-74, -23, -26, -24, -19, -35, -22, -69),
'B': ( -59, -78, -82, -76, -23,-107, -37, -50,
-11, 20, 35, -42, -39, 31, 2, -22,
-9, 39, -32, 41, 52, -10, 28, -14,
25, 17, 20, 34, 26, 25, 15, 10,
13, 10, 17, 23, 17, 16, 0, 7,
14, 25, 24, 15, 8, 25, 20, 15,
19, 20, 11, 6, 7, 6, 20, 16,
-7, 2, -15, -12, -14, -15, -10, -10),
'R': ( 35, 29, 33, 4, 37, 33, 56, 50,
55, 29, 56, 67, 55, 62, 34, 60,
19, 35, 28, 33, 45, 27, 25, 15,
0, 5, 16, 13, 18, -4, -9, -6,
-28, -35, -16, -21, -13, -29, -46, -30,
-42, -28, -42, -25, -25, -35, -26, -46,
-53, -38, -31, -26, -29, -43, -44, -53,
-30, -24, -18, 5, -2, -18, -31, -32),
'Q': ( 6, 1, -8,-104, 69, 24, 88, 26,
14, 32, 60, -10, 20, 76, 57, 24,
-2, 43, 32, 60, 72, 63, 43, 2,
1, -16, 22, 17, 25, 20, -13, -6,
-14, -15, -2, -5, -1, -10, -20, -22,
-30, -6, -13, -11, -16, -11, -16, -27,
-36, -18, 0, -19, -15, -15, -21, -38,
-39, -30, -31, -13, -31, -36, -34, -42),
'K': ( 4, 54, 47, -99, -99, 60, 83, -62,
-32, 10, 55, 56, 56, 55, 10, 3,
-62, 12, -57, 44, -67, 28, 37, -31,
-55, 50, 11, -4, -19, 13, 0, -49,
-55, -43, -52, -28, -51, -47, -8, -50,
-47, -42, -43, -79, -64, -32, -29, -32,
-4, 3, -14, -50, -57, -18, 13, 4,
17, 30, -3, -14, 6, -1, 40, 18),
}

Pad tables and join piece and pst dictionaries

for k, table in pst.items():
padrow = lambda row: (0,) + tuple(x + piece[k] for x in row) + (0,)
pst[k] = sum((padrow(table[i * 8 : i * 8 + 8]) for i in range(8)), ())
pst[k] = (0,) * 20 + pst[k] + (0,) * 20

Lists of possible moves for each piece type.

N, E, S, W = -10, 1, 10, -1
directions = {
"P": (N, N+W, N+E),
"N": (N+N+E, E+N+E, E+S+E, S+S+E, S+S+W, W+S+W, W+N+W, N+N+W),
"B": (N+E, S+E, S+W, N+W),
"R": (N, E, S, W),
"Q": (N, E, S, W, N+E, S+E, S+W, N+W),
"K": (N, E, S, W, N+E, S+E, S+W, N+W)
}

Mate value must be greater than 8queen + 2(rook+knight+bishop)

King value is set to twice this value such that if the opponent is

8 queens up, but we got the king, we still exceed MATE_VALUE.

When a MATE is detected, we'll set the score to MATE_UPPER - plies to get there

E.g. Mate in 3 will be MATE_UPPER - 6

MATE_LOWER = piece["K"] - 10 * piece["Q"]
MATE_UPPER = piece["K"] + 10 * piece["Q"]

Constants for tuning search

QS = 35
EVAL_ROUGHNESS = 15

minifier-hide start

opt_ranges = dict(
QS = (0, 300),
EVAL_ROUGHNESS = (0, 50),
)

minifier-hide end

import sys

INF = sys.maxsize

import chess
import chess.variant
from collections import namedtuple
from itertools import count
board = chess.variant.ThreeCheckBoard()
Move = namedtuple("Move", "i j prom")

class Position(namedtuple("Position", "board score kp checks1 checks2")):
"""A state of a chess game
board -- a ThreeCheckBoard representation of the board
score -- the board evaluation
kp - the king passant square
checks1 - number of checks inflicted by player 1
checks2 - number of checks inflicted by player 2
"""

def is_check(self, move):
    i, j, prom = move
    p, q = self.board.piece_at(i), self.board.piece_at(j)
    # Copy variables and reset kp and checks
    board = self.board.copy()
    kp = None
    checks1, checks2 = self.checks1, self.checks2
    # Simulate the move
    board.remove_piece_at(i)
    board.set_piece_at(j, p)
    # Check if the move gives a check
    if q and q.piece_type == chess.KING:
        if p.piece_type == chess.PAWN:
            checks1 += 1
            if checks1 == 3:
                self.score = -chess.INFINITY
        else:
            checks2 += 1
            if checks2 == 3:
                self.score = chess.INFINITY
        return True
    return False

def is_game_over(self):
    # Check if repeated 3 position
    if self.board.is_repetition(3):
        return True, "Draw"
    # Check for three-check
    if self.is_three_check():
        return True, "Three-check"
    else:
        return False, None

def gen_moves(self):

    self.history.append(self.board.fen())
    for i, p in self.board.piece_map().items():
        if p.color != self.board.turn:
            continue
        for d in p.piece_type.directions:
            for j in count(i + d, d):
                q = self.board.piece_at(j)
                # Stay inside the board, and off friendly pieces
                if q is None or q.color == p.color:
                    break
                # Pawn move, double move and capture
                if p.piece_type == chess.PAWN:
                    if d in (chess.WEST, chess.EAST) and q:
                        break
                    if (
                        d in (chess.WEST + chess.NORTH, chess.EAST + chess.NORTH)
                        and not q
                        and j not in (self.board.ep_square, self.board.ep_square - 1, self.board.ep_square + 1)
                        and j !=  abs(j - self.board.ep_square) >= 2
                    ):
                        break

                    if d in (chess.WEST + chess.NORTH, chess.EAST + chess.NORTH) and not q:
                        if j not in (self.board.ep_square, self.board.ep_square - 1, self.board.ep_square + 1) or j != self.board.ep_square and abs(j - self.board.ep_square) >= 2:
                            break
                        else:
                            yield chess.Move(i, j)
                    if d == chess.NORTH and not q:
                        if j + chess.NORTH * self.board.turn in self.board.occupied_co[chess.WHITE if self.board.turn == chess.BLACK else chess.BLACK]:
                            break
                        elif j + chess.NORTH * self.board.turn not in self.board.occupied:
                            if j < 16 or j >= 48:
                                 for promotion in (chess.QUEEN, chess.ROOK, chess.BISHOP, chess.KNIGHT):
                                      yield chess.Move(i, j, promotion=promotion)
                            else:
                                  yield chess.Move(i, j)
            # Other pieces    
                else:
                    if j in chess.SQUARES:
                        if q is None:
                            yield chess.Move(i, j)
                        elif q.color != p.color:
                            yield chess.Move(i, j)
                        break
                    else:
                        break
def move(self, move):
    i, j, prom = move.i, move.j, move.prom
    p = self.board.piece_at(i)
    # Copy variables and reset kp and checks
    board = self.board.copy()
    kp = None
    checks1, checks2 = self.checks1, self.checks2
    # Simulate the move
    board.remove_piece_at(i)
    if prom:
        board.set_piece_at(j, chess.Piece(prom, p.color))
    else:
        board.set_piece_at(j, p)
    if p.piece_type == chess.KING:
        if abs(i-j) == 2:
            kp = chess.square((i+j)//2, j if j<i else i)
    elif p.piece_type == chess.PAWN and abs(i-j) == 16:
        kp = i + (j-i)//2
    # Count number of checks
    checks1, checks2 = 0, 0
    opp_king = chess.KING | (chess.BLACK if p.color == chess.WHITE else chess.WHITE)
    for k in board.pieces(opp_king, p.color):
        for l in board.attacks_mask(k) & board.occupied_co[p.color]:
            checks2 += 1 if board.gives_check(chess.Move(l, k)) else 0
    opp_king = chess.KING | (chess.WHITE if p.color == chess.WHITE else chess.BLACK)
    for k in board.pieces(opp_king, not p.color):
        for l in board.attacks_mask(k) & board.occupied_co[not p.color]:
            checks1 += 1 if board.gives_check(chess.Move(l, k)) else 0
    return Position(board, self.score, kp, checks1, checks2)
  
def is_three_check(self):
    return self.checks1 >= 3 or self.checks2 >= 3

def evaluate(self):
    # Check for checkmate
    if self.board.is_checkmate():
        return -chess.INFINITY if self.board.turn == chess.WHITE else chess.INFINITY
    # Check for stalemate
    if self.board.is_stalemate() or self.board.is_insufficient_material():
        return 0
    # Evaluate based on material and positional advantage
    score = 0
    for i in range(64):
        p = self.board.piece_at(i)
        if p is None:
            continue
        score += (chess.piece_value[p.piece_type] if p.color == chess.WHITE else -chess.piece_value[p.piece_type])
        if p.color == chess.WHITE:
            if p.piece_type == chess.PAWN and i+8 in self.board.occupied:
                score -= 5
            if p.piece_type == chess.KING and i in (chess.C1, chess.C8, chess.G1, chess.G8):
                score -= 25
        else:
            if p.piece_type == chess.PAWN and i-8 in self.board.occupied:
                score += 5
            if p.piece_type == chess.KING and i in (chess.C1, chess.C8, chess.G1, chess.G8):
                score += 25
    return score

def value(self, move):
    i, j, prom = move.from_square, move.to_square, move.promotion
    p = self.piece_type_at(i)
    q = self.piece_type_at(j)

    # Actual move
    score = pst[p][j] - pst[p][i]

    # Capture
    if q is not None and q != chess.PAWN:
       score += pst[q][j]
    
     # Special pawn stuff
    if p == chess.PAWN:
       if chess.square_rank(j) in [0, 7]:
          score += pst[prom][j] - pst[chess.PAWN][j]

    return score

lower <= s(pos) <= upper

Entry = namedtuple("Entry", "lower upper", defaults=(-MATE_UPPER, MATE_UPPER))

class Searcher:
def init(self):
self.tp_score = defaultdict(Entry)
self.tp_move = {}
self.history = set()
self.nodes = 0

def bound(self, pos, gamma, depth, can_null=True):
    """ Let s* be the "true" score of the sub-tree we are searching.
        The method returns r, where
        if gamma >  s* then s* <= r < gamma  (A better upper bound)
        if gamma <= s* then gamma <= r <= s* (A better lower bound) """
    self.nodes += 1

    depth = max(depth, 0)
    if pos.score <= -MATE_LOWER:
        return -MATE_UPPER

    entry = self.tp_score[pos, depth, can_null]
    if entry.lower >= gamma: return entry.lower
    if entry.upper < gamma: return entry.upper

    if can_null and depth > 0 and pos in self.history:
        return 0

    # Generator of moves to search in order.
    # This allows us to define the moves, but only calculate them if needed.
    def moves():
        # First try not moving at all. We only do this if there is at least one major
        # piece left on the board, since otherwise zugzwangs are too dangerous.
        if depth > 2 and can_null and any(c in pos.board for c in "RBNQ"):
            yield None, -self.bound(pos.rotate(nullmove=True), 1 - gamma, depth - 3)

        # For QSearch we have a different kind of null-move, namely we can just stop
        # and not capture anything else.
        if depth == 0:
            yield None, pos.score

        # Look for the strongest ove from last time, the hash-move.
        killer = self.tp_move.get(pos)

        # If there isn't one, try to find one with a more shallow search.

        if not killer and depth > 2:
            self.bound(pos, gamma, depth - 3, can_null=False)
            killer = self.tp_move.get(pos)

        # If depth == 0 we only try moves with high intrinsic score (captures and
        # promotions). Otherwise we do all moves. This is called quiescent search.
        val_lower = QS if depth == 0 else -MATE_LOWER

        # Only play the move if it would be included at the current val-limit,
        # since otherwise we'd get search instability.
        # We will search it again in the main loop below, but the tp will fix
        # things for us.
        if killer and pos.value(killer) >= val_lower:
            yield killer, -self.bound(pos.move(killer), 1 - gamma, depth - 1)

        # Then all the other moves
        for val, move in sorted(((pos.value(m), m) for m in pos.gen_moves()), reverse=True):
            # Quiescent search
            if val < val_lower:
                break

            if depth <= 1 and pos.score + val < gamma:
                # Need special case for MATE, since it would normally be caught
                # before standing pat.
                yield move, pos.score + val if val < MATE_LOWER else MATE_UPPER
                # We can also break, since we have ordered the moves by value,
                # so it can't get any better than this.
                break

            yield move, -self.bound(pos.move(move), 1 - gamma, depth - 1)

    # Run through the moves, shortcutting when possible
    best = -MATE_UPPER
    for move, score in moves():
        best = max(best, score)
        if best >= gamma:
            # Save the move for pv construction and killer heuristic
            if move is not None:
                self.tp_move[pos] = move
            break


    if depth > 0 and best == -MATE_UPPER:
        flipped = pos.rotate(nullmove=True)
        # Hopefully this is already in the TT because of null-move
        in_check = self.bound(flipped, MATE_UPPER, 0) == MATE_UPPER
        best = -MATE_LOWER if in_check else 0

    # Table part 2
    if best >= gamma:
        self.tp_score[pos, depth, can_null] = Entry(best, entry.upper)
    if best < gamma:
        self.tp_score[pos, depth, can_null] = Entry(entry.lower, best)

    return best

def search(self, history):
    """Iterative deepening MTD-bi search"""
    self.nodes = 0
    self.history = set(history)
    self.tp_score.clear()

    gamma = 0
    # In finished games, we could potentially go far enough to cause a recursion
    # limit exception. Hence we bound the ply. We also can't start at 0, since
    # that's quiscent search, and we don't always play legal moves there.
    for depth in range(1, 1000):
        # The inner loop is a binary search on the score of the position.
        # Inv: lower <= score <= upper
        # 'while lower != upper' would work, but it's too much effort to spend
        # on what's probably not going to change the move played.
        lower, upper = -MATE_LOWER, MATE_LOWER
        while lower < upper - EVAL_ROUGHNESS:
            score = self.bound(history[-1], gamma, depth, can_null=False)
            if score >= gamma:
                lower = score
            if score < gamma:
                upper = score
            yield depth, gamma, score, self.tp_move.get(history[-1])
            gamma = (lower + upper + 1) // 2

A1, H1, A8, H8 = 91, 98, 21, 28

Our board is represented as a 120 character string. The padding allows for

fast detection of moves that don't stay within the board.

A1, H1, A8, H8 = 91, 98, 21, 28
initial = (
" \n" # 0 - 9
" \n" # 10 - 19
" rnbqkbnr\n" # 20 - 29
" pppppppp\n" # 30 - 39
" ........\n" # 40 - 49
" ........\n" # 50 - 59
" ........\n" # 60 - 69
" ........\n" # 70 - 79
" PPPPPPPP\n" # 80 - 89
" RNBQKBNR\n" # 90 - 99
" \n" # 100 -109
" \n" # 110 -119
)

def parse(c):
fil, rank = ord(c[0]) - ord("a"), int(c[1]) - 1
return A1 + fil - 10 * rank

def render(i):
rank, fil = divmod(i - A1, 10)
return chr(fil + ord("a")) + str(-rank + 1)

hist = [Position(initial, 0, 0,0,0)]

searcher = Searcher()
while True:
args = input().split()
if args[0] == "uci":
print(f"id name {version}")
print("uciok")

elif args[0] == "isready":
    print("readyok")

elif args[0] == "quit":
    break

elif args[:2] == ["position", "startpos"]:
    del hist[1:]
    for ply, move in enumerate(args[3:]):
        i, j, prom = parse(move[:2]), parse(move[2:4]), move[4:].upper()
        if ply % 2 == 1:
            i, j = 119 - i, 119 - j
        hist.append(hist[-1].move(Move(i, j, prom)))

elif args[0] == "go":
    wtime, btime, winc, binc = [int(a) / 1000 for a in args[2::2]]
    if len(hist) % 2 == 0:
        wtime, winc = btime, binc
    think = min(wtime / 40 + winc, wtime / 2 - 1)

    start = time.time()
    move_str = None
    for depth, gamma, score, move in Searcher().search(hist):
        # The only way we can be sure to have the real move in tp_move,
        # is if we have just failed high.
        if score >= gamma:
            i, j = move.i, move.j
            if len(hist) % 2 == 0:
                i, j = 119 - i, 119 - j
            move_str = render(i) + render(j) + move.prom.lower()
            print(f"info depth {depth} score cp {score} pv {move_str}")
        if move_str and time.time() - start > think * 0.8:
            break

    print("bestmove", move_str or '(none)')

from sunfish.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.