Skip to content

Commit

Permalink
Merge pull request #45 from georgemunyoro/square-safety-checking
Browse files Browse the repository at this point in the history
Square safety checking
  • Loading branch information
georgemunyoro authored Nov 1, 2020
2 parents be0e66e + 713f45a commit 471b1cc
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 61 deletions.
154 changes: 136 additions & 18 deletions skaak/board.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from itertools import count
from typing import List

import skaak.chess as chess


Expand All @@ -15,9 +16,20 @@ def __init__(self, fen: str = chess.STARTING_FEN, **kwargs) -> None:

self.full: int = 0
self.half: int = 0

self.set_fen(fen)

@property
def white_king_position(self) -> int:
return self.board.index("K")

@property
def black_king_position(self) -> int:
return self.board.index("k")

@property
def legal_moves(self) -> List[chess.Move]:
return self.generate_legal_moves()

def __repr__(self) -> str:
ascii_repr: str = ""
for i, char in enumerate(self.board):
Expand Down Expand Up @@ -73,17 +85,117 @@ def undo_move(self) -> None:

self.half -= 1

def get_square_color(self, square: int) -> int:
if self.board[square] == chess.EMPTY:
return None

if self.board[square].isupper():
return chess.WHITE
return chess.BLACK

def is_square_attacked(self, square: int) -> bool:
if square & 0x88 != 0:
return None

if (self.board[square] !=
chess.EMPTY) and self.get_square_color(square) != self.turn:
return None

for direction in chess.MOVES["r"]:
for i in count(square + direction, direction):
if (i & 0x88) != 0:
break
if (self.board[i].isupper() and self.turn == chess.WHITE) or (
self.board[i].islower() and self.turn == chess.BLACK):
break

if self.board[i].lower() in "rq":
return True
elif self.board[i] == chess.EMPTY:
continue
else:
break

for direction in chess.MOVES["b"]:
for i in count(square + direction, direction):
if (i & 0x88) != 0:
break
if (self.board[i].isupper() and self.turn == chess.WHITE or
self.board[i].islower() and self.turn == chess.BLACK):
break

if self.board[i].lower() in "bq":
return True
elif self.board[i] == chess.EMPTY:
continue
else:
break

if self.turn == chess.WHITE:
if (self.board[square + chess.NORTH + chess.EAST] == "p"
or self.board[square + chess.NORTH + chess.WEST] == "p"):
return True
elif self.turn == chess.BLACK:
if (self.board[square + chess.SOUTH + chess.EAST] == "P"
or self.board[square + chess.SOUTH + chess.WEST] == "P"):
return True

non_sliding_pieces = ["k", "n"]
for piece_type in non_sliding_pieces:
for i in chess.MOVES[piece_type]:
if (square + i) & 0x88 == 0:
if (self.board[square + i] == piece_type.lower()
and self.turn == chess.WHITE) or (
self.board[square + i] == piece_type.upper()
and self.turn == chess.BLACK):
return True

return False

def generate_fen(self) -> str:
fen = ""
empty_square_count = 0
for i, j in enumerate(self.board):
if (i & 0x88) != 0:
if empty_square_count > 0:
fen += str(empty_square_count)
empty_square_count = 0
continue
if i % 8 == 0 and i != 0:
fen += "/"
if j in chess.PIECES:
if empty_square_count > 0:
fen += str(empty_square_count)
empty_square_count = 0
fen += j
elif j == chess.EMPTY:
empty_square_count += 1

return (
fen +
f' {"wb"[self.turn]} {self.castling} {self.en_pas} {self.half} {self.full}'
)

def generate_legal_moves(self) -> List[chess.Move]:
for move in self.generate_pseudo_moves():
self.move(move)
if not self.in_check():
yield (move)
self.undo_move()

def generate_pseudo_moves(self) -> List[chess.Move]:
for square, piece in enumerate(self.board):

if (square & 0x88) != 0:
continue

