You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

chess_game.py 8.6 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. """This is an example of simulating a chess game with two agents
  2. that play against each other, using tools to reason about the game state
  3. and make moves, and using a group chat manager to orchestrate the conversation."""
  4. import argparse
  5. import asyncio
  6. import logging
  7. from typing import Annotated, Literal
  8. from autogen_core.application import SingleThreadedAgentRuntime
  9. from autogen_core.base import AgentId, AgentInstantiationContext, AgentRuntime
  10. from autogen_core.components import DefaultSubscription, DefaultTopicId
  11. from autogen_core.components.model_context import BufferedChatCompletionContext
  12. from autogen_core.components.models import SystemMessage
  13. from autogen_core.components.tools import FunctionTool
  14. from chess import BLACK, SQUARE_NAMES, WHITE, Board, Move
  15. from chess import piece_name as get_piece_name
  16. from common.agents._chat_completion_agent import ChatCompletionAgent
  17. from common.patterns._group_chat_manager import GroupChatManager
  18. from common.types import TextMessage
  19. from common.utils import get_chat_completion_client_from_envs
  20. def validate_turn(board: Board, player: Literal["white", "black"]) -> None:
  21. """Validate that it is the player's turn to move."""
  22. last_move = board.peek() if board.move_stack else None
  23. if last_move is not None:
  24. if player == "white" and board.color_at(last_move.to_square) == WHITE:
  25. raise ValueError("It is not your turn to move. Wait for black to move.")
  26. if player == "black" and board.color_at(last_move.to_square) == BLACK:
  27. raise ValueError("It is not your turn to move. Wait for white to move.")
  28. elif last_move is None and player != "white":
  29. raise ValueError("It is not your turn to move. Wait for white to move first.")
  30. def get_legal_moves(
  31. board: Board, player: Literal["white", "black"]
  32. ) -> Annotated[str, "A list of legal moves in UCI format."]:
  33. """Get legal moves for the given player."""
  34. validate_turn(board, player)
  35. legal_moves = list(board.legal_moves)
  36. if player == "black":
  37. legal_moves = [move for move in legal_moves if board.color_at(move.from_square) == BLACK]
  38. elif player == "white":
  39. legal_moves = [move for move in legal_moves if board.color_at(move.from_square) == WHITE]
  40. else:
  41. raise ValueError("Invalid player, must be either 'black' or 'white'.")
  42. if not legal_moves:
  43. return "No legal moves. The game is over."
  44. return "Possible moves are: " + ", ".join([move.uci() for move in legal_moves])
  45. def get_board(board: Board) -> str:
  46. return str(board)
  47. def make_move(
  48. board: Board,
  49. player: Literal["white", "black"],
  50. thinking: Annotated[str, "Thinking for the move."],
  51. move: Annotated[str, "A move in UCI format."],
  52. ) -> Annotated[str, "Result of the move."]:
  53. """Make a move on the board."""
  54. validate_turn(board, player)
  55. newMove = Move.from_uci(move)
  56. board.push(newMove)
  57. # Print the move.
  58. print("-" * 50)
  59. print("Player:", player)
  60. print("Move:", newMove.uci())
  61. print("Thinking:", thinking)
  62. print("Board:")
  63. print(board.unicode(borders=True))
  64. # Get the piece name.
  65. piece = board.piece_at(newMove.to_square)
  66. assert piece is not None
  67. piece_symbol = piece.unicode_symbol()
  68. piece_name = get_piece_name(piece.piece_type)
  69. if piece_symbol.isupper():
  70. piece_name = piece_name.capitalize()
  71. return f"Moved {piece_name} ({piece_symbol}) from {SQUARE_NAMES[newMove.from_square]} to {SQUARE_NAMES[newMove.to_square]}."
  72. async def chess_game(runtime: AgentRuntime) -> None: # type: ignore
  73. """Create agents for a chess game and return the group chat."""
  74. # Create the board.
  75. board = Board()
  76. # Create tools for each player.
  77. # @functools.wraps(get_legal_moves)
  78. def get_legal_moves_black() -> str:
  79. return get_legal_moves(board, "black")
  80. # @functools.wraps(get_legal_moves)
  81. def get_legal_moves_white() -> str:
  82. return get_legal_moves(board, "white")
  83. # @functools.wraps(make_move)
  84. def make_move_black(
  85. thinking: Annotated[str, "Thinking for the move"],
  86. move: Annotated[str, "A move in UCI format"],
  87. ) -> str:
  88. return make_move(board, "black", thinking, move)
  89. # @functools.wraps(make_move)
  90. def make_move_white(
  91. thinking: Annotated[str, "Thinking for the move"],
  92. move: Annotated[str, "A move in UCI format"],
  93. ) -> str:
  94. return make_move(board, "white", thinking, move)
  95. def get_board_text() -> Annotated[str, "The current board state"]:
  96. return get_board(board)
  97. black_tools = [
  98. FunctionTool(
  99. get_legal_moves_black,
  100. name="get_legal_moves",
  101. description="Get legal moves.",
  102. ),
  103. FunctionTool(
  104. make_move_black,
  105. name="make_move",
  106. description="Make a move.",
  107. ),
  108. FunctionTool(
  109. get_board_text,
  110. name="get_board",
  111. description="Get the current board state.",
  112. ),
  113. ]
  114. white_tools = [
  115. FunctionTool(
  116. get_legal_moves_white,
  117. name="get_legal_moves",
  118. description="Get legal moves.",
  119. ),
  120. FunctionTool(
  121. make_move_white,
  122. name="make_move",
  123. description="Make a move.",
  124. ),
  125. FunctionTool(
  126. get_board_text,
  127. name="get_board",
  128. description="Get the current board state.",
  129. ),
  130. ]
  131. await runtime.register(
  132. "PlayerBlack",
  133. lambda: ChatCompletionAgent(
  134. description="Player playing black.",
  135. system_messages=[
  136. SystemMessage(
  137. content="You are a chess player and you play as black. "
  138. "Use get_legal_moves() to get list of legal moves. "
  139. "Use get_board() to get the current board state. "
  140. "Think about your strategy and call make_move(thinking, move) to make a move."
  141. ),
  142. ],
  143. model_context=BufferedChatCompletionContext(buffer_size=10),
  144. model_client=get_chat_completion_client_from_envs(model="gpt-4o"),
  145. tools=black_tools,
  146. ),
  147. lambda: [DefaultSubscription()],
  148. )
  149. await runtime.register(
  150. "PlayerWhite",
  151. lambda: ChatCompletionAgent(
  152. description="Player playing white.",
  153. system_messages=[
  154. SystemMessage(
  155. content="You are a chess player and you play as white. "
  156. "Use get_legal_moves() to get list of legal moves. "
  157. "Use get_board() to get the current board state. "
  158. "Think about your strategy and call make_move(thinking, move) to make a move."
  159. ),
  160. ],
  161. model_context=BufferedChatCompletionContext(buffer_size=10),
  162. model_client=get_chat_completion_client_from_envs(model="gpt-4o"),
  163. tools=white_tools,
  164. ),
  165. lambda: [DefaultSubscription()],
  166. )
  167. # Create a group chat manager for the chess game to orchestrate a turn-based
  168. # conversation between the two agents.
  169. await runtime.register(
  170. "ChessGame",
  171. lambda: GroupChatManager(
  172. description="A chess game between two agents.",
  173. model_context=BufferedChatCompletionContext(buffer_size=10),
  174. participants=[
  175. AgentId("PlayerWhite", AgentInstantiationContext.current_agent_id().key),
  176. AgentId("PlayerBlack", AgentInstantiationContext.current_agent_id().key),
  177. ], # white goes first
  178. ),
  179. lambda: [DefaultSubscription()],
  180. )
  181. async def main() -> None:
  182. runtime = SingleThreadedAgentRuntime()
  183. await chess_game(runtime)
  184. runtime.start()
  185. # Publish an initial message to trigger the group chat manager to start orchestration.
  186. await runtime.publish_message(TextMessage(content="Game started.", source="System"), topic_id=DefaultTopicId())
  187. await runtime.stop_when_idle()
  188. if __name__ == "__main__":
  189. parser = argparse.ArgumentParser(description="Run a chess game between two agents.")
  190. parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.")
  191. args = parser.parse_args()
  192. if args.verbose:
  193. logging.basicConfig(level=logging.WARNING)
  194. logging.getLogger("autogen_core").setLevel(logging.DEBUG)
  195. handler = logging.FileHandler("chess_game.log")
  196. logging.getLogger("autogen_core").addHandler(handler)
  197. asyncio.run(main())