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.9 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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. """Get the current board state."""
  47. return str(board)
  48. def make_move(
  49. board: Board,
  50. player: Literal["white", "black"],
  51. thinking: Annotated[str, "Thinking for the move."],
  52. move: Annotated[str, "A move in UCI format."],
  53. ) -> Annotated[str, "Result of the move."]:
  54. """Make a move on the board."""
  55. validate_turn(board, player)
  56. new_move = Move.from_uci(move)
  57. board.push(new_move)
  58. # Print the move.
  59. print("-" * 50)
  60. print("Player:", player)
  61. print("Move:", new_move.uci())
  62. print("Thinking:", thinking)
  63. print("Board:")
  64. print(board.unicode(borders=True))
  65. # Get the piece name.
  66. piece = board.piece_at(new_move.to_square)
  67. assert piece is not None
  68. piece_symbol = piece.unicode_symbol()
  69. piece_name = get_piece_name(piece.piece_type)
  70. if piece_symbol.isupper():
  71. piece_name = piece_name.capitalize()
  72. return f"Moved {piece_name} ({piece_symbol}) from {SQUARE_NAMES[new_move.from_square]} to {SQUARE_NAMES[new_move.to_square]}."
  73. async def chess_game(runtime: AgentRuntime) -> None: # type: ignore
  74. """Create agents for a chess game and return the group chat."""
  75. # Create the board.
  76. board = Board()
  77. # Create tools for each player.
  78. # @functools.wraps(get_legal_moves)
  79. def get_legal_moves_black() -> str:
  80. return get_legal_moves(board, "black")
  81. # @functools.wraps(get_legal_moves)
  82. def get_legal_moves_white() -> str:
  83. return get_legal_moves(board, "white")
  84. # @functools.wraps(make_move)
  85. def make_move_black(
  86. thinking: Annotated[str, "Thinking for the move"],
  87. move: Annotated[str, "A move in UCI format"],
  88. ) -> str:
  89. return make_move(board, "black", thinking, move)
  90. # @functools.wraps(make_move)
  91. def make_move_white(
  92. thinking: Annotated[str, "Thinking for the move"],
  93. move: Annotated[str, "A move in UCI format"],
  94. ) -> str:
  95. return make_move(board, "white", thinking, move)
  96. def get_board_text() -> Annotated[str, "The current board state"]:
  97. return get_board(board)
  98. black_tools = [
  99. FunctionTool(
  100. get_legal_moves_black,
  101. name="get_legal_moves",
  102. description="Get legal moves.",
  103. ),
  104. FunctionTool(
  105. make_move_black,
  106. name="make_move",
  107. description="Make a move.",
  108. ),
  109. FunctionTool(
  110. get_board_text,
  111. name="get_board",
  112. description="Get the current board state.",
  113. ),
  114. ]
  115. white_tools = [
  116. FunctionTool(
  117. get_legal_moves_white,
  118. name="get_legal_moves",
  119. description="Get legal moves.",
  120. ),
  121. FunctionTool(
  122. make_move_white,
  123. name="make_move",
  124. description="Make a move.",
  125. ),
  126. FunctionTool(
  127. get_board_text,
  128. name="get_board",
  129. description="Get the current board state.",
  130. ),
  131. ]
  132. await ChatCompletionAgent.register(
  133. runtime,
  134. "PlayerBlack",
  135. lambda: ChatCompletionAgent(
  136. description="Player playing black.",
  137. system_messages=[
  138. SystemMessage(
  139. content="You are a chess player and you play as black. "
  140. "Use get_legal_moves() to get list of legal moves. "
  141. "Use get_board() to get the current board state. "
  142. "Think about your strategy and call make_move(thinking, move) to make a move."
  143. ),
  144. ],
  145. model_context=BufferedChatCompletionContext(buffer_size=10),
  146. model_client=get_chat_completion_client_from_envs(model="gpt-4o"),
  147. tools=black_tools,
  148. ),
  149. )
  150. await runtime.add_subscription(DefaultSubscription(agent_type="PlayerBlack"))
  151. await ChatCompletionAgent.register(
  152. runtime,
  153. "PlayerWhite",
  154. lambda: ChatCompletionAgent(
  155. description="Player playing white.",
  156. system_messages=[
  157. SystemMessage(
  158. content="You are a chess player and you play as white. "
  159. "Use get_legal_moves() to get list of legal moves. "
  160. "Use get_board() to get the current board state. "
  161. "Think about your strategy and call make_move(thinking, move) to make a move."
  162. ),
  163. ],
  164. model_context=BufferedChatCompletionContext(buffer_size=10),
  165. model_client=get_chat_completion_client_from_envs(model="gpt-4o"),
  166. tools=white_tools,
  167. ),
  168. )
  169. await runtime.add_subscription(DefaultSubscription(agent_type="PlayerWhite"))
  170. # Create a group chat manager for the chess game to orchestrate a turn-based
  171. # conversation between the two agents.
  172. await GroupChatManager.register(
  173. runtime,
  174. "ChessGame",
  175. lambda: GroupChatManager(
  176. description="A chess game between two agents.",
  177. model_context=BufferedChatCompletionContext(buffer_size=10),
  178. participants=[
  179. AgentId("PlayerWhite", AgentInstantiationContext.current_agent_id().key),
  180. AgentId("PlayerBlack", AgentInstantiationContext.current_agent_id().key),
  181. ], # white goes first
  182. ),
  183. )
  184. await runtime.add_subscription(DefaultSubscription(agent_type="ChessGame"))
  185. async def main() -> None:
  186. """Main Entrypoint."""
  187. runtime = SingleThreadedAgentRuntime()
  188. await chess_game(runtime)
  189. runtime.start()
  190. # Publish an initial message to trigger the group chat manager to start
  191. # orchestration.
  192. await runtime.publish_message(
  193. TextMessage(content="Game started.", source="System"),
  194. topic_id=DefaultTopicId(),
  195. )
  196. await runtime.stop_when_idle()
  197. if __name__ == "__main__":
  198. parser = argparse.ArgumentParser(description="Run a chess game between two agents.")
  199. parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.")
  200. args = parser.parse_args()
  201. if args.verbose:
  202. logging.basicConfig(level=logging.WARNING)
  203. logging.getLogger("autogen_core").setLevel(logging.DEBUG)
  204. handler = logging.FileHandler("chess_game.log")
  205. logging.getLogger("autogen_core").addHandler(handler)
  206. asyncio.run(main())