Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement classical moves and rule checks #162

Merged
merged 12 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion unitary/examples/quantum_chinese_chess/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List
import numpy as np
from typing import List, Tuple
import unitary.alpha as alpha
from unitary.examples.quantum_chinese_chess.enums import (
SquareState,
Expand All @@ -34,6 +35,7 @@ def __init__(
):
self.board = board
self.current_player = current_player
# This saves the locations of KINGs in the order of [RED_KING_LOCATION, BLACK_KING_LOCATION].
self.king_locations = king_locations
self.lang = Language.EN # The default language is English.

Expand Down Expand Up @@ -76,6 +78,11 @@ def from_fen(cls, fen: str = _INITIAL_FEN) -> "Board":
board = alpha.QuantumWorld(chess_board.values())
# Here 0 means the player RED while 1 the player BLACK.
current_player = 0 if "w" in turns else 1
# TODO(): maybe add check to make sure the input fen itself is correct.
if len(king_locations) != 2:
raise ValueError(
f"We expect two KINGs on the board, but got {len(king_locations)}."
)
return cls(board, current_player, king_locations)

def __str__(self):
Expand Down Expand Up @@ -119,3 +126,65 @@ def __str__(self):
.replace("abcdefghi", " abcdefghi")
.translate(translation)
)

def path_pieces(self, source: str, target: str) -> Tuple[List[str], List[str]]:
"""Returns the nonempty classical and quantum pieces from source to target (excluded)."""
x0 = ord(source[0])
x1 = ord(target[0])
dx = x1 - x0
y0 = int(source[1])
y1 = int(target[1])
dy = y1 - y0
# In case of only moving one step, return empty path pieces.
if abs(dx) + abs(dy) <= 1:
return [], []
# In case of advisor moving, return empty path pieces.
# TODO(): maybe move this to the advisor move check.
if abs(dx) == 1 and abs(dy) == 1:
return [], []
pieces = []
classical_pieces = []
quantum_pieces = []
dx_sign = np.sign(dx)
dy_sign = np.sign(dy)
# In case of elephant move, there should only be one path piece.
if abs(dx) == abs(dy):
pieces.append(f"{chr(x0 + dx_sign)}{y0 + dy_sign}")
# This could be move of rook, king, pawn or cannon.
elif dx == 0:
for i in range(1, abs(dy)):
madcpf marked this conversation as resolved.
Show resolved Hide resolved
pieces.append(f"{chr(x0)}{y0 + dy_sign * i}")
# This could be move of rook, king, pawn or cannon.
elif dy == 0:
for i in range(1, abs(dx)):
pieces.append(f"{chr(x0 + dx_sign * i)}{y0}")
# This covers four possible directions of horse move.
elif abs(dx) == 2 and abs(dy) == 1:
pieces.append(f"{chr(x0 + dx_sign)}{y0}")
# This covers the other four possible directions of horse move.
elif abs(dy) == 2 and abs(dx) == 1:
pieces.append(f"{chr(x0)}{y0 + dy_sign}")
else:
raise ValueError("Unexpected input to path_pieces().")
for piece in pieces:
if self.board[piece].is_entangled:
quantum_pieces.append(piece)
elif self.board[piece].type_ != Type.EMPTY:
classical_pieces.append(piece)
return classical_pieces, quantum_pieces
madcpf marked this conversation as resolved.
Show resolved Hide resolved

def flying_general_check(self) -> bool:
"""Check and return if the two KINGs are directly facing each other (i.e. in the same column) without any pieces in between."""
king_0 = self.king_locations[0]
king_1 = self.king_locations[1]
madcpf marked this conversation as resolved.
Show resolved Hide resolved
if king_0[0] != king_1[0]:
# If they are in different columns, the check fails. Game continues.
return False
classical_pieces, quantum_pieces = self.path_pieces(king_0, king_1)
if len(classical_pieces) > 0:
# If there are classical pieces between two KINGs, the check fails. Game continues.
return False
if len(quantum_pieces) == 0:
# If there are no pieces between two KINGs, the check successes. Game ends.
return True
# TODO(): add check when there are quantum pieces in between.
58 changes: 57 additions & 1 deletion unitary/examples/quantum_chinese_chess/board_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from unitary.examples.quantum_chinese_chess.enums import Language
from unitary.examples.quantum_chinese_chess.enums import (
Language,
Color,
Type,
SquareState,
)
from unitary.examples.quantum_chinese_chess.board import Board
from unitary.examples.quantum_chinese_chess.piece import Piece


def test_init_with_default_fen():
Expand Down Expand Up @@ -99,3 +105,53 @@ def test_init_with_specified_fen():
)

assert board.king_locations == ["e9", "e0"]


def test_path_pieces():
board = Board.from_fen()
# In case of only moving one step, return empty path pieces.
assert board.path_pieces("a0", "a1") == ([], [])

# In case of advisor moving, return empty path pieces.
assert board.path_pieces("d0", "e1") == ([], [])

# In case of elephant move, there should be at most one path piece.
assert board.path_pieces("c0", "e2") == ([], [])
# Add one classical piece in the path.
board.board["d1"].reset(Piece("d1", SquareState.OCCUPIED, Type.ROOK, Color.RED))
assert board.path_pieces("c0", "e2") == (["d1"], [])
# Add one quantum piece in the path.
board.board["d1"].is_entangled = True
assert board.path_pieces("c0", "e2") == ([], ["d1"])

# Horizontal move
board.board["c7"].reset(Piece("c7", SquareState.OCCUPIED, Type.ROOK, Color.RED))
board.board["c7"].is_entangled = True
assert board.path_pieces("a7", "i7") == (["b7", "h7"], ["c7"])

# Vertical move
assert board.path_pieces("c0", "c9") == (["c3", "c6"], ["c7"])

# In case of horse move, there should be at most one path piece.
assert board.path_pieces("b9", "a7") == ([], [])
assert board.path_pieces("b9", "c7") == ([], [])
# One classical piece in path.
assert board.path_pieces("b9", "d8") == (["c9"], [])
# One quantum piece in path.
assert board.path_pieces("c8", "d6") == ([], ["c7"])


def test_flying_general_check():
board = Board.from_fen()
# If they are in different columns, the check fails.
board.king_locations = ["d0", "e9"]
assert board.flying_general_check() == False

# If there are classical pieces between two KINGs, the check fails.
board.king_locations = ["e0", "e9"]
assert board.flying_general_check() == False

# If there are no pieces between two KINGs, the check successes.
board.board["e3"].reset()
board.board["e6"].reset()
assert board.flying_general_check() == True
Loading
Loading