Skip to content

Commit

Permalink
Merge pull request #168 from madcpf/fly
Browse files Browse the repository at this point in the history
[Quantum Chinese Chess] Update flying general check
  • Loading branch information
madcpf authored Nov 29, 2023
2 parents 32d68be + 01c3ab7 commit eef96ad
Showing 4 changed files with 135 additions and 66 deletions.
28 changes: 27 additions & 1 deletion unitary/examples/quantum_chinese_chess/board.py
Original file line number Diff line number Diff line change
@@ -19,8 +19,10 @@
Color,
Type,
Language,
MoveVariant,
)
from unitary.examples.quantum_chinese_chess.piece import Piece
from unitary.examples.quantum_chinese_chess.move import Jump


# The default initial state of the game.
@@ -183,7 +185,31 @@ def flying_general_check(self) -> bool:
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.
# When there are only quantum pieces in between, the check successes (and game ends)
# if all of those pieces turn out to be empty.
capture_ancilla = self.board._add_ancilla("flying_general_check")
control_objects = [self.board[path] for path in quantum_pieces]
conditions = [0] * len(control_objects)
alpha.quantum_if(*control_objects).equals(*conditions).apply(alpha.Flip())(
capture_ancilla
)
could_capture = self.board.pop([capture_ancilla])[0]
if could_capture:
# Force measure all path pieces to be empty.
for path_piece in control_objects:
self.board.force_measurement(path_piece, 0)
path_piece.reset()

# Let the general/king fly, i.e. the opposite king will capture the current king.
current_king = self.board[self.king_locations[self.current_player]]
oppsite_king = self.board[self.king_locations[1 - self.current_player]]
Jump(MoveVariant.CLASSICAL)(oppsite_king, current_king)
print("==== FLYING GENERAL ! ====")
return True
else:
# Note: we are leaving the path pieces unchanged in entangled state.
print("==== General not flies yet ! ====")
return False

