init
This commit is contained in:
173
AlgoriAgent/examples/game_gomoku/code/board_agent.py
Normal file
173
AlgoriAgent/examples/game_gomoku/code/board_agent.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""A board agent class that can host a Gomoku game, and a function to
|
||||
convert the board to an image."""
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt, patches
|
||||
|
||||
from agentscope.message import Msg
|
||||
from agentscope.agents import AgentBase
|
||||
|
||||
|
||||
CURRENT_BOARD_PROMPT_TEMPLATE = """The current board is as follows:
|
||||
{board}
|
||||
{player}, it's your turn."""
|
||||
|
||||
NAME_BLACK = "Alice"
|
||||
NAME_WHITE = "Bob"
|
||||
|
||||
# The mapping from name to piece
|
||||
NAME_TO_PIECE = {
|
||||
NAME_BLACK: "o",
|
||||
NAME_WHITE: "x",
|
||||
}
|
||||
|
||||
EMPTY_PIECE = "0"
|
||||
|
||||
|
||||
def board2img(board: np.ndarray, save_path: str) -> str:
|
||||
"""Convert the board to an image and save it to the specified path."""
|
||||
|
||||
size = board.shape[0]
|
||||
fig, ax = plt.subplots(figsize=(10, 10))
|
||||
ax.set_xlim(0, size - 1)
|
||||
ax.set_ylim(0, size - 1)
|
||||
|
||||
for i in range(size):
|
||||
ax.axhline(i, color="black", linewidth=1)
|
||||
ax.axvline(i, color="black", linewidth=1)
|
||||
|
||||
for y in range(size):
|
||||
for x in range(size):
|
||||
if board[y, x] == NAME_TO_PIECE[NAME_BLACK]: # black player
|
||||
circle = patches.Circle(
|
||||
(x, y),
|
||||
0.45,
|
||||
edgecolor="black",
|
||||
facecolor="black",
|
||||
zorder=10,
|
||||
)
|
||||
ax.add_patch(circle)
|
||||
elif board[y, x] == NAME_TO_PIECE[NAME_WHITE]: # white player
|
||||
circle = patches.Circle(
|
||||
(x, y),
|
||||
0.45,
|
||||
edgecolor="black",
|
||||
facecolor="white",
|
||||
zorder=10,
|
||||
)
|
||||
ax.add_patch(circle)
|
||||
# Hide the axes and invert the y-axis
|
||||
ax.set_xticks(range(size))
|
||||
ax.set_yticks(range(size))
|
||||
ax.set_xticklabels(range(size))
|
||||
ax.set_yticklabels(range(size))
|
||||
ax.invert_yaxis()
|
||||
plt.savefig(save_path, bbox_inches="tight", pad_inches=0.1)
|
||||
plt.close(fig) # Close the figure to free memory
|
||||
return save_path
|
||||
|
||||
|
||||
class BoardAgent(AgentBase):
|
||||
"""A board agent that can host a Gomoku game."""
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
super().__init__(name=name, use_memory=False)
|
||||
|
||||
# Init the board
|
||||
self.size = 15
|
||||
self.board = np.full((self.size, self.size), EMPTY_PIECE)
|
||||
|
||||
# Record the status of the game
|
||||
self.game_end = False
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
if x is None:
|
||||
# Beginning of the game
|
||||
content = (
|
||||
"Welcome to the Gomoku game! Black player goes first. "
|
||||
"Please make your move."
|
||||
)
|
||||
else:
|
||||
row, col = x.content
|
||||
|
||||
self.assert_valid_move(row, col)
|
||||
|
||||
# change the board
|
||||
self.board[row, col] = NAME_TO_PIECE[x.name]
|
||||
|
||||
# check if the game ends
|
||||
if self.check_draw():
|
||||
content = "The game ends in a draw!"
|
||||
self.game_end = True
|
||||
else:
|
||||
next_player_name = (
|
||||
NAME_BLACK if x.name == NAME_WHITE else NAME_WHITE
|
||||
)
|
||||
content = CURRENT_BOARD_PROMPT_TEMPLATE.format(
|
||||
board=self.board2text(),
|
||||
player=next_player_name,
|
||||
)
|
||||
|
||||
if self.check_win(row, col, NAME_TO_PIECE[x.name]):
|
||||
content = f"The game ends, {x.name} wins!"
|
||||
self.game_end = True
|
||||
|
||||
msg_host = Msg(self.name, content, role="assistant")
|
||||
self.speak(msg_host)
|
||||
|
||||
# Note: we disable the image display here to avoid too many images
|
||||
# img = plt.imread(board2img(self.board, 'current_board.png'))
|
||||
# plt.imshow(img)
|
||||
# plt.axis('off')
|
||||
# plt.show()
|
||||
|
||||
return msg_host
|
||||
|
||||
def assert_valid_move(self, x: int, y: int) -> None:
|
||||
"""Check if the move is valid."""
|
||||
if not (0 <= x < self.size and 0 <= y < self.size):
|
||||
raise RuntimeError(f"Invalid move: {[x, y]} out of board range.")
|
||||
|
||||
if not self.board[x, y] == EMPTY_PIECE:
|
||||
raise RuntimeError(
|
||||
f"Invalid move: {[x, y]} is already "
|
||||
f"occupied by {self.board[x, y]}.",
|
||||
)
|
||||
|
||||
def check_win(self, x: int, y: int, piece: str) -> bool:
|
||||
"""Check if the player wins the game."""
|
||||
xline = self._check_line(self.board[x, :], piece)
|
||||
yline = self._check_line(self.board[:, y], piece)
|
||||
diag1 = self._check_line(np.diag(self.board, y - x), piece)
|
||||
diag2 = self._check_line(
|
||||
np.diag(np.fliplr(self.board), self.size - 1 - x - y),
|
||||
piece,
|
||||
)
|
||||
return xline or yline or diag1 or diag2
|
||||
|
||||
def check_draw(self) -> bool:
|
||||
"""Check if the game ends in a draw."""
|
||||
return np.all(self.board != EMPTY_PIECE)
|
||||
|
||||
def board2text(self) -> str:
|
||||
"""Convert the board to a text representation."""
|
||||
return "\n".join(
|
||||
[
|
||||
str(_)[1:-1].replace("'", "").replace(" ", "")
|
||||
for _ in self.board
|
||||
],
|
||||
)
|
||||
|
||||
def _check_line(self, line: np.ndarray, piece: str) -> bool:
|
||||
"""Check if the player wins in a line."""
|
||||
count = 0
|
||||
for i in line:
|
||||
if i == piece:
|
||||
count += 1
|
||||
if count == 5:
|
||||
return True
|
||||
else:
|
||||
count = 0
|
||||
return False
|
||||
77
AlgoriAgent/examples/game_gomoku/code/game_gomoku.py
Normal file
77
AlgoriAgent/examples/game_gomoku/code/game_gomoku.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The main script to start a Gomoku game between two agents and a board
|
||||
agent."""
|
||||
|
||||
from board_agent import NAME_TO_PIECE, NAME_BLACK, NAME_WHITE, BoardAgent
|
||||
from gomoku_agent import GomokuAgent
|
||||
|
||||
from agentscope import msghub
|
||||
|
||||
import agentscope
|
||||
|
||||
|
||||
MAX_STEPS = 10
|
||||
|
||||
SYS_PROMPT_TEMPLATE = """
|
||||
You're a skillful Gomoku player. You should play against your opponent according to the following rules:
|
||||
|
||||
Game Rules:
|
||||
1. This Gomoku board is a 15*15 grid. Moves are made by specifying row and column indexes, with [0, 0] marking the top-left corner and [14, 14] indicating the bottom-right corner.
|
||||
2. The goal is to be the first player to form an unbroken line of your pieces horizontally, vertically, or diagonally.
|
||||
3. If the board is completely filled with pieces and no player has formed a row of five, the game is declared a draw.
|
||||
|
||||
Note:
|
||||
1. Your pieces are represented by '{}', your opponent's by '{}'. 0 represents an empty spot on the board.
|
||||
2. You should think carefully about your strategy and moves, considering both your and your opponent's subsequent moves.
|
||||
3. Make sure you don't place your piece on a spot that has already been occupied.
|
||||
4. Only an unbroken line of five same pieces will win the game. For example, "xxxoxx" won't be considered a win.
|
||||
5. Note the unbroken line can be formed in any direction: horizontal, vertical, or diagonal.
|
||||
""" # noqa
|
||||
|
||||
# Prepare the model configuration
|
||||
YOUR_MODEL_CONFIGURATION_NAME = "{YOUR_MODEL_CONFIGURATION_NAME}"
|
||||
YOUR_MODEL_CONFIGURATION = {
|
||||
"config_name": YOUR_MODEL_CONFIGURATION_NAME,
|
||||
# ...
|
||||
}
|
||||
|
||||
# Initialize the agents
|
||||
agentscope.init(model_configs=YOUR_MODEL_CONFIGURATION)
|
||||
|
||||
piece_black = NAME_TO_PIECE[NAME_BLACK]
|
||||
piece_white = NAME_TO_PIECE[NAME_WHITE]
|
||||
|
||||
black = GomokuAgent(
|
||||
NAME_BLACK,
|
||||
model_config_name=YOUR_MODEL_CONFIGURATION_NAME,
|
||||
sys_prompt=SYS_PROMPT_TEMPLATE.format(piece_black, piece_white),
|
||||
)
|
||||
|
||||
white = GomokuAgent(
|
||||
NAME_WHITE,
|
||||
model_config_name=YOUR_MODEL_CONFIGURATION_NAME,
|
||||
sys_prompt=SYS_PROMPT_TEMPLATE.format(piece_white, piece_black),
|
||||
)
|
||||
|
||||
board = BoardAgent(name="Host")
|
||||
|
||||
# Start the game!
|
||||
msg = None
|
||||
i = 0
|
||||
# Use a msg hub to share conversation between two players, e.g. white player
|
||||
# can hear what black player says to the board
|
||||
with msghub(participants=[black, white, board]):
|
||||
while not board.game_end and i < MAX_STEPS:
|
||||
for player in [black, white]:
|
||||
# receive the move from the player, judge if the game ends and
|
||||
# remind the player to make a move
|
||||
msg = board(msg)
|
||||
|
||||
# end the game if draw or win
|
||||
if board.game_end:
|
||||
break
|
||||
|
||||
# make a move
|
||||
msg = player(msg)
|
||||
|
||||
i += 1
|
||||
82
AlgoriAgent/examples/game_gomoku/code/gomoku_agent.py
Normal file
82
AlgoriAgent/examples/game_gomoku/code/gomoku_agent.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""A Gomoku agent that can play the game with another agent."""
|
||||
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
import json
|
||||
|
||||
from agentscope.message import Msg
|
||||
from agentscope.agents import AgentBase
|
||||
from agentscope.models import ModelResponse
|
||||
|
||||
HINT_PROMPT = """
|
||||
You should respond in the following format, which can be loaded by json.loads in Python:
|
||||
{{
|
||||
"thought": "analyze the present situation, and what move you should make",
|
||||
"move": [row index, column index]
|
||||
}}
|
||||
""" # noqa
|
||||
|
||||
|
||||
def parse_func(response: ModelResponse) -> ModelResponse:
|
||||
"""Parse the response from the model into a dict with "move" and "thought"
|
||||
keys."""
|
||||
res_dict = json.loads(response.text)
|
||||
if "move" in res_dict and "thought" in res_dict:
|
||||
return ModelResponse(raw=res_dict)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid response format in parse_func "
|
||||
f"with response: {response.text}",
|
||||
)
|
||||
|
||||
|
||||
class GomokuAgent(AgentBase):
|
||||
"""A Gomoku agent that can play the game with another agent."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model_config_name: str,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
)
|
||||
|
||||
self.memory.add(Msg("system", sys_prompt, role="system"))
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
msg_hint = Msg("system", HINT_PROMPT, role="system")
|
||||
|
||||
prompt = self.model.format(
|
||||
self.memory.get_memory(),
|
||||
msg_hint,
|
||||
)
|
||||
|
||||
response = self.model(
|
||||
prompt,
|
||||
parse_func=parse_func,
|
||||
max_retries=3,
|
||||
).raw
|
||||
|
||||
# For better presentation, we print the response proceeded by
|
||||
# json.dumps, this msg won't be recorded in memory
|
||||
self.speak(
|
||||
Msg(
|
||||
self.name,
|
||||
json.dumps(response, indent=4, ensure_ascii=False),
|
||||
role="assistant",
|
||||
),
|
||||
)
|
||||
|
||||
if self.memory:
|
||||
self.memory.add(Msg(self.name, response, role="assistant"))
|
||||
|
||||
# Hide thought from the response
|
||||
return Msg(self.name, response["move"], role="assistant")
|
||||
Reference in New Issue
Block a user