序言
为了提升自己的多模态处理能力和API调用技巧,我决定挑战一个有趣的项目——开发一款可以与Claude对战的黑白棋游戏!这个项目不仅涉及游戏逻辑的实现,还需要调用Claude的API,让AI作为对手进行博弈。通过这个过程,我希望能够深入理解多模态交互的实现方式,同时提升自己在AI集成和API开发方面的技能。接下来,就让我来分享这个项目的构思和开发过程吧!
我的计划是利用 Claude 3.5 Sonnet 的 多模态功能,让 Claude 通过分析棋盘图像来计算下一步最佳落子策略。相比传统的 API 仅处理文本输入,这次的挑战在于如何让 Claude 通过视觉信息理解棋局局势,并给出合理的走法建议。这不仅是对 Claude 多模态能力的测试,也是一次结合图像处理与 AI 交互的实践探索。
让AI编码
整个代码的实现都将交给 Claude 来完成!
这不仅是一次 AI 对战的实验,也是对 AI 代码生成能力的挑战。我会专注于设计思路,而所有的代码,包括棋盘状态的解析、游戏逻辑的实现、以及 Claude 的 API 调用,都会完全由 Claude 生成。通过这个方式,我希望能验证 Claude 在实际编程任务中的表现,同时也能更加专注于优化交互体验和 AI 对弈策略。
我是使用 Anthropic 的控制台 进行的实验。
界面看起来虽然简单,但其实内置了 Prompt 生成器 等实用功能,非常值得一试!如果你还没有用过,不妨尝试一下,或许会对提升你的 AI 交互体验有所帮助。
首先,我们来编写黑白棋的代码。
我打算先实现一个基础的黑白棋游戏。
在网上搜索了一下,发现有人使用 Python 的 numpy 和 matplotlib 来还原棋盘,我决定采用这种方法。
接下来,我执行了以下的提示词(Prompt)。
使用 Python 的 numpy 和 matplotlib 创建一个黑白棋(Othello)的棋盘。
- 在横轴上标注 A~H。
- 在纵轴上标注 1~8。
- 创建一个放置棋子(disc)的函数 `put_disc`。
- `put_disc` 函数的参数有 4 个:`x`、`y`、`color` 和 `board`。
- `x` 表示放置棋子的位置,接受 a~h(不区分大小写)。
- `y` 只能接受 1~8 作为合法输入。
- `color` 只能是 `BLACK` 或 `WHITE`。
- `board` 代表当前棋盘的状态。
- 如果尝试在 不可放置的位置 落子,应返回错误。
- 放置棋子后,应翻转相应的棋子颜色。
- 返回 更新后的棋盘状态。
- 创建一个检查棋盘状态并 判断游戏是否结束 的函数 `judge`。
- 若分出胜负,则返回 获胜方(BLACK 或 WHITE)。
- 若游戏尚未结束,则返回 `None`。
- 创建一个函数,将当前棋盘状态 导出为 PNG 图片。
Claude 为我生成了 Python 代码
import numpy as np
import matplotlib.pyplot as plt
# 常量定义
EMPTY = 0
BLACK = 1
WHITE = 2
def create_board():
"""创建初始棋盘"""
board = np.zeros((8, 8), dtype=int)
# 初始布局
board[3:5, 3:5] = np.array([[WHITE, BLACK],
[BLACK, WHITE]])
return board
def draw_board(board, filename=None):
"""绘制棋盘"""
fig, ax = plt.subplots(figsize=(8, 8))
# 设置背景颜色为绿色
ax.set_facecolor('forestgreen')
# 绘制网格
for i in range(9):
ax.axhline(i, color='black', linewidth=1)
ax.axvline(i, color='black', linewidth=1)
# 设置坐标轴标签
ax.set_xticks(np.arange(8))
ax.set_yticks(np.arange(8))
ax.set_xticklabels(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
ax.set_yticklabels(['1', '2', '3', '4', '5', '6', '7', '8'])
# 绘制棋子
for i in range(8):
for j in range(8):
if board[i, j] == BLACK:
circle = plt.Circle((j + 0.5, 7.5 - i), 0.4, color='black')
ax.add_patch(circle)
elif board[i, j] == WHITE:
circle = plt.Circle((j + 0.5, 7.5 - i), 0.4, color='white')
ax.add_patch(circle)
ax.set_aspect('equal')
if filename:
plt.savefig(filename)
plt.close()
def convert_position(x, y):
"""将坐标转换为内部表示"""
if not isinstance(x, str) or len(x) != 1:
raise ValueError("x 必须是单个字符")
x = x.upper()
if x not in 'ABCDEFGH':
raise ValueError("x 必须在 A 到 H 之间")
if not isinstance(y, int) or y < 1 or y > 8:
raise ValueError("y 必须在 1 到 8 之间")
return 8 - y, ord(x) - ord('A')
def is_valid_move(board, row, col, color):
"""检查指定位置是否可以落子"""
if board[row, col] != EMPTY:
return False
directions = [(0,1), (1,0), (0,-1), (-1,0),
(1,1), (-1,-1), (1,-1), (-1,1)]
valid = False
for dx, dy in directions:
if check_direction(board, row, col, dx, dy, color):
valid = True
return valid
def check_direction(board, row, col, dx, dy, color):
"""检查某个方向上是否可以翻转棋子"""
opponent = WHITE if color == BLACK else BLACK
x, y = row + dx, col + dy
if not (0 <= x < 8 and 0 <= y < 8) or board[x, y] != opponent:
return False
while 0 <= x < 8 and 0 <= y < 8:
if board[x, y] == EMPTY:
return False
if board[x, y] == color:
return True
x, y = x + dx, y + dy
return False
def put_disc(x, y, color, board):
"""落子"""
row, col = convert_position(x, y)
if not is_valid_move(board, row, col, color):
raise ValueError("无效的落子位置")
new_board = board.copy()
new_board[row, col] = color
directions = [(0,1), (1,0), (0,-1), (-1,0),
(1,1), (-1,-1), (1,-1), (-1,1)]
for dx, dy in directions:
if check_direction(board, row, col, dx, dy, color):
x, y = row + dx, col + dy
while board[x, y] != color:
new_board[x, y] = color
x, y = x + dx, y + dy
return new_board
def judge(board):
"""判断胜负"""
black_count = np.sum(board == BLACK)
white_count = np.sum(board == WHITE)
empty_count = np.sum(board == EMPTY)
if empty_count == 0:
if black_count > white_count:
return BLACK
elif white_count > black_count:
return WHITE
else:
return None
# 仍有空位时,游戏未结束
return None
# 使用示例
if __name__ == "__main__":
board = create_board()
draw_board(board, "initial_board.png")
# 示例:落子
try:
board = put_disc('C', 4, BLACK, board)
draw_board(board, "after_move.png")
except ValueError as e:
print(f"错误: {e}")
# 判断胜负
result = judge(board)
if result == BLACK:
print("Black wins!")
elif result == WHITE:
print("White wins!")
else:
print("Game is not finished yet")
执行后,生成了以下图像
看起来非常不错!!🎉
不过,我觉得棋盘格子的坐标标识可能不太直观,所以我用以下的提示词让 Claude 进行了调整,使其在棋盘上显示更清晰的标签。
请在棋盘的每个格子中央添加对应的坐标标签(如 A1、B2)。
字体大小 请设置为较大,以确保清晰可见。
字体颜色 设为黑色,以提高可读性。
标签位置 应符合标准棋盘标识方式,左上角为 A8,右下角为 H1。
效果非常不错!
修改后的代码
import matplotlib.pyplot as plt
import numpy as np
# 常量定义
EMPTY = 0
BLACK = 1
WHITE = 2
def create_board():
"""创建初始棋盘"""
board = np.zeros((8, 8), dtype=int)
# 初始布局
board[3:5, 3:5] = np.array([[WHITE, BLACK], [BLACK, WHITE]])
return board
def draw_board(board, filename=None):
"""绘制棋盘"""
fig, ax = plt.subplots(figsize=(8, 8))
# 设置背景为绿色
ax.set_facecolor('forestgreen')
# 绘制网格
for i in range(9):
ax.axhline(i, color='black', linewidth=1)
ax.axvline(i, color='black', linewidth=1)
# 设置标签
ax.set_xticks(np.arange(8))
ax.set_yticks(np.arange(8))
ax.set_xticklabels(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
ax.set_yticklabels(['1', '2', '3', '4', '5', '6', '7', '8'])
# 在网格中添加坐标标签(从 A8 到 H1)
for i in range(8):
for j in range(8):
label = f"{chr(65+j)}{8-i}"
ax.text(j + 0.5, 7.5 - i, label,
horizontalalignment='center',
verticalalignment='center',
color='black',
fontsize=12,
alpha=0.3) # 设置透明度
# 绘制棋子
for i in range(8):
for j in range(8):
if board[i, j] == BLACK:
circle = plt.Circle((j + 0.5, 7.5 - i), 0.4, color='black')
ax.add_patch(circle)
elif board[i, j] == WHITE:
circle = plt.Circle((j + 0.5, 7.5 - i), 0.4, color='white')
ax.add_patch(circle)
ax.set_aspect('equal')
if filename:
plt.savefig(filename)
plt.close()
def convert_position(x, y):
"""将坐标转换为内部表示"""
if not isinstance(x, str) or len(x) != 1:
raise ValueError("x 必须是单个字符")
x = x.upper()
if x not in "ABCDEFGH":
raise ValueError("x 必须在 A 到 H 之间")
if not isinstance(y, int) or y < 1 or y > 8:
raise ValueError("y 必须在 1 到 8 之间")
return 8 - y, ord(x) - ord("A")
def is_valid_move(board, row, col, color):
"""检查是否可以在指定位置落子"""
if board[row, col] != EMPTY:
return False
directions = [(0, 1), (1, 0), (0, -1), (-1, 0),
(1, 1), (-1, -1), (1, -1), (-1, 1)]
valid = False
for dx, dy in directions:
if check_direction(board, row, col, dx, dy, color):
valid = True
return valid
def check_direction(board, row, col, dx, dy, color):
"""检查某个方向是否可以翻转棋子"""
opponent = WHITE if color == BLACK else BLACK
x, y = row + dx, col + dy
if not (0 <= x < 8 and 0 <= y < 8) or board[x, y
使用 Streamlit 开发 UI
接下来,我计划创建一个 对战界面,让游戏更直观、更容易操作!
我将使用 Streamlit 来构建用户界面,让玩家可以通过网页直接进行黑白棋对战,同时实现棋盘的可视化显示和交互功能。
import json
import base64
import boto3
import httpx
# 创建 Amazon Bedrock 客户端
bedrock_client = boto3.client('bedrock-runtime', region_name='us-east-1')
# 获取棋盘图片并进行 Base64 编码
image_url = "https://your-image-url.com/board.png" # 请替换为实际的棋盘图片 URL
image_media_type = "image/png"
image_data = base64.standard_b64encode(httpx.get(image_url).content).decode("utf-8")
# 生成请求体
prompt = "请分析当前黑白棋的棋盘状态,并建议下一步的最佳落子位置。"
request_body = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 50,
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image",
"source": {
"type": "base64",
"media_type": image_media_type,
"data": image_data # 传入棋盘图片的 Base64 数据
}
}
]
}
]
}
# 调用 Amazon Bedrock 运行 Claude 3.5 Sonnet
response = bedrock_client.invoke_model(
modelId="anthropic.claude-3-5-sonnet-20240620-v1:0",
body=json.dumps(request_body),
contentType="application/json",
accept="application/json"
)
# 解析 AI 返回的结果
response_data = json.loads(response['body'].read())
ai_reply = response_data.get("content", [{}])[0].get("text", "")
print("Claude's move suggestion:", ai_reply)
生成的代码
import streamlit as st
import base64
import io
import boto3
import numpy as np
from othello import * # Assuming this contains your Othello game logic
import matplotlib.pyplot as plt
import tempfile
import os
import json
import uuid
# Initialize Amazon Bedrock client
bedrock_client = boto3.client('bedrock-runtime', region_name='us-east-1')
# Store all active game sessions
if 'game_sessions' not in st.session_state:
st.session_state.game_sessions = {}
def save_board_image(board):
"""Save board image to temporary file and return path"""
temp_dir = tempfile.gettempdir()
temp_path = os.path.join(temp_dir, f'othello_board_{uuid.uuid4().hex}.png')
draw_board(board, temp_path)
return temp_path
def get_board_image_bytes(board):
"""Convert board image to bytes and encode as base64"""
buf = io.BytesIO()
draw_board(board)
plt.savefig(buf, format='png', bbox_inches='tight')
buf.seek(0)
return base64.b64encode(buf.getvalue()).decode('utf-8')
def get_valid_moves(board, color):
"""Get list of valid moves"""
valid_moves = []
for i in range(8):
for j in range(8):
if is_valid_move(board, i, j, color):
col = chr(j + ord('A'))
row = 8 - i
valid_moves.append(f"{col}{row}")
return valid_moves
def get_ai_move(board, valid_moves):
"""Get AI's next move"""
if not valid_moves:
return None, None
board_image_base64 = get_board_image_bytes(board)
prompt = f"""You are playing as White in this Othello/Reversi game.
Valid moves available to you are: {', '.join(valid_moves)}
Analyze the board and choose one move from the valid moves listed above.
Respond with ONLY the move coordinate (e.g., 'E6') - no other text."""
request_body = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 50,
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": board_image_base64
}
}
]
}
]
}
response = bedrock_client.invoke_model(
modelId="anthropic.claude-3-5-sonnet-20240620-v1:0",
body=json.dumps(request_body),
contentType="application/json",
accept="application/json"
)
response_body = json.loads(response['body'].read().decode('utf-8'))
ai_response = response_body['content'][0]['text'].strip()
if ai_response in valid_moves:
return ai_response[0], int(ai_response[1])
else:
print(f"Invalid AI response. Using first valid move: {valid_moves[0]}")
return valid_moves[0][0], int(valid_moves[0][1])
def get_session_id():
"""Get or create a session ID for the current player"""
if 'session_id' not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
# Initialize new game state for this session
st.session_state.game_sessions[st.session_state.session_id] = {
'board': create_board(),
'game_over': False
}
return st.session_state.session_id
def main():
st.title("Othello vs AI")
# Get current player's session
session_id = get_session_id()
session = st.session_state.game_sessions[session_id]
# Display board
board_path = save_board_image(session['board'])
st.image(board_path)
# Show stone counts
black_count = np.sum(session['board'] == BLACK)
white_count = np.sum(session['board'] == WHITE)
st.write(f"Black (You): {black_count} stones")
st.write(f"White (AI): {white_count} stones")
if not session['game_over']:
valid_moves = get_valid_moves(session['board'], BLACK)
if valid_moves:
col1, col2 = st.columns(2)
with col1:
x = st.selectbox("Column (A-H)", list("ABCDEFGH"))
with col2:
y = st.number_input("Row (1-8)", min_value=1, max_value=8)
st.write("Valid moves:", ", ".join(valid_moves))
if st.button("Place Stone"):
try:
session['board'] = put_disc(x, y, BLACK, session['board'])
print(f"\nPlayer's move: {x}{y}")
result = judge(session['board'])
if result is not None:
session['game_over'] = True
else:
ai_valid_moves = get_valid_moves(
session['board'], WHITE)
if ai_valid_moves:
ai_x, ai_y = get_ai_move(
session['board'], ai_valid_moves)
if ai_x and ai_y:
session['board'] = put_disc(
ai_x, ai_y, WHITE, session['board'])
print(f"AI's move: {ai_x}{ai_y}")
result = judge(session['board'])
if result is not None:
session['game_over'] = True
st.rerun()
except ValueError as e:
st.error(f"Invalid move: {e}")
print(f"Error: {e}")
else:
st.write("No valid moves available for you. Skipping turn...")
ai_valid_moves = get_valid_moves(session['board'], WHITE)
if ai_valid_moves:
ai_x, ai_y = get_ai_move(session['board'], ai_valid_moves)
if ai_x and ai_y:
session['board'] = put_disc(
ai_x, ai_y, WHITE, session['board'])
st.rerun()
else:
session['game_over'] = True
if session['game_over']:
result = judge(session['board'])
if result == BLACK:
st.success("You win!")
elif result == WHITE:
st.error("AI wins!")
else:
st.info("Draw!")
if st.button("New Game"):
session['board'] = create_board()
session['game_over'] = False
st.rerun()
if __name__ == "__main__":
main()
让我们运行看看吧!
streamlit run app.py
正确的表示出了棋盘!
AI也能正确的做出响应!
总结
我们成功完成了 黑白棋(Othello) 的开发~~
在过程中,虽然 多次想要自己修改代码,但最终还是完全依靠 生成式 AI 完成了整个编程工作。
对于 严格的业务逻辑,可能仍然存在一定的挑战,但通过 生成式 AI,即使是 零编程经验 的人,也能体验到 创造软件的乐趣,这确实是一件很棒的事情!