Skip to content

Commit

Permalink
update test
Browse files Browse the repository at this point in the history
  • Loading branch information
madcpf committed Oct 11, 2023
1 parent 720666b commit ee07708
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 57 deletions.
88 changes: 54 additions & 34 deletions unitary/examples/quantum_chinese_chess/chess.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,9 @@ def check_classical_rule(
source_piece = self.board.board[source]
target_piece = self.board.board[target]
# Check if the move is blocked by classical path piece.
if len(classical_path_pieces) > 0:
if source_piece.type_ != Type.CANNON:
# The path is blocked by classical pieces.
raise ValueError("The path is blocked.")
elif len(classical_path_pieces) > 1:
# Invalid cannon move, since there could only be at most one classical piece between
# the source (i.e. the cannon) and the target.
raise ValueError("Cannon cannot fire like this.")
elif source_piece.color == target_piece.color:
raise ValueError("Cannon cannot fire to a piece with same color.")
elif target_piece.color == Color.NA:
raise ValueError("Cannon cannot fire to an empty piece.")
if len(classical_path_pieces) > 0 and source_piece.type_ != Type.CANNON:
# The path is blocked by classical pieces.
raise ValueError("The path is blocked.")

# Check if the target has classical piece of the same color.
if not target_piece.is_entangled and source_piece.color == target_piece.color:
Expand Down Expand Up @@ -190,6 +181,15 @@ def check_classical_rule(
elif source_piece.type_ == Type.CANNON:
if dx != 0 and dy != 0:
raise ValueError("CANNON cannot move like this.")
if len(classical_path_pieces) > 0:
if len(classical_path_pieces) > 1:
# Invalid cannon move, since there could only be at most one classical piece between
# the source (i.e. the cannon) and the target.
raise ValueError("CANNON cannot fire like this.")
elif source_piece.color == target_piece.color:
raise ValueError("CANNON cannot fire to a piece with same color.")
elif target_piece.color == Color.NA:
raise ValueError("CANNON cannot fire to an empty piece.")
elif source_piece.type_ == Type.PAWN:
if abs(dx) + abs(dy) != 1:
raise ValueError("PAWN cannot move like this.")
Expand Down Expand Up @@ -226,51 +226,71 @@ def classify_move(

if len(sources) == 1 and len(targets) == 1:
if len(quantum_path_pieces_0) == 0:
if not source.is_entangled() and not target.is_entangled():
if target.color == source.color:
raise ValueError(
"The target piece is classical with the same color as the source piece."
)
move_type = MoveType.CLASSICAL
if (
len(classical_path_pieces_0) == 0
and source.type_ == Type.CANNON
and target.color.value == 1 - source.color.value
):
raise ValueError(
"CANNON could not fire/capture without a cannon platform."
)
if not source.is_entangled and not target.is_entangled:
return MoveType.CLASSICAL, MoveVariant.UNSPECIFIED
else:
move_type = MoveType.JUMP
else:
move_type = MoveType.SLIDE

if move_type != MoveType.CLASSICAL and len(classical_path_pieces_0) == 1:
# Special checks when source is cannon.
if target.color == source.color:
raise ValueError(
"Cannon cannot capture a piece with the same color."
)
elif target.color == Color.NA:
raise ValueError("Cannon cannot capture an empty piece.")
else:
return MoveType.CANNON_NORMAL_FIRE, MoveVariant.CAPTURE
if (
move_type != MoveType.CLASSICAL
and source.type_ == Type.CANNON
and (
len(classical_path_pieces_0) == 1 or len(quantum_path_pieces_0) > 0
)
):
# By this time the classical cannon fire has been identified as CLASSICAL JUMP.
return MoveType.CANNON_FIRE, MoveVariant.CAPTURE
# Determine MoveVariant.
if target.color == Color.NA:
move_variant = MoveVariant.BASIC
# TODO(): such move could be a merge. Take care of such cases later.
elif target.color == source.color:
move_variant = MoveVariant.EXCLUDED
else:
move_variant = MoveVariant.CAPTURE

elif len(sources) == 2:
source_1 = self.board.board[sources[1]]
if not source.is_entangled or not source_1.is_entangled:
raise ValueError(
"Both sources need to be in quantum state in order to merge."
)
if target.type_ != Type.EMPTY:
# TODO(): Currently we don't support merge + excluded/capture, or cannon_merge_fire + capture. Maybe add support later.
if len(classical_path_pieces_0) > 0 or len(classical_path_pieces_1) > 0:
raise ValueError("Currently CANNON could not merge while fire.")
raise ValueError("Currently we could only merge into an empty piece.")
if len(classical_path_pieces_0) > 0 or len(classical_path_pieces_1) > 0:
raise ValueError("Currently Cannon could not merge while fire.")
if len(quantum_path_pieces_0) == 0 and len(quantum_path_pieces_1) == 0:
move_type = Type.MERGE_JUMP
move_type = MoveType.MERGE_JUMP
else:
move_type = Type.MERGE_SLIDE
move_type = MoveType.MERGE_SLIDE
move_variant = MoveVariant.BASIC

elif len(targets) == 2:
target_1 = self.board.board[targets[1]]
if target.type_ != Type.EMPTY or target_1.type_ != Type.EMPTY:
# TODO(): Currently we don't support split + excluded/capture, or cannon_split_fire + capture. Maybee add support later.
if len(classical_path_pieces_0) > 0 or len(classical_path_pieces_1) > 0:
raise ValueError("Currently CANNON could not split while fire.")
raise ValueError("Currently we could only split into empty pieces.")
if source.type_ == Type.KING:
# TODO(): Currently we don't support KING split. Maybe add support later.
raise ValueError("King split is not supported currently.")
if len(quantum_path_pieces_0) == 0 and len(quantum_path_pieces_1) == 0:
move_type = Type.SPLIT_JUMP
move_type = MoveType.SPLIT_JUMP
else:
move_type = Type.SPLIT_SLIDE
move_type = MoveType.SPLIT_SLIDE
move_variant = MoveVariant.BASIC
return move_type, move_variant

def apply_move(self, str_to_parse: str) -> None:
Expand Down
184 changes: 165 additions & 19 deletions unitary/examples/quantum_chinese_chess/chess_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
Color,
Type,
SquareState,
MoveType,
MoveVariant,
)


Expand Down Expand Up @@ -100,20 +102,6 @@ def test_check_classical_rule(monkeypatch):
with pytest.raises(ValueError, match="The path is blocked."):
game.check_classical_rule("a0", "a4", ["a3"])

# Cannon could jump across exactly one piece.
game.check_classical_rule("b2", "b9", ["b7"])
with pytest.raises(ValueError, match="Cannon cannot fire like this."):
game.check_classical_rule("b2", "b9", ["b5", "b7"])
# Cannon cannot fire to a piece with same color.
game.board.board["b3"].reset(game.board.board["b2"])
game.board.board["b2"].reset()
with pytest.raises(
ValueError, match="Cannon cannot fire to a piece with same color."
):
game.check_classical_rule("b3", "e3", ["c3"])
with pytest.raises(ValueError, match="Cannon cannot fire to an empty piece."):
game.check_classical_rule("b3", "d3", ["c3"])

# Target should not be a classical piece of the same color.
with pytest.raises(
ValueError, match="The target place has classical piece with the same color."
Expand Down Expand Up @@ -168,6 +156,20 @@ def test_check_classical_rule(monkeypatch):
game.check_classical_rule("b7", "b4", [])
with pytest.raises(ValueError, match="CANNON cannot move like this."):
game.check_classical_rule("b7", "a8", [])
# Cannon could jump across exactly one piece.
game.check_classical_rule("b2", "b9", ["b7"])
with pytest.raises(ValueError, match="CANNON cannot fire like this."):
game.check_classical_rule("b2", "b9", ["b5", "b7"])
# Cannon cannot fire to a piece with same color.
game.board.board["b3"].reset(game.board.board["b2"])
game.board.board["b2"].reset()
game.board.board["e3"].is_entangled = True
with pytest.raises(
ValueError, match="CANNON cannot fire to a piece with same color."
):
game.check_classical_rule("b3", "e3", ["c3"])
with pytest.raises(ValueError, match="CANNON cannot fire to an empty piece."):
game.check_classical_rule("b3", "d3", ["c3"])

# PAWN
game.check_classical_rule("a6", "a5", [])
Expand All @@ -192,19 +194,163 @@ def test_check_classical_rule(monkeypatch):
game.check_classical_rule("c4", "d4", [])


def test_classify_move():
# classical basic
# classical excluded
# classical capture
def test_classify_move_fail(monkeypatch):
output = io.StringIO()
sys.stdout = output
inputs = iter(["y", "Bob", "Ben"])
monkeypatch.setattr("builtins.input", lambda _: next(inputs))
game = QuantumChineseChess()
with pytest.raises(
ValueError, match="CANNON could not fire/capture without a cannon platform."
):
game.classify_move(["b7"], ["b2"], [], [], [], [])

with pytest.raises(
ValueError, match="Both sources need to be in quantum state in order to merge."
):
game.classify_move(["b2", "h2"], ["e2"], [], [], [], [])

game.board.board["c0"].reset(game.board.board["b7"])
game.board.board["c0"].is_entangled = True
game.board.board["b7"].reset()
game.board.board["g0"].reset(game.board.board["h7"])
game.board.board["g0"].is_entangled = True
game.board.board["h7"].reset()
with pytest.raises(
ValueError, match="Currently CANNON could not merge while fire."
):
game.classify_move(["c0", "g0"], ["e0"], ["d0"], [], ["f0"], [])

game.board.board["b3"].reset(game.board.board["b2"])
game.board.board["b3"].is_entangled = True
game.board.board["b2"].reset()
game.board.board["d3"].reset(game.board.board["h2"])
game.board.board["d3"].is_entangled = True
game.board.board["h2"].reset()
with pytest.raises(
ValueError, match="Currently we could only merge into an empty piece."
):
game.classify_move(["b3", "d3"], ["c3"], [], [], [], [])

with pytest.raises(
ValueError, match="Currently CANNON could not split while fire."
):
game.classify_move(["g0"], ["e0", "i0"], ["f0"], [], ["h0"], [])

game.board.board["d0"].is_entangled = True
with pytest.raises(
ValueError, match="Currently we could only split into empty pieces."
):
game.classify_move(["d3"], ["d0", "d4"], [], [], [], [])

game.board.board["d0"].reset()
game.board.board["f0"].reset()
with pytest.raises(ValueError, match="King split is not supported currently."):
game.classify_move(["e0"], ["d0", "f0"], [], [], [], [])


def test_classify_move_success(monkeypatch):
output = io.StringIO()
sys.stdout = output
inputs = iter(["y", "Bob", "Ben"])
monkeypatch.setattr("builtins.input", lambda _: next(inputs))
game = QuantumChineseChess()
# classical
assert game.classify_move(["h9"], ["g7"], [], [], [], []) == (
MoveType.CLASSICAL,
MoveVariant.UNSPECIFIED,
)
assert game.classify_move(["b2"], ["b9"], ["b7"], [], [], []) == (
MoveType.CLASSICAL,
MoveVariant.UNSPECIFIED,
)

# jump basic
game.board.board["c9"].is_entangled = True
assert game.classify_move(["c9"], ["e7"], [], [], [], []) == (
MoveType.JUMP,
MoveVariant.BASIC,
)
game.board.board["b2"].is_entangled = True
assert game.classify_move(["b2"], ["e2"], [], [], [], []) == (
MoveType.JUMP,
MoveVariant.BASIC,
)

# jump excluded
game.board.board["a3"].is_entangled = True
assert game.classify_move(["a0"], ["a3"], [], [], [], []) == (
MoveType.JUMP,
MoveVariant.EXCLUDED,
)

# jump capture
game.board.board["g4"].reset(game.board.board["g6"])
game.board.board["g4"].is_entangled = True
game.board.board["g6"].reset()
assert game.classify_move(["g4"], ["g3"], [], [], [], []) == (
MoveType.JUMP,
MoveVariant.CAPTURE,
)

# slide basic
assert game.classify_move(["a0"], ["a4"], [], ["a3"], [], []) == (
MoveType.SLIDE,
MoveVariant.BASIC,
)

# slide excluded
game.board.board["i7"].reset(game.board.board["h7"])
game.board.board["i7"].is_entangled = True
game.board.board["h7"].reset()
game.board.board["i6"].is_entangled = True
assert game.classify_move(["i9"], ["i6"], [], ["i7"], [], []) == (
MoveType.SLIDE,
MoveVariant.EXCLUDED,
)

# slide capture
assert game.classify_move(["a0"], ["a6"], [], ["a3"], [], []) == (
MoveType.SLIDE,
MoveVariant.CAPTURE,
)

# split_jump basic
assert game.classify_move(["g4"], ["f4", "h4"], [], [], [], []) == (
MoveType.SPLIT_JUMP,
MoveVariant.BASIC,
)

# split_slide basic
game.board.board["d3"].reset(game.board.board["h2"])
game.board.board["h2"].reset()
game.board.board["c3"].is_entangled = True
game.board.board["e3"].is_entangled = True
assert game.classify_move(["d3"], ["b3", "f3"], [], ["c3"], [], ["e3"]) == (
MoveType.SPLIT_SLIDE,
MoveVariant.BASIC,
)

# merge_jump basic
game.board.board["b7"].is_entangled = True
assert game.classify_move(["b7", "i7"], ["e7"], [], [], [], []) == (
MoveType.MERGE_JUMP,
MoveVariant.BASIC,
)

# merge_slide basic
assert game.classify_move(["b7", "i7"], ["a7"], [], [], [], ["b7"]) == (
MoveType.MERGE_SLIDE,
MoveVariant.BASIC,
)

# cannon_fire capture
pass
assert game.classify_move(["i7"], ["i3"], [], ["i6"], [], []) == (
MoveType.CANNON_FIRE,
MoveVariant.CAPTURE,
)
game.board.board["i6"].is_entangled = False
assert game.classify_move(["i7"], ["i3"], ["i6"], [], [], []) == (
MoveType.CANNON_FIRE,
MoveVariant.CAPTURE,
)
8 changes: 4 additions & 4 deletions unitary/examples/quantum_chinese_chess/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ class MoveVariant(enum.Enum):
the MoveType above.
"""

UNSPECIFIED = 0
BASIC = 1
EXCLUDED = 2
CAPTURE = 3
UNSPECIFIED = 0 # Used together with MoveType = CLASSICAL.
BASIC = 1 # The target piece is empty.
EXCLUDED = 2 # The target piece has the same color.
CAPTURE = 3 # The target piece has the opposite color.


class Color(enum.Enum):
Expand Down

0 comments on commit ee07708

Please sign in to comment.