def sample(self, repetitions: int) -> List[int]:
"""Sample the current board by the given `repetitions`.
60 changes: 59 additions & 1 deletion unitary/examples/quantum_chinese_chess/board_test.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,14 @@
)
from unitary.examples.quantum_chinese_chess.board import Board
from unitary.examples.quantum_chinese_chess.piece import Piece
from unitary.examples.quantum_chinese_chess.test_utils import (
locations_to_bitboard,
assert_samples_in,
assert_sample_distribution,
get_board_probability_distribution,
set_board,
)
from unitary import alpha


def test_init_with_default_fen():
@@ -141,7 +149,7 @@ def test_path_pieces():
assert board.path_pieces("c8", "d6") == ([], ["c7"])


def test_flying_general_check():
def test_flying_general_check_classical_cases():
board = Board.from_fen()
# If they are in different columns, the check fails.
board.king_locations = ["d0", "e9"]
@@ -155,3 +163,53 @@ def test_flying_general_check():
board.board["e3"].reset()
board.board["e6"].reset()
assert board.flying_general_check() == True


def test_flying_general_check_quantum_cases():
# When there are quantum pieces in between.
board = set_board(["a3", "a4", "e0", "e9"])
board.king_locations = ["e0", "e9"]
board.current_player = 0 # i.e. RED
world = board.board
alpha.PhasedSplit()(world["a3"], world["c3"], world["e3"])
world["e3"].is_entangled = True
alpha.PhasedSplit()(world["a4"], world["c4"], world["e4"])
world["e4"].is_entangled = True

result = board.flying_general_check()
# We check the ancilla to learn whether the general/king flies or not.
captured = world.post_selection[world["ancilla_ancilla_flying_general_check_0_0"]]
if captured:
assert result
assert_samples_in(board, {locations_to_bitboard(["c3", "c4", "e0"]): 1.0})
else:
assert not result
assert_sample_distribution(
board,
{
locations_to_bitboard(["e0", "e9", "e3", "e4"]): 1.0 / 3,
locations_to_bitboard(["e0", "e9", "e3", "c4"]): 1.0 / 3,
locations_to_bitboard(["e0", "e9", "c3", "e4"]): 1.0 / 3,
},
)


def test_flying_general_check_quantum_cases_not_captured():
# When there are quantum pieces in between, but the capture cannot happen.
board = set_board(["a3", "e0", "e9"])
board.king_locations = ["e0", "e9"]
board.current_player = 0 # i.e. RED
world = board.board
alpha.PhasedSplit()(world["a3"], world["e3"], world["e4"])
world["e3"].is_entangled = True
world["e4"].is_entangled = True

result = board.flying_general_check()
assert not result
assert_sample_distribution(
board,
{
locations_to_bitboard(["e0", "e9", "e3"]): 1.0 / 2,
locations_to_bitboard(["e0", "e9", "e4"]): 1.0 / 2,
},
)
46 changes: 13 additions & 33 deletions unitary/examples/quantum_chinese_chess/move.py
Original file line number Diff line number Diff line change
@@ -15,23 +15,21 @@
import cirq
from unitary import alpha
from unitary.alpha.quantum_effect import QuantumEffect
from unitary.examples.quantum_chinese_chess.board import Board
from unitary.examples.quantum_chinese_chess.piece import Piece
from unitary.examples.quantum_chinese_chess.enums import MoveType, MoveVariant, Type


# TODO(): now the class is no longer the base class of all chess moves. Maybe convert this class
# to a helper class to save each move (with its pop results) in a string form into move history.
class Move(QuantumEffect):
class Move:
"""The base class of all chess moves."""

def __init__(
self,
source: str,
target: str,
board: Board,
source2: Optional[str] = None,
target2: Optional[str] = None,
source: Piece,
target: Piece,
source2: Optional[Piece] = None,
target2: Optional[Piece] = None,
move_type: Optional[MoveType] = None,
move_variant: Optional[MoveVariant] = None,
):
@@ -41,28 +39,12 @@ def __init__(
self.target2 = target2
self.move_type = move_type
self.move_variant = move_variant
self.board = board

def __eq__(self, other):
if isinstance(other, Move):
return (
self.source == other.source
and self.source2 == other.source2
and self.target == other.target
and self.target2 == other.target2
and self.move_type == other.move_type
and self.move_variant == other.move_variant
)
return self.to_str(3) == other.to_str(3)
return False

def _verify_objects(self, *objects):
# TODO(): add checks that apply to all move types
return

def effect(self, *objects):
# TODO(): add effects according to move_type and move_variant
return

def is_split_move(self) -> bool:
return self.target2 is not None

@@ -80,27 +62,25 @@ def to_str(self, verbose_level: int = 1) -> str:
return ""

if self.is_split_move():
move_str = [self.source + "^" + self.target + str(self.target2)]
move_str = [self.source.name + "^" + self.target.name + self.target2.name]
elif self.is_merge_move():
move_str = [self.source + str(self.source2) + "^" + self.target]
move_str = [self.source.name + self.source2.name + "^" + self.target.name]
else:
move_str = [self.source + self.target]
move_str = [self.source.name + self.target.name]

if verbose_level > 1:
move_str.append(self.move_type.name)
move_str.append(self.move_variant.name)

if verbose_level > 2:
source = self.board.board[self.source]
target = self.board.board[self.target]
move_str.append(
source.color.name
self.source.color.name
+ "_"
+ source.type_.name
+ self.source.type_.name
+ "->"
+ target.color.name
+ self.target.color.name
+ "_"
+ target.type_.name
+ self.target.type_.name
)
return ":".join(move_str)

67 changes: 36 additions & 31 deletions unitary/examples/quantum_chinese_chess/move_test.py
Original file line number Diff line number Diff line change
@@ -39,30 +39,31 @@

def test_move_eq():
board = Board.from_fen()
world = board.board
move1 = Move(
"a1",
"b2",
board,
"c1",
world["a1"],
world["b2"],
world["c1"],
move_type=MoveType.MERGE_JUMP,
move_variant=MoveVariant.CAPTURE,
)
move2 = Move(
"a1",
"b2",
board,
"c1",
world["a1"],
world["b2"],
world["c1"],
move_type=MoveType.MERGE_JUMP,
move_variant=MoveVariant.CAPTURE,
)
move3 = Move(
"a1", "b2", board, move_type=MoveType.JUMP, move_variant=MoveVariant.CAPTURE
world["a1"],
world["b2"],
move_type=MoveType.JUMP,
move_variant=MoveVariant.CAPTURE,
)
move4 = Move(
"a1",
"b2",
board,
"c1",
world["a1"],
world["b2"],
world["c1"],
move_type=MoveType.MERGE_SLIDE,
move_variant=MoveVariant.CAPTURE,
)
@@ -75,30 +76,32 @@ def test_move_eq():
def test_move_type():
# TODO(): change to real senarios
board = Board.from_fen()
world = board.board
move1 = Move(
"a1",
"b2",
board,
"c1",
world["a1"],
world["b2"],
world["c1"],
move_type=MoveType.MERGE_JUMP,
move_variant=MoveVariant.CAPTURE,
)
assert move1.is_split_move() == False
assert move1.is_merge_move()

move2 = Move(
"a1",
"b2",
board,
target2="c1",
world["a1"],
world["b2"],
target2=world["c1"],
move_type=MoveType.SPLIT_JUMP,
move_variant=MoveVariant.BASIC,
)
assert move2.is_split_move()
assert move2.is_merge_move() == False

move3 = Move(
"a1", "b2", board, move_type=MoveType.SLIDE, move_variant=MoveVariant.CAPTURE
world["a1"],
world["b2"],
move_type=MoveType.SLIDE,
move_variant=MoveVariant.CAPTURE,
)
assert move3.is_split_move() == False
assert move3.is_merge_move() == False
@@ -107,11 +110,11 @@ def test_move_type():
def test_to_str():
# TODO(): change to real scenarios
board = Board.from_fen()
world = board.board
move1 = Move(
"a0",
"a6",
board,
"c1",
world["a0"],
world["a6"],
world["c1"],
move_type=MoveType.MERGE_JUMP,
move_variant=MoveVariant.CAPTURE,
)
@@ -121,10 +124,9 @@ def test_to_str():
assert move1.to_str(3) == "a0c1^a6:MERGE_JUMP:CAPTURE:RED_ROOK->BLACK_PAWN"

move2 = Move(
"a0",
"b3",
board,
target2="c1",
world["a0"],
world["b3"],
target2=world["c1"],
move_type=MoveType.SPLIT_JUMP,
move_variant=MoveVariant.BASIC,
)
@@ -134,7 +136,10 @@ def test_to_str():
assert move2.to_str(3) == "a0^b3c1:SPLIT_JUMP:BASIC:RED_ROOK->NA_EMPTY"

move3 = Move(
"a0", "a6", board, move_type=MoveType.SLIDE, move_variant=MoveVariant.CAPTURE
world["a0"],
world["a6"],
move_type=MoveType.SLIDE,
move_variant=MoveVariant.CAPTURE,
)
assert move3.to_str(0) == ""
assert move3.to_str(1) == "a0a6"

0 comments on commit eef96ad

Please sign in to comment.