From 904cc75a6e20e426cc1d75bb8ec699c069878dfd Mon Sep 17 00:00:00 2001 From: Pengfei Chen Date: Tue, 30 Jan 2024 14:53:31 -0800 Subject: [PATCH 1/5] update --- .../examples/quantum_chinese_chess/board.py | 185 ++++++++++++++---- .../quantum_chinese_chess/board_test.py | 91 +++------ .../examples/quantum_chinese_chess/chess.py | 24 ++- .../examples/quantum_chinese_chess/enums.py | 13 +- .../quantum_chinese_chess/enums_test.py | 10 +- .../quantum_chinese_chess/piece_test.py | 6 +- 6 files changed, 209 insertions(+), 120 deletions(-) diff --git a/unitary/examples/quantum_chinese_chess/board.py b/unitary/examples/quantum_chinese_chess/board.py index 109801ea..0d8d9cbd 100644 --- a/unitary/examples/quantum_chinese_chess/board.py +++ b/unitary/examples/quantum_chinese_chess/board.py @@ -27,6 +27,16 @@ # The default initial state of the game. _INITIAL_FEN = "RHEAKAEHR/9/1C5C1/P1P1P1P1P/9/9/p1p1p1p1p/1c5c1/9/rheakaehr w---1" +reset = "\033[0m" +bold = "\033[01m" +dim = "\033[02m" +# background +grey = "\033[47m" +# foreground +black = "\033[30m" +red = "\033[31m" +lightred = "\033[91m" +lightgrey = "\033[37m" class Board: @@ -83,47 +93,146 @@ def from_fen(cls, fen: str = _INITIAL_FEN) -> "Board": # TODO(): maybe add check to make sure the input fen itself is correct. return cls(board, current_player, king_locations) - def __str__(self): - num_rows = 10 - board_string = ["\n "] - # Print the top line of col letters. - for col in "abcdefghi": - board_string.append(f" {col}") - board_string.append("\n") - for row in range(num_rows - 1, -1, -1): - # Print the row index on the left. - board_string.append(f"{row} ") - for col in "abcdefghi": - piece = self.board[f"{col}{row}"] + # TODO(): print players' names in their corresponding side of the board. + def to_str( + self, + probabilities: List[float] = None, + print_probabilities=True, + peek_result: List[int] = None, + sublime_terminus=False, + ): + def add_piece_symbol( + board_string: str, + piece: Piece, + peek_result: List[int] = None, + index: int = 0, + sublime_terminus=False, + ): + if peek_result is None and piece.is_entangled: + # dim works on mac terminal and gLinux terminal, + # but not on sublime terminus + # if not sublime_terminus: + # board_string += dim + if piece.color == Color.RED: + board_string += lightred + else: + board_string += lightgrey + else: + # bold works on mac terminal and gLinux terminal, + # but not on sublime terminus + if not sublime_terminus: + board_string += bold + if piece.color == Color.RED: + board_string += red + else: + pass + if ( + peek_result is None + or piece.type_ == Type.EMPTY + or peek_result[index] == 1 + ): board_string += piece.symbol(self.lang) - if self.lang == Language.EN: - board_string.append(" ") - # Print the row index on the right. - board_string.append(f" {row}\n") - board_string.append(" ") - # Print the bottom line of col letters. - for col in "abcdefghi": - board_string.append(f" {col}") - board_string.append("\n") + elif piece.is_entangled and peek_result[index] == 0: + board_string += Type.symbol(Type.EMPTY, Color.NA, self.lang) + board_string += reset + + num_rows = 10 + if print_probabilities and probabilities is None: + probabilities = self.board.get_binary_probabilities() + if self.lang == Language.EN: + board_string = ["\n "] + # Print the top line of col letters. + board_string += grey + board_string += black + for col in "abcdefghi": + board_string.append(f" {col} ") + board_string += "\b" + reset + " \n" + index = 0 + for row in range(num_rows): + # Print the row index on the left. + board_string.append(f"{row} ") + for col in "abcdefghi": + piece = self.board[f"{col}{row}"] + add_piece_symbol( + board_string, piece, peek_result, index, sublime_terminus + ) + if col != "i": + board_string.append(" ") + board_string += reset + index += 1 + # Print the row index on the right. + board_string += f" {row}" + reset + "\n" + # Print the sampled prob. of the pieces in the above row. + if print_probabilities: + board_string += " " + board_string += grey + board_string += black + for i in range(row * 9, (row + 1) * 9): + if probabilities[i] > 0.01 and probabilities[i] < 0.99: + board_string.append("{:.1f} ".format(probabilities[i])) + else: + board_string.append(" ") + board_string += "\b" + reset + " \n" + board_string.append(" ") + # Print the bottom line of col letters. + board_string += grey + board_string += black + for col in "abcdefghi": + board_string.append(f" {col} ") + board_string += "\b" + reset + " \n" + return "".join(board_string) + else: + f_space = "\N{IDEOGRAPHIC SPACE}" + f_a = ord("\N{FULLWIDTH LATIN SMALL LETTER A}") + board_string = ["\n" + f_space + " "] + # Print the top line of col letters. + board_string += grey + board_string += black + for col in range(f_a, f_a + 9): + board_string.append(f"{chr(col)}") + if col != f_a + 8: + board_string += f_space * 2 + board_string += " \n" + reset + index = 0 + for row in range(num_rows): + # Print the row index on the left. + board_string.append(f"{row}" + f_space) + for col in "abcdefghi": + piece = self.board[f"{col}{row}"] + add_piece_symbol( + board_string, piece, peek_result, index, sublime_terminus + ) + if col != "i": + board_string.append(f_space * 2) + index += 1 + # Print the row index on the right. + board_string += f_space * 2 + f"{row}\n" + # Print the sampled prob. of the pieces in the above row. + if print_probabilities: + board_string += f_space + " " + board_string += grey + board_string += black + for i in range(row * 9, (row + 1) * 9): + if not sublime_terminus: + # space + f_space works for mac terminal and gLinux terminal. + board_string.append( + "{:.1f} ".format(probabilities[i]) + f_space + ) + else: + # space + space works for sublime terminus. + board_string.append("{:.1f} ".format(probabilities[i])) + board_string += "\b" + reset + f_space + "\n" + # Print the bottom line of col letters. + board_string.append(f_space + " ") + board_string += grey + board_string += black + for col in range(f_a, f_a + 9): + board_string.append(f"{chr(col)}") + if col != f_a + 8: + board_string += f_space * 2 + board_string += " " + reset return "".join(board_string) - # We need to turn letters into their full-width counterparts to align - # a mix of letters + Chinese characters. - chars = "".join(chr(c) for c in range(ord(" "), ord("z"))) - full_width_chars = "\N{IDEOGRAPHIC SPACE}" + "".join( - chr(c) - for c in range( - ord("\N{FULLWIDTH EXCLAMATION MARK}"), - ord("\N{FULLWIDTH LATIN SMALL LETTER Z}"), - ) - ) - translation = str.maketrans(chars, full_width_chars) - return ( - "".join(board_string) - .replace(" ", "") - .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).""" diff --git a/unitary/examples/quantum_chinese_chess/board_test.py b/unitary/examples/quantum_chinese_chess/board_test.py index a8812228..a920ae93 100644 --- a/unitary/examples/quantum_chinese_chess/board_test.py +++ b/unitary/examples/quantum_chinese_chess/board_test.py @@ -28,44 +28,26 @@ set_board, ) from unitary import alpha +import re def test_init_with_default_fen(): board = Board.from_fen() assert ( - board.__str__() + re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") == """ - a b c d e f g h i -9 r h e a k a e h r 9 -8 . . . . . . . . . 8 -7 . c . . . . . c . 7 -6 p . p . p . p . p 6 -5 . . . . . . . . . 5 -4 . . . . . . . . . 4 -3 P . P . P . P . P 3 -2 . C . . . . . C . 2 -1 . . . . . . . . . 1 -0 R H E A K A E H R 0 - a b c d e f g h i -""" - ) - - board.set_language(Language.ZH) - assert ( - board.__str__() - == """ - abcdefghi -9車馬相仕帥仕相馬車9 -8.........8 -7.砲.....砲.7 -6卒.卒.卒.卒.卒6 -5.........5 -4.........4 -3兵.兵.兵.兵.兵3 -2.炮.....炮.2 -1.........1 -0车马象士将士象马车0 - abcdefghi + a b c d e f g h i +0 R H E A K A E H R 0 +1 · · · · · · · · · 1 +2 · C · · · · · C · 2 +3 P · P · P · P · P 3 +4 · · · · · · · · · 4 +5 · · · · · · · · · 5 +6 p · p · p · p · p 6 +7 · c · · · · · c · 7 +8 · · · · · · · · · 8 +9 r h e a k a e h r 9 + a b c d e f g h i """ ) @@ -76,39 +58,20 @@ def test_init_with_specified_fen(): board = Board.from_fen("4kaR2/4a4/3hR4/7H1/9/9/9/9/4Ap1r1/3AK3c w---1 ") assert ( - board.__str__() - == """ - a b c d e f g h i -9 . . . A K . . . c 9 -8 . . . . A p . r . 8 -7 . . . . . . . . . 7 -6 . . . . . . . . . 6 -5 . . . . . . . . . 5 -4 . . . . . . . . . 4 -3 . . . . . . . H . 3 -2 . . . h R . . . . 2 -1 . . . . a . . . . 1 -0 . . . . k a R . . 0 - a b c d e f g h i -""" - ) - - board.set_language(Language.ZH) - assert ( - board.__str__() + re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") == """ - abcdefghi -9...士将...砲9 -8....士卒.車.8 -7.........7 -6.........6 -5.........5 -4.........4 -3.......马.3 -2...馬车....2 -1....仕....1 -0....帥仕车..0 - abcdefghi + a b c d e f g h i +0 · · · · k a R · · 0 +1 · · · · a · · · · 1 +2 · · · h R · · · · 2 +3 · · · · · · · H · 3 +4 · · · · · · · · · 4 +5 · · · · · · · · · 5 +6 · · · · · · · · · 6 +7 · · · · · · · · · 7 +8 · · · · A p · r · 8 +9 · · · A K · · · c 9 + a b c d e f g h i """ ) diff --git a/unitary/examples/quantum_chinese_chess/chess.py b/unitary/examples/quantum_chinese_chess/chess.py index 58cc2656..9b51b55b 100644 --- a/unitary/examples/quantum_chinese_chess/chess.py +++ b/unitary/examples/quantum_chinese_chess/chess.py @@ -73,7 +73,7 @@ def __init__(self): self.print_welcome() self.board = Board.from_fen() self.board.set_language(self.lang) - print(self.board) + print(self.board.to_str()) self.game_state = GameState.CONTINUES self.current_player = self.board.current_player self.debug_level = 3 @@ -442,13 +442,23 @@ def next_move(self) -> Tuple[bool, str]: self.game_state = GameState(1 - self.current_player) output = "Exiting." elif input_str.lower() == "peek": - # TODO(): make it look like the normal board. Right now it's only for debugging purposes. - print(self.board.board.peek(convert_to_enum=False)) + print( + self.board.to_str( + None, False, self.board.board.peek(convert_to_enum=False)[0] + ) + ) + elif input_str.lower() == "peek all": + all_boards = self.board.board.get_correlated_histogram() + sorted_boards = sorted(all_boards.items(), key=lambda x: x[1], reverse=True) + for board, count in sorted_boards: + print( + "\n ====== With probability ~ {:.1f} ======".format(count / 100.0) + ) + print(self.board.to_str(None, False, list(board))) elif input_str.lower() == "undo": if self.undo(): return True, "Undoing." return False, "Failed to undo." - else: try: # The move is success if no ValueError is raised. @@ -550,6 +560,7 @@ def undo(self) -> bool: def play(self) -> None: """The loop where each player takes turn to play.""" + probs = None while True: move_success, output = self.next_move() if not move_success: @@ -567,8 +578,9 @@ def play(self) -> None: probs = self.update_board_by_sampling() # Save the current states. self.save_snapshot() - # TODO(): pass probs into the following method to print probabilities. - print(self.board) + else: + probs = self.update_board_by_sampling() + print(self.board.to_str(probs)) if self.game_state == GameState.CONTINUES: # If the game continues, switch the player. self.current_player = 1 - self.current_player diff --git a/unitary/examples/quantum_chinese_chess/enums.py b/unitary/examples/quantum_chinese_chess/enums.py index f017cec2..6242b3b4 100644 --- a/unitary/examples/quantum_chinese_chess/enums.py +++ b/unitary/examples/quantum_chinese_chess/enums.py @@ -85,7 +85,12 @@ class Type(enum.Enum): - Chinese black """ - EMPTY = (".", ".", ".", ".") + EMPTY = ( + "\u00B7", + "\u00B7", + "\u30FB", + "\u30FB", + ) # \u00B7 is a half width mid dot, and \u30FB is full width PAWN = ("P", "p", "兵", "卒") CANNON = ("C", "c", "炮", "砲") ROOK = ("R", "r", "车", "車") @@ -104,7 +109,7 @@ def type_of(c: str) -> Optional["Type"]: "e": Type.ELEPHANT, "a": Type.ADVISOR, "k": Type.KING, - ".": Type.EMPTY, + "\u00B7": Type.EMPTY, }.get(c.lower(), None) @staticmethod @@ -115,11 +120,11 @@ def symbol(type_: "Type", color: Color, lang: Language = Language.EN) -> str: if lang == Language.EN: # Return English symbols if color == Color.RED: return type_.value[0] - elif color == Color.BLACK: + else: return type_.value[1] elif lang == Language.ZH: # Return Chinese symbols if color == Color.RED: return type_.value[2] - elif color == Color.BLACK: + else: return type_.value[3] raise ValueError("Unexpected combinations of language and color.") diff --git a/unitary/examples/quantum_chinese_chess/enums_test.py b/unitary/examples/quantum_chinese_chess/enums_test.py index 894a4991..bab07d52 100644 --- a/unitary/examples/quantum_chinese_chess/enums_test.py +++ b/unitary/examples/quantum_chinese_chess/enums_test.py @@ -19,7 +19,7 @@ def test_type_of(): assert Type.type_of("P") == Type.PAWN assert Type.type_of("k") == Type.KING assert Type.type_of("K") == Type.KING - assert Type.type_of(".") == Type.EMPTY + assert Type.type_of("·") == Type.EMPTY assert Type.type_of("b") == None @@ -34,7 +34,7 @@ def test_symbol(): assert Type.symbol(Type.HORSE, Color.RED, Language.ZH) == "马" assert Type.symbol(Type.HORSE, Color.BLACK, Language.ZH) == "馬" - assert Type.symbol(Type.EMPTY, Color.RED) == "." - assert Type.symbol(Type.EMPTY, Color.BLACK) == "." - assert Type.symbol(Type.EMPTY, Color.RED, Language.ZH) == "." - assert Type.symbol(Type.EMPTY, Color.BLACK, Language.ZH) == "." + assert Type.symbol(Type.EMPTY, Color.RED) == "·" + assert Type.symbol(Type.EMPTY, Color.BLACK) == "·" + assert Type.symbol(Type.EMPTY, Color.RED, Language.ZH) == "·" + assert Type.symbol(Type.EMPTY, Color.BLACK, Language.ZH) == "·" diff --git a/unitary/examples/quantum_chinese_chess/piece_test.py b/unitary/examples/quantum_chinese_chess/piece_test.py index 291a2691..a7a54e05 100644 --- a/unitary/examples/quantum_chinese_chess/piece_test.py +++ b/unitary/examples/quantum_chinese_chess/piece_test.py @@ -34,9 +34,9 @@ def test_symbol(): assert p1.symbol(Language.ZH) == "馬" p2 = Piece("c2", SquareState.EMPTY, Type.EMPTY, Color.NA) - assert p2.symbol() == "." - assert p2.__str__() == "." - assert p2.symbol(Language.ZH) == "." + assert p2.symbol() == "·" + assert p2.__str__() == "·" + assert p2.symbol(Language.ZH) == "·" def test_enum(): From 0c3374335fbb7601f3fb4d62e867be6462fa5b86 Mon Sep 17 00:00:00 2001 From: Pengfei Chen Date: Tue, 30 Jan 2024 15:01:48 -0800 Subject: [PATCH 2/5] update --- unitary/examples/quantum_chinese_chess/chess.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/unitary/examples/quantum_chinese_chess/chess.py b/unitary/examples/quantum_chinese_chess/chess.py index 9b51b55b..4ed5611c 100644 --- a/unitary/examples/quantum_chinese_chess/chess.py +++ b/unitary/examples/quantum_chinese_chess/chess.py @@ -32,15 +32,19 @@ ) import readline -# List of accepable commands. +# List of acceptable commands. _HELP_TEXT = """ Each location on the board is represented by two characters [abcdefghi][0-9], i.e. from a0 to i9. You may input (s=source, t=target) - s1t1 to do a slide move, e.g. "a1a4"; - s1^t1t2 to do a split move, e.g. "a1^b1a2"; - s1s2^t1 to do a merge move, e.g. "b1a2^a1"; + Other commands: - - "exit" to quit + - "peek": to peek (print a sample of) the current board state + - "peek all": to print all possible board states with associated probabilities + - "undo": to undo last move - "help": to see this message again + - "exit": to quit the game """ _WELCOME_MESSAGE = """ From ff0b842880ae20a2e2cb73fc130bbc1b807eaf84 Mon Sep 17 00:00:00 2001 From: Pengfei Chen Date: Wed, 31 Jan 2024 11:15:49 -0800 Subject: [PATCH 3/5] update --- .../examples/quantum_chinese_chess/board.py | 141 ++++++++++-------- .../quantum_chinese_chess/board_test.py | 45 ++++++ .../examples/quantum_chinese_chess/enums.py | 15 +- .../quantum_chinese_chess/enums_test.py | 4 +- .../quantum_chinese_chess/piece_test.py | 2 +- 5 files changed, 133 insertions(+), 74 deletions(-) diff --git a/unitary/examples/quantum_chinese_chess/board.py b/unitary/examples/quantum_chinese_chess/board.py index 0d8d9cbd..73afd0b9 100644 --- a/unitary/examples/quantum_chinese_chess/board.py +++ b/unitary/examples/quantum_chinese_chess/board.py @@ -27,16 +27,20 @@ # The default initial state of the game. _INITIAL_FEN = "RHEAKAEHR/9/1C5C1/P1P1P1P1P/9/9/p1p1p1p1p/1c5c1/9/rheakaehr w---1" -reset = "\033[0m" -bold = "\033[01m" -dim = "\033[02m" + +# Constants for printing board +_RESET = "\033[0m" +_BOLD = "\033[01m" # background -grey = "\033[47m" +_BG_GREY = "\033[47m" # foreground -black = "\033[30m" -red = "\033[31m" -lightred = "\033[91m" -lightgrey = "\033[37m" +_FG_BLACK = "\033[30m" +_FG_RED = "\033[31m" +_FG_LIGHT_RED = "\033[91m" +_FG_LIGHT_GREY = "\033[37m" +# full width chars +_FULL_SPACE = "\N{IDEOGRAPHIC SPACE}" +_FULL_A = ord("\N{FULLWIDTH LATIN SMALL LETTER A}") class Board: @@ -94,36 +98,38 @@ def from_fen(cls, fen: str = _INITIAL_FEN) -> "Board": return cls(board, current_player, king_locations) # TODO(): print players' names in their corresponding side of the board. + # To run the game on sublime terminus, set the following sublime_terminus=True + # for proer printing. + # TODO(): check if there is better way to automatic determine the current terminal + # type, e.g. sublime terminus vs glinux / mac terminal. + # TODO(): right now all possibilities are printed in black, maybe update to print + # in the same color as the corresponding pieces. def to_str( self, probabilities: List[float] = None, print_probabilities=True, peek_result: List[int] = None, - sublime_terminus=False, + sublime_terminus: bool = True, ): def add_piece_symbol( board_string: str, piece: Piece, - peek_result: List[int] = None, - index: int = 0, - sublime_terminus=False, + peek_result: List[int], + index: int, + sublime_terminus: bool, ): if peek_result is None and piece.is_entangled: - # dim works on mac terminal and gLinux terminal, - # but not on sublime terminus - # if not sublime_terminus: - # board_string += dim if piece.color == Color.RED: - board_string += lightred + board_string += _FG_LIGHT_RED else: - board_string += lightgrey + board_string += _FG_LIGHT_GREY else: # bold works on mac terminal and gLinux terminal, # but not on sublime terminus if not sublime_terminus: - board_string += bold + board_string += _BOLD if piece.color == Color.RED: - board_string += red + board_string += _FG_RED else: pass if ( @@ -132,9 +138,10 @@ def add_piece_symbol( or peek_result[index] == 1 ): board_string += piece.symbol(self.lang) + # If an entangled piece is peeked to be empty, we print empty. elif piece.is_entangled and peek_result[index] == 0: board_string += Type.symbol(Type.EMPTY, Color.NA, self.lang) - board_string += reset + board_string += _RESET num_rows = 10 if print_probabilities and probabilities is None: @@ -143,15 +150,16 @@ def add_piece_symbol( if self.lang == Language.EN: board_string = ["\n "] # Print the top line of col letters. - board_string += grey - board_string += black + board_string += _BG_GREY + board_string += _FG_BLACK for col in "abcdefghi": board_string.append(f" {col} ") - board_string += "\b" + reset + " \n" + board_string += "\b" + _RESET + " \n" index = 0 for row in range(num_rows): # Print the row index on the left. board_string.append(f"{row} ") + # Print each piece of this row, including empty piece. for col in "abcdefghi": piece = self.board[f"{col}{row}"] add_piece_symbol( @@ -159,79 +167,86 @@ def add_piece_symbol( ) if col != "i": board_string.append(" ") - board_string += reset + board_string += _RESET index += 1 # Print the row index on the right. - board_string += f" {row}" + reset + "\n" + board_string += f" {row}" + _RESET + "\n" # Print the sampled prob. of the pieces in the above row. if print_probabilities: board_string += " " - board_string += grey - board_string += black + board_string += _BG_GREY + board_string += _FG_BLACK for i in range(row * 9, (row + 1) * 9): - if probabilities[i] > 0.01 and probabilities[i] < 0.99: + # We only print non-zero probabilities + if probabilities[i] >= 1e-3: board_string.append("{:.1f} ".format(probabilities[i])) else: board_string.append(" ") - board_string += "\b" + reset + " \n" + board_string += "\b" + _RESET + " \n" board_string.append(" ") # Print the bottom line of col letters. - board_string += grey - board_string += black + board_string += _BG_GREY + board_string += _FG_BLACK for col in "abcdefghi": board_string.append(f" {col} ") - board_string += "\b" + reset + " \n" + board_string += "\b" + _RESET + " \n" return "".join(board_string) - else: - f_space = "\N{IDEOGRAPHIC SPACE}" - f_a = ord("\N{FULLWIDTH LATIN SMALL LETTER A}") - board_string = ["\n" + f_space + " "] + else: # Print Chinese + full width characters + board_string = ["\n" + _FULL_SPACE + " "] # Print the top line of col letters. - board_string += grey - board_string += black - for col in range(f_a, f_a + 9): + board_string += _BG_GREY + board_string += _FG_BLACK + for col in range(_FULL_A, _FULL_A + 9): board_string.append(f"{chr(col)}") - if col != f_a + 8: - board_string += f_space * 2 - board_string += " \n" + reset + if col != _FULL_A + 8: + board_string += _FULL_SPACE * 2 + board_string += " \n" + _RESET index = 0 for row in range(num_rows): # Print the row index on the left. - board_string.append(f"{row}" + f_space) + board_string.append(f"{row}" + _FULL_SPACE) + # Print each piece of this row, including empty piece. for col in "abcdefghi": piece = self.board[f"{col}{row}"] add_piece_symbol( board_string, piece, peek_result, index, sublime_terminus ) if col != "i": - board_string.append(f_space * 2) + board_string.append(_FULL_SPACE * 2) index += 1 # Print the row index on the right. - board_string += f_space * 2 + f"{row}\n" + board_string += _FULL_SPACE * 2 + f"{row}\n" # Print the sampled prob. of the pieces in the above row. if print_probabilities: - board_string += f_space + " " - board_string += grey - board_string += black + board_string += _FULL_SPACE + " " + board_string += _BG_GREY + board_string += _FG_BLACK for i in range(row * 9, (row + 1) * 9): + # We only print non-zero probabilities if not sublime_terminus: - # space + f_space works for mac terminal and gLinux terminal. - board_string.append( - "{:.1f} ".format(probabilities[i]) + f_space - ) + # space + _FULL_SPACE works for mac terminal and gLinux terminal. + if probabilities[i] >= 1e-3: + board_string.append( + "{:.1f} ".format(probabilities[i]) + _FULL_SPACE + ) + else: + board_string.append(" " + _FULL_SPACE) else: - # space + space works for sublime terminus. - board_string.append("{:.1f} ".format(probabilities[i])) - board_string += "\b" + reset + f_space + "\n" + if probabilities[i] >= 1e-3: + # space + space works for sublime terminus. + board_string.append("{:.1f} ".format(probabilities[i])) + else: + board_string.append(" ") + board_string += "\b" + _RESET + _FULL_SPACE + "\n" # Print the bottom line of col letters. - board_string.append(f_space + " ") - board_string += grey - board_string += black - for col in range(f_a, f_a + 9): + board_string.append(_FULL_SPACE + " ") + board_string += _BG_GREY + board_string += _FG_BLACK + for col in range(_FULL_A, _FULL_A + 9): board_string.append(f"{chr(col)}") - if col != f_a + 8: - board_string += f_space * 2 - board_string += " " + reset + if col != _FULL_A + 8: + board_string += _FULL_SPACE * 2 + board_string += " " + _RESET + "\n" return "".join(board_string) def path_pieces(self, source: str, target: str) -> Tuple[List[str], List[str]]: diff --git a/unitary/examples/quantum_chinese_chess/board_test.py b/unitary/examples/quantum_chinese_chess/board_test.py index a920ae93..f2823c8e 100644 --- a/unitary/examples/quantum_chinese_chess/board_test.py +++ b/unitary/examples/quantum_chinese_chess/board_test.py @@ -33,6 +33,8 @@ def test_init_with_default_fen(): board = Board.from_fen() + + # test English print assert ( re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") == """ @@ -51,12 +53,34 @@ def test_init_with_default_fen(): """ ) + # test Chinese print + board.set_language(Language.ZH) + assert ( + re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") + == """ +  a  b  c  d  e  f  g  h  i +0 车  马  象  士  将  士  象  马  车  0 +1 ・  ・  ・  ・  ・  ・  ・  ・  ・  1 +2 ・  炮  ・  ・  ・  ・  ・  炮  ・  2 +3 兵  ・  兵  ・  兵  ・  兵  ・  兵  3 +4 ・  ・  ・  ・  ・  ・  ・  ・  ・  4 +5 ・  ・  ・  ・  ・  ・  ・  ・  ・  5 +6 卒  ・  卒  ・  卒  ・  卒  ・  卒  6 +7 ・  砲  ・  ・  ・  ・  ・  砲  ・  7 +8 ・  ・  ・  ・  ・  ・  ・  ・  ・  8 +9 車  馬  相  仕  帥  仕  相  馬  車  9 +  a  b  c  d  e  f  g  h  i +""" + ) + + # check locations of KINGs assert board.king_locations == ["e0", "e9"] def test_init_with_specified_fen(): board = Board.from_fen("4kaR2/4a4/3hR4/7H1/9/9/9/9/4Ap1r1/3AK3c w---1 ") + # test English print assert ( re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") == """ @@ -75,6 +99,27 @@ def test_init_with_specified_fen(): """ ) + # test Chinese print + board.set_language(Language.ZH) + assert ( + re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") + == """ +  a  b  c  d  e  f  g  h  i +0 ・  ・  ・  ・  帥  仕  车  ・  ・  0 +1 ・  ・  ・  ・  仕  ・  ・  ・  ・  1 +2 ・  ・  ・  馬  车  ・  ・  ・  ・  2 +3 ・  ・  ・  ・  ・  ・  ・  马  ・  3 +4 ・  ・  ・  ・  ・  ・  ・  ・  ・  4 +5 ・  ・  ・  ・  ・  ・  ・  ・  ・  5 +6 ・  ・  ・  ・  ・  ・  ・  ・  ・  6 +7 ・  ・  ・  ・  ・  ・  ・  ・  ・  7 +8 ・  ・  ・  ・  士  卒  ・  車  ・  8 +9 ・  ・  ・  士  将  ・  ・  ・  砲  9 +  a  b  c  d  e  f  g  h  i +""" + ) + + # check locations of KINGs assert board.king_locations == ["e0", "e9"] diff --git a/unitary/examples/quantum_chinese_chess/enums.py b/unitary/examples/quantum_chinese_chess/enums.py index 6242b3b4..9b357ccd 100644 --- a/unitary/examples/quantum_chinese_chess/enums.py +++ b/unitary/examples/quantum_chinese_chess/enums.py @@ -85,12 +85,6 @@ class Type(enum.Enum): - Chinese black """ - EMPTY = ( - "\u00B7", - "\u00B7", - "\u30FB", - "\u30FB", - ) # \u00B7 is a half width mid dot, and \u30FB is full width PAWN = ("P", "p", "兵", "卒") CANNON = ("C", "c", "炮", "砲") ROOK = ("R", "r", "车", "車") @@ -98,6 +92,13 @@ class Type(enum.Enum): ELEPHANT = ("E", "e", "象", "相") ADVISOR = ("A", "a", "士", "仕") KING = ("K", "k", "将", "帥") + # \u00B7 is a half width mid dot, and \u30FB is full width + EMPTY = ( + "\u00B7", + "\u00B7", + "\u30FB", + "\u30FB", + ) @staticmethod def type_of(c: str) -> Optional["Type"]: @@ -115,8 +116,6 @@ def type_of(c: str) -> Optional["Type"]: @staticmethod def symbol(type_: "Type", color: Color, lang: Language = Language.EN) -> str: """Returns symbol of the given piece according to its color and desired language.""" - if type_ == Type.EMPTY: - return type_.value[0] if lang == Language.EN: # Return English symbols if color == Color.RED: return type_.value[0] diff --git a/unitary/examples/quantum_chinese_chess/enums_test.py b/unitary/examples/quantum_chinese_chess/enums_test.py index bab07d52..2f77c6e4 100644 --- a/unitary/examples/quantum_chinese_chess/enums_test.py +++ b/unitary/examples/quantum_chinese_chess/enums_test.py @@ -36,5 +36,5 @@ def test_symbol(): assert Type.symbol(Type.EMPTY, Color.RED) == "·" assert Type.symbol(Type.EMPTY, Color.BLACK) == "·" - assert Type.symbol(Type.EMPTY, Color.RED, Language.ZH) == "·" - assert Type.symbol(Type.EMPTY, Color.BLACK, Language.ZH) == "·" + assert Type.symbol(Type.EMPTY, Color.RED, Language.ZH) == "・" + assert Type.symbol(Type.EMPTY, Color.BLACK, Language.ZH) == "・" diff --git a/unitary/examples/quantum_chinese_chess/piece_test.py b/unitary/examples/quantum_chinese_chess/piece_test.py index a7a54e05..650960ea 100644 --- a/unitary/examples/quantum_chinese_chess/piece_test.py +++ b/unitary/examples/quantum_chinese_chess/piece_test.py @@ -36,7 +36,7 @@ def test_symbol(): p2 = Piece("c2", SquareState.EMPTY, Type.EMPTY, Color.NA) assert p2.symbol() == "·" assert p2.__str__() == "·" - assert p2.symbol(Language.ZH) == "·" + assert p2.symbol(Language.ZH) == "・" def test_enum(): From 42b29a46ecdd1ae6e57eb71512029e6da64f0f85 Mon Sep 17 00:00:00 2001 From: Pengfei Chen Date: Mon, 5 Feb 2024 11:35:31 -0800 Subject: [PATCH 4/5] update --- .../examples/quantum_chinese_chess/board.py | 51 ++++++++++--------- .../examples/quantum_chinese_chess/chess.py | 23 ++++++--- .../examples/quantum_chinese_chess/enums.py | 9 ++++ 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/unitary/examples/quantum_chinese_chess/board.py b/unitary/examples/quantum_chinese_chess/board.py index 73afd0b9..ce77b851 100644 --- a/unitary/examples/quantum_chinese_chess/board.py +++ b/unitary/examples/quantum_chinese_chess/board.py @@ -20,6 +20,7 @@ Type, Language, MoveVariant, + TerminalType, ) from unitary.examples.quantum_chinese_chess.piece import Piece from unitary.examples.quantum_chinese_chess.move import Jump @@ -55,7 +56,7 @@ def __init__( self.king_locations = king_locations self.lang = Language.EN # The default language is English. - def set_language(self, lang: Language): + def set_language(self, lang: Language) -> None: self.lang = lang @classmethod @@ -98,40 +99,48 @@ def from_fen(cls, fen: str = _INITIAL_FEN) -> "Board": return cls(board, current_player, king_locations) # TODO(): print players' names in their corresponding side of the board. - # To run the game on sublime terminus, set the following sublime_terminus=True - # for proer printing. # TODO(): check if there is better way to automatic determine the current terminal - # type, e.g. sublime terminus vs glinux / mac terminal. + # type, e.g. colab / sublime terminus vs glinux / mac terminal. # TODO(): right now all possibilities are printed in black, maybe update to print # in the same color as the corresponding pieces. + # TODO(): in some scenarios the black entangled pieces seems too light/weak to see. def to_str( self, + terminal: TerminalType, probabilities: List[float] = None, - print_probabilities=True, peek_result: List[int] = None, - sublime_terminus: bool = True, - ): + ) -> str: + """ + Print the board into string. + + Args: + terminal: type of the terminal that the game is currently running on; + probabilities: the probabilities of each piece of the board, in length of 90; + peek_result: for one peek of the board, provide a list of ints with length 90, + with int=1 indicating the piece is there (for this peek); + """ + def add_piece_symbol( board_string: str, piece: Piece, peek_result: List[int], index: int, - sublime_terminus: bool, + terminal: TerminalType, ): if peek_result is None and piece.is_entangled: if piece.color == Color.RED: board_string += _FG_LIGHT_RED else: + # bold works on mac terminal and gLinux terminal, + # but not on sublime terminus + if terminal != TerminalType.COLAB_OR_SUBLIME_TERMINUS: + board_string += _BOLD board_string += _FG_LIGHT_GREY else: - # bold works on mac terminal and gLinux terminal, - # but not on sublime terminus - if not sublime_terminus: + if terminal != TerminalType.COLAB_OR_SUBLIME_TERMINUS: board_string += _BOLD if piece.color == Color.RED: board_string += _FG_RED - else: - pass if ( peek_result is None or piece.type_ == Type.EMPTY @@ -144,8 +153,6 @@ def add_piece_symbol( board_string += _RESET num_rows = 10 - if print_probabilities and probabilities is None: - probabilities = self.board.get_binary_probabilities() if self.lang == Language.EN: board_string = ["\n "] @@ -162,9 +169,7 @@ def add_piece_symbol( # Print each piece of this row, including empty piece. for col in "abcdefghi": piece = self.board[f"{col}{row}"] - add_piece_symbol( - board_string, piece, peek_result, index, sublime_terminus - ) + add_piece_symbol(board_string, piece, peek_result, index, terminal) if col != "i": board_string.append(" ") board_string += _RESET @@ -172,7 +177,7 @@ def add_piece_symbol( # Print the row index on the right. board_string += f" {row}" + _RESET + "\n" # Print the sampled prob. of the pieces in the above row. - if print_probabilities: + if probabilities is not None: board_string += " " board_string += _BG_GREY board_string += _FG_BLACK @@ -208,22 +213,20 @@ def add_piece_symbol( # Print each piece of this row, including empty piece. for col in "abcdefghi": piece = self.board[f"{col}{row}"] - add_piece_symbol( - board_string, piece, peek_result, index, sublime_terminus - ) + add_piece_symbol(board_string, piece, peek_result, index, terminal) if col != "i": board_string.append(_FULL_SPACE * 2) index += 1 # Print the row index on the right. board_string += _FULL_SPACE * 2 + f"{row}\n" # Print the sampled prob. of the pieces in the above row. - if print_probabilities: + if probabilities is not None: board_string += _FULL_SPACE + " " board_string += _BG_GREY board_string += _FG_BLACK for i in range(row * 9, (row + 1) * 9): # We only print non-zero probabilities - if not sublime_terminus: + if terminal != TerminalType.COLAB_OR_SUBLIME_TERMINUS: # space + _FULL_SPACE works for mac terminal and gLinux terminal. if probabilities[i] >= 1e-3: board_string.append( diff --git a/unitary/examples/quantum_chinese_chess/chess.py b/unitary/examples/quantum_chinese_chess/chess.py index 4ed5611c..e0c35ebe 100644 --- a/unitary/examples/quantum_chinese_chess/chess.py +++ b/unitary/examples/quantum_chinese_chess/chess.py @@ -20,6 +20,7 @@ Color, MoveType, MoveVariant, + TerminalType, ) from unitary.examples.quantum_chinese_chess.move import ( Jump, @@ -67,6 +68,13 @@ def print_welcome(self) -> None: self.lang = Language.ZH else: self.lang = Language.EN + terminal = input( + "Are you running this game on Colab or Sublime Terminus? (y/n) " + ) + if terminal == "y": + self.terminal = TerminalType.COLAB_OR_SUBLIME_TERMINUS + else: + self.terminal = TerminalType.MAC_OR_LINUX name_0 = input("Player 0's name (default to be Player_0): ") self.players_name.append("Player_0" if len(name_0) == 0 else name_0) name_1 = input("Player 1's name (default to be Player_1): ") @@ -77,7 +85,7 @@ def __init__(self): self.print_welcome() self.board = Board.from_fen() self.board.set_language(self.lang) - print(self.board.to_str()) + print(self.board.to_str(self.terminal)) self.game_state = GameState.CONTINUES self.current_player = self.board.current_player self.debug_level = 3 @@ -448,7 +456,7 @@ def next_move(self) -> Tuple[bool, str]: elif input_str.lower() == "peek": print( self.board.to_str( - None, False, self.board.board.peek(convert_to_enum=False)[0] + self.terminal, None, self.board.board.peek(convert_to_enum=False)[0] ) ) elif input_str.lower() == "peek all": @@ -458,7 +466,7 @@ def next_move(self) -> Tuple[bool, str]: print( "\n ====== With probability ~ {:.1f} ======".format(count / 100.0) ) - print(self.board.to_str(None, False, list(board))) + print(self.board.to_str(self.terminal, None, list(board))) elif input_str.lower() == "undo": if self.undo(): return True, "Undoing." @@ -479,10 +487,6 @@ def update_board_by_sampling(self) -> List[float]: This method is called after each quantum move, and runs (100x) sampling of the board to identify and fix those cases. """ - # TODO(): return the sampled probabilities and pass it into the print method - # of the board to print it together with the board, or better use mathemetical - # matrix calculations to determine the probability, and use it (with some error - # threshold) to update the piece infos. probs = self.board.board.get_binary_probabilities() num_rows = 10 num_cols = 9 @@ -494,8 +498,11 @@ def update_board_by_sampling(self) -> List[float]: # Change it to be more meaningful values maybe when we do error mitigation. if prob < 1e-3: piece.reset() + probs[row * num_cols + ord(col) - ord("a")] = 0 elif prob > 1 - 1e-3: piece.is_entangled = False + probs[row * num_cols + ord(col) - ord("a")] = 1 + return probs def game_over(self) -> None: """Checks if the game is over, and update self.game_state accordingly.""" @@ -584,7 +591,7 @@ def play(self) -> None: self.save_snapshot() else: probs = self.update_board_by_sampling() - print(self.board.to_str(probs)) + print(self.board.to_str(self.terminal, probs)) if self.game_state == GameState.CONTINUES: # If the game continues, switch the player. self.current_player = 1 - self.current_player diff --git a/unitary/examples/quantum_chinese_chess/enums.py b/unitary/examples/quantum_chinese_chess/enums.py index 9b357ccd..1f01c4a5 100644 --- a/unitary/examples/quantum_chinese_chess/enums.py +++ b/unitary/examples/quantum_chinese_chess/enums.py @@ -127,3 +127,12 @@ def symbol(type_: "Type", color: Color, lang: Language = Language.EN) -> str: else: return type_.value[3] raise ValueError("Unexpected combinations of language and color.") + + +class TerminalType(enum.Enum): + """Type of the terminal that the game is running. This affects + how to properly print boards. + """ + + MAC_OR_LINUX = 0 + COLAB_OR_SUBLIME_TERMINUS = 1 From a23e9601a902e99d3b874f7aba42d0f065d1d5ae Mon Sep 17 00:00:00 2001 From: Pengfei Chen Date: Mon, 5 Feb 2024 11:44:22 -0800 Subject: [PATCH 5/5] update --- .../quantum_chinese_chess/board_test.py | 17 +++++++++---- .../quantum_chinese_chess/chess_test.py | 24 ++++++++++--------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/unitary/examples/quantum_chinese_chess/board_test.py b/unitary/examples/quantum_chinese_chess/board_test.py index f2823c8e..20b64f13 100644 --- a/unitary/examples/quantum_chinese_chess/board_test.py +++ b/unitary/examples/quantum_chinese_chess/board_test.py @@ -17,6 +17,7 @@ Color, Type, SquareState, + TerminalType, ) from unitary.examples.quantum_chinese_chess.board import Board from unitary.examples.quantum_chinese_chess.piece import Piece @@ -36,7 +37,9 @@ def test_init_with_default_fen(): # test English print assert ( - re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") + re.sub("\\033\[\d{1,2}m", "", board.to_str(TerminalType.MAC_OR_LINUX)).replace( + "\b", "" + ) == """ a b c d e f g h i 0 R H E A K A E H R 0 @@ -56,7 +59,9 @@ def test_init_with_default_fen(): # test Chinese print board.set_language(Language.ZH) assert ( - re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") + re.sub("\\033\[\d{1,2}m", "", board.to_str(TerminalType.MAC_OR_LINUX)).replace( + "\b", "" + ) == """   a  b  c  d  e  f  g  h  i 0 车  马  象  士  将  士  象  马  车  0 @@ -82,7 +87,9 @@ def test_init_with_specified_fen(): # test English print assert ( - re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") + re.sub("\\033\[\d{1,2}m", "", board.to_str(TerminalType.MAC_OR_LINUX)).replace( + "\b", "" + ) == """ a b c d e f g h i 0 · · · · k a R · · 0 @@ -102,7 +109,9 @@ def test_init_with_specified_fen(): # test Chinese print board.set_language(Language.ZH) assert ( - re.sub("\\033\[\d{1,2}m", "", board.to_str(None, False)).replace("\b", "") + re.sub("\\033\[\d{1,2}m", "", board.to_str(TerminalType.MAC_OR_LINUX)).replace( + "\b", "" + ) == """   a  b  c  d  e  f  g  h  i 0 ・  ・  ・  ・  帥  仕  车  ・  ・  0 diff --git a/unitary/examples/quantum_chinese_chess/chess_test.py b/unitary/examples/quantum_chinese_chess/chess_test.py index e8adc19e..ba307107 100644 --- a/unitary/examples/quantum_chinese_chess/chess_test.py +++ b/unitary/examples/quantum_chinese_chess/chess_test.py @@ -30,16 +30,18 @@ SquareState, MoveType, MoveVariant, + TerminalType, ) def test_game_init(monkeypatch): - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) output = io.StringIO() sys.stdout = output game = QuantumChineseChess() assert game.lang == Language.ZH + assert game.terminal == TerminalType.MAC_OR_LINUX assert game.players_name == ["Bob", "Ben"] assert game.current_player == 0 assert "Welcome" in output.getvalue() @@ -47,7 +49,7 @@ def test_game_init(monkeypatch): def test_parse_input_string_success(monkeypatch): - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() assert game.parse_input_string("a1b1") == (["a1"], ["b1"]) @@ -56,7 +58,7 @@ def test_parse_input_string_success(monkeypatch): def test_parse_input_string_fail(monkeypatch): - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() with pytest.raises(ValueError, match="Invalid sources/targets string "): @@ -76,7 +78,7 @@ def test_parse_input_string_fail(monkeypatch): def test_apply_move_fail(monkeypatch): - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() with pytest.raises(ValueError, match="Could not move empty piece."): @@ -94,7 +96,7 @@ def test_apply_move_fail(monkeypatch): def test_game_invalid_move(monkeypatch): output = io.StringIO() sys.stdout = output - inputs = iter(["y", "Bob", "Ben", "a1n1", "exit"]) + inputs = iter(["y", "n", "Bob", "Ben", "a1n1", "exit"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() game.play() @@ -108,7 +110,7 @@ def test_game_invalid_move(monkeypatch): def test_check_classical_rule(monkeypatch): output = io.StringIO() sys.stdout = output - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() board = game.board.board @@ -207,7 +209,7 @@ def test_check_classical_rule(monkeypatch): def test_classify_move_fail(monkeypatch): output = io.StringIO() sys.stdout = output - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() board = game.board.board @@ -259,7 +261,7 @@ def test_classify_move_fail(monkeypatch): def test_classify_move_success(monkeypatch): output = io.StringIO() sys.stdout = output - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() board = game.board.board @@ -367,7 +369,7 @@ def test_classify_move_success(monkeypatch): def test_update_board_by_sampling(monkeypatch): output = io.StringIO() sys.stdout = output - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() board = game.board.board @@ -390,7 +392,7 @@ def test_update_board_by_sampling(monkeypatch): def test_undo_single_effect_per_move(monkeypatch): - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() board = set_board(["a1", "b1", "c1"]) @@ -431,7 +433,7 @@ def test_undo_single_effect_per_move(monkeypatch): def test_undo_multiple_effects_per_move(monkeypatch): - inputs = iter(["y", "Bob", "Ben"]) + inputs = iter(["y", "n", "Bob", "Ben"]) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) game = QuantumChineseChess() board = set_board(["a1", "b1", "c1"])