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

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