if self.turn == chess.WHITE and not piece.isupper():
continue
elif self.turn == chess.BLACK and not piece.islower():
continue

for direction in chess.MOVES[piece.lower() if piece != "P" else piece]:
for direction in chess.MOVES[piece.lower(
) if piece != "P" else piece]:
for j in count(square + direction, direction):

# Check if the square is offboard
Expand All @@ -97,44 +209,49 @@ def generate_pseudo_moves(self) -> List[chess.Move]:
break

if piece.lower() == "p":
if (
self.turn == chess.WHITE
and direction == (chess.NORTH * 2)
and square // 16 != 6
) or (
self.turn == chess.BLACK
and direction == (chess.SOUTH * 2)
and square // 16 != 1
):
if (self.turn == chess.WHITE
and direction == (chess.NORTH * 2) and
(square // 16 != 6 or
self.board[square + chess.NORTH] != chess.EMPTY)
) or (self.turn == chess.BLACK
and direction == (chess.SOUTH * 2) and
(square // 16 != 1
or self.board[square + chess.SOUTH] !=
chess.EMPTY)):
break
if (j % 16 != square % 16) and self.board[j] in "-.":
break
if (j % 16 == square % 16) and self.board[j] not in "-.":
if (j % 16 == square %
16) and self.board[j] not in "-.":
break
if (j // 8 - square // 8) ** 1 == 1 and self.board[
j - ((j // 8 - square // 8) * 8)
] != "":
if (j // 8 - square // 8)**1 == 1 and self.board[j - (
(j // 8 - square // 8) * 8)] != "":
break

yield chess.Move(
yield (chess.Move(
initial_square=square,
target_square=j,
moving_piece=self.board[square],
attacked_piece=self.board[j],
capture=(self.board[j] not in "-."),
score=0,
)
))

if self.board[j] not in "-." or piece.lower() in "knp":
break

def in_check(self) -> bool:
return (self.is_square_attacked(self.white_king_position)
if self.turn == chess.WHITE else self.is_square_attacked(
self.black_king_position))

def perft(self, depth: int) -> int:
nodes = 0

if depth == 0:
return 1

for move in self.generate_pseudo_moves():
for move in self.generate_legal_moves():
self.move(move)
nodes += self.perft(depth - 1)
self.undo_move()
Expand All @@ -158,6 +275,7 @@ def set_fen(self, fen: str) -> None:
self.turn = chess.WHITE if turn == "w" else chess.BLACK

self.castling = castling
self.en_pas = en_pas
self.half = int(half)
self.full = int(full)

Expand Down
20 changes: 14 additions & 6 deletions skaak/chess.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from collections import namedtuple

STARTING_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"

WHITE = 0
Expand All @@ -9,9 +7,19 @@

NORTH, EAST, SOUTH, WEST = -16, 1, 16, -1

Move = namedtuple(
"Move", "initial_square target_square moving_piece attacked_piece capture score"
)
PIECES = "rnbqkpRNBQKP"


class Move:
def __init__(self, **kwargs):
self.initial_square: int = kwargs.pop("initial_square", None)
self.target_square: int = kwargs.pop("target_square", None)
self.moving_piece: int = kwargs.pop("moving_piece", None)
self.attacked_piece: int = kwargs.pop("attacked_piece", None)
self.capture: bool = kwargs.pop("capture", None)
self.score: int = kwargs.pop("score", None)
self.pseudo: bool = kwargs.pop("pseudo", None)


RANKS = "abcdefgh"

Expand All @@ -35,7 +43,7 @@
SOUTH + SOUTH + EAST,
NORTH + NORTH + WEST,
SOUTH + EAST + EAST,
EAST + EAST + SOUTH,
WEST + SOUTH + SOUTH,
WEST + WEST + SOUTH,
),
"k": (
Expand Down
Loading

0 comments on commit 471b1cc

Please # to comment.