525 lines
21 KiB
YAML
525 lines
21 KiB
YAML
default_max_attempts_per_step: 9
|
|
sections:
|
|
- section_id: "section_1"
|
|
title: "Battleship"
|
|
steps:
|
|
- step_id: "step_0"
|
|
title: "Introduction"
|
|
content_blocks:
|
|
- |
|
|
Welcome to Battleship! 🚢
|
|
In this game, both you and the AI have a fleet of ships placed randomly on a 10x10 grid.
|
|
The grid positions are numbered 0 to 99.
|
|
|
|
Your goal is to sink all of the AI's ships before it sinks yours.
|
|
Let's get started!
|
|
|
|
- step_id: "step_1"
|
|
title: "Choose AI Mode"
|
|
question: "Choose the AI mode: Random, Hunter, or Super Human Hunter?"
|
|
tokens_for_ai: |
|
|
If the user chooses Random, categorize as 'random_mode'.
|
|
If the user chooses Hunter, categorize as 'hunter_mode'.
|
|
If the user chooses Super Human Hunter, categorize as 'super_hunter_mode'.
|
|
feedback_tokens_for_ai: |
|
|
If the user chooses Random, acknowledge the choice.
|
|
If the user chooses Hunter, acknowledge the choice.
|
|
If the user chooses Super Human Hunter, acknowledge the choice.
|
|
processing_script: |
|
|
import random
|
|
|
|
def place_ships():
|
|
global random
|
|
# Define ship sizes and names
|
|
ships = {
|
|
"Carrier": 5,
|
|
"Battleship": 4,
|
|
"Cruiser": 3,
|
|
"Submarine": 3,
|
|
"Destroyer": 2
|
|
}
|
|
|
|
board = [-1] * 100
|
|
for ship, size in ships.items():
|
|
placed = False
|
|
while not placed:
|
|
orientation = random.choice(['horizontal', 'vertical'])
|
|
if orientation == 'horizontal':
|
|
row = random.randint(0, 9)
|
|
col = random.randint(0, 9 - size)
|
|
start = row * 10 + col
|
|
if all(board[start + i] == -1 for i in range(size)):
|
|
for i in range(size):
|
|
board[start + i] = ship
|
|
placed = True
|
|
else:
|
|
row = random.randint(0, 9 - size)
|
|
col = random.randint(0, 9)
|
|
start = row * 10 + col
|
|
if all(board[start + i * 10] == -1 for i in range(size)):
|
|
for i in range(size):
|
|
board[start + i * 10] = ship
|
|
placed = True
|
|
return board
|
|
|
|
user_board = place_ships()
|
|
ai_board = place_ships()
|
|
|
|
script_result = {
|
|
"metadata": {
|
|
"user_board": user_board,
|
|
"ai_board": ai_board
|
|
}
|
|
}
|
|
|
|
buckets:
|
|
- random_mode
|
|
- hunter_mode
|
|
- super_hunter_mode
|
|
transitions:
|
|
random_mode:
|
|
run_processing_script: True
|
|
ai_feedback:
|
|
tokens_for_ai: "Random Mode enabled for the AI."
|
|
metadata_add:
|
|
ai_mode: "random"
|
|
next_section_and_step: "section_1:step_2"
|
|
hunter_mode:
|
|
run_processing_script: True
|
|
ai_feedback:
|
|
tokens_for_ai: "Hunter Mode enabled for the AI."
|
|
metadata_add:
|
|
ai_mode: "hunter"
|
|
next_section_and_step: "section_1:step_2"
|
|
super_hunter_mode:
|
|
run_processing_script: True
|
|
ai_feedback:
|
|
tokens_for_ai: "Super Human Hunter Mode enabled for the AI."
|
|
metadata_add:
|
|
ai_mode: "super_hunter"
|
|
next_section_and_step: "section_1:step_2"
|
|
|
|
- step_id: "step_2"
|
|
title: "Take a Shot"
|
|
question: "Choose a position to fire at (0-99)."
|
|
tokens_for_ai: |
|
|
If the user wants to restart or play again, categorize as 'restart'.
|
|
If the user wants to exit, categorize as 'exit'.
|
|
If the move is valid, categorize as 'valid_move'.
|
|
If the move is invalid, categorize as 'invalid_move'.
|
|
feedback_tokens_for_ai: |
|
|
Important: Use the metadata to fill in the brackets and provide a conversational tone.
|
|
|
|
On a new line, announce the user's move and provide feedback.
|
|
|
|
The user's latest shot was [user_hit_result]:
|
|
- If user_hit_result is "hit", consider saying: "Great shot! [user_name] hit an AI ship!"
|
|
- If user_hit_result is "miss", consider saying: "Oh no, [user_name] missed the shot. Better luck next time!"
|
|
|
|
On a new line, announce the AI's move: "The AI fired at position [ai_shot] and it was a [ai_hit_result]."
|
|
|
|
The AI's latest shot was a [ai_hit_result]:
|
|
- If ai_hit_result is "hit", consider saying: "The AI hit one of [user_name]'s ships!"
|
|
- If ai_hit_result is "miss", consider saying: "The AI missed [user_name]'s ships this time."
|
|
|
|
If either [user_sunk_ship_this_round] or [ai_sunk_ship_this_round] is not None,
|
|
announce the destruction in a LOT of detail, use many sentences:
|
|
- If user_sunk_ship_this_round is not None, consider saying: "The user has sunk the AI's [user_sunk_ship_this_round]!"
|
|
- If ai_sunk_ship_this_round is not None, consider saying: "The AI has sunk the [user_name]'s [ai_sunk_ship_this_round]!"
|
|
|
|
If game_over = True, determine the winner:
|
|
- If user_wins = True, consider saying: "Congratulations! The [user_name] has sunk all AI ships and won the game!"
|
|
- If ai_wins = True, consider saying: "The AI has sunk all [user_name] ships and won the game!"
|
|
|
|
If game_over = True, suggest: "Would you like to restart and play again, or would you prefer to exit?"
|
|
|
|
processing_script: |
|
|
import random
|
|
import matplotlib.pyplot as plt
|
|
import io
|
|
import base64
|
|
|
|
# Define ship sizes
|
|
ship_sizes = {
|
|
"Carrier": 5,
|
|
"Battleship": 4,
|
|
"Cruiser": 3,
|
|
"Submarine": 3,
|
|
"Destroyer": 2
|
|
}
|
|
|
|
# Define colors for ships
|
|
ship_colors = {
|
|
"Carrier": "blue",
|
|
"Battleship": "green",
|
|
"Cruiser": "orange",
|
|
"Submarine": "purple",
|
|
"Destroyer": "pink"
|
|
}
|
|
|
|
# Retrieve the game state
|
|
user_board = metadata.get("user_board")
|
|
ai_board = metadata.get("ai_board")
|
|
user_shots = metadata.get("user_shots", [])
|
|
ai_shots = metadata.get("ai_shots", [])
|
|
user_hits = metadata.get("user_hits", [])
|
|
ai_hits = metadata.get("ai_hits", [])
|
|
game_over = metadata.get("game_over", False)
|
|
user_wins = False
|
|
ai_wins = False
|
|
user_hit_result = "miss"
|
|
ai_hit_result = "miss"
|
|
user_sunk_ships = metadata.get("user_sunk_ships", [])
|
|
ai_sunk_ships = metadata.get("ai_sunk_ships", [])
|
|
user_sunk_ship_this_round = None
|
|
ai_sunk_ship_this_round = None
|
|
|
|
# AI state variables
|
|
ai_mode = metadata.get("ai_mode", "random")
|
|
probability_matrix = metadata.get("probability_matrix", [[1] * 10 for _ in range(10)])
|
|
hits = metadata.get("hits", [])
|
|
misses = metadata.get("misses", [])
|
|
sunk_ships = metadata.get("sunk_ships", [])
|
|
|
|
# Function to check if a ship is sunk
|
|
def check_sunk(board, hits, ship_name):
|
|
ship_positions = []
|
|
for i, ship in enumerate(board):
|
|
if ship == ship_name:
|
|
ship_positions.append(i)
|
|
for pos in ship_positions:
|
|
if pos not in hits:
|
|
return False
|
|
return True
|
|
|
|
# Function to draw a line across a sunken ship
|
|
def draw_line(ax, board, ship_name):
|
|
ship_positions = []
|
|
for i, ship in enumerate(board):
|
|
if ship == ship_name:
|
|
ship_positions.append(i)
|
|
if not ship_positions:
|
|
return
|
|
|
|
# Determine if the ship is horizontal or vertical
|
|
first_pos = ship_positions[0]
|
|
last_pos = ship_positions[-1]
|
|
if last_pos - first_pos < 10: # Horizontal
|
|
x_start, y_start = first_pos % 10 + 0.5, 9 - first_pos // 10 + 0.5
|
|
x_end, y_end = last_pos % 10 + 0.5, 9 - last_pos // 10 + 0.5
|
|
else: # Vertical
|
|
x_start, y_start = first_pos % 10 + 0.5, 9 - first_pos // 10 + 0.5
|
|
x_end, y_end = first_pos % 10 + 0.5, 9 - last_pos // 10 + 0.5
|
|
|
|
ax.plot([x_start, x_end], [y_start, y_end], color='red', linewidth=2)
|
|
|
|
# Function to update probability matrix
|
|
def update_probability(x, y, hit):
|
|
global probability_matrix, hits, misses, sunk_ships, ship_sizes
|
|
|
|
if hit:
|
|
hits.append((x, y))
|
|
probability_matrix[y][x] = 0 # Mark hit
|
|
# Increase probabilities for adjacent cells
|
|
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
|
|
nx, ny = x + dx, y + dy
|
|
if 0 <= nx < 10 and 0 <= ny < 10 and probability_matrix[ny][nx] > 0:
|
|
probability_matrix[ny][nx] += 5 # Increase probability significantly
|
|
else:
|
|
misses.append((x, y))
|
|
probability_matrix[y][x] = -1 # Mark miss
|
|
|
|
# Set probabilities to 1 for cells that can't fit any remaining ships
|
|
max_ship_size = max(size for ship, size in ship_sizes.items() if ship not in sunk_ships)
|
|
for y in range(10):
|
|
for x in range(10):
|
|
if probability_matrix[y][x] > 0 and not can_fit_ship(x, y, max_ship_size):
|
|
probability_matrix[y][x] = 1 # Minimum probability
|
|
|
|
# Function to check if a ship can fit
|
|
def can_fit_ship(x, y, ship_size):
|
|
# Check horizontal fit
|
|
if x + ship_size <= 10:
|
|
fit = True
|
|
for i in range(ship_size):
|
|
if probability_matrix[y][x+i] <= 0:
|
|
fit = False
|
|
break
|
|
if fit:
|
|
return True
|
|
# Check vertical fit
|
|
if y + ship_size <= 10:
|
|
fit = True
|
|
for i in range(ship_size):
|
|
if probability_matrix[y+i][x] <= 0:
|
|
fit = False
|
|
break
|
|
if fit:
|
|
return True
|
|
return False
|
|
|
|
# AI chooses a shot
|
|
def choose_ai_shot():
|
|
global can_fit_ship, update_probability, generate_hunt_targets, random_search, probability_matrix, ai_mode, ai_shots, random, user_board, ai_hits, ai_hit_result
|
|
|
|
if ai_mode == "super_hunter":
|
|
# Use probabilistic grid algorithm
|
|
max_prob = 0
|
|
candidates = []
|
|
for i in range(100):
|
|
x, y = i % 10, i // 10
|
|
if probability_matrix[y][x] > max_prob:
|
|
max_prob = probability_matrix[y][x]
|
|
candidates = [i]
|
|
elif probability_matrix[y][x] == max_prob:
|
|
candidates.append(i)
|
|
ai_shot = random.choice(candidates)
|
|
elif ai_mode == "hunter":
|
|
# Simple hunter mode logic
|
|
if hits:
|
|
# Target adjacent cells of the last hit
|
|
last_hit = hits[-1]
|
|
hunt_targets = generate_hunt_targets(last_hit, ai_hits)
|
|
if hunt_targets:
|
|
ai_shot = hunt_targets.pop(0)
|
|
else:
|
|
ai_shot = random_search()
|
|
else:
|
|
ai_shot = random_search()
|
|
else:
|
|
# Random mode
|
|
ai_shot = random_search()
|
|
|
|
# Update AI state after the shot
|
|
if user_board[ai_shot] != -1:
|
|
ai_hits.append(ai_shot)
|
|
ai_hit_result = "hit"
|
|
if ai_mode == "super_hunter":
|
|
update_probability(ai_shot % 10, ai_shot // 10, True)
|
|
else:
|
|
ai_hit_result = "miss"
|
|
if ai_mode == "super_hunter":
|
|
update_probability(ai_shot % 10, ai_shot // 10, False)
|
|
|
|
return ai_shot
|
|
|
|
# Function for random search
|
|
def random_search():
|
|
available_positions = []
|
|
for i in range(100):
|
|
if i not in ai_shots:
|
|
available_positions.append(i)
|
|
return random.choice(available_positions)
|
|
|
|
# Function to generate hunt targets around a hit
|
|
def generate_hunt_targets(hit_position, ai_hits):
|
|
potential_targets = []
|
|
row, col = divmod(hit_position, 10)
|
|
|
|
# Up
|
|
if row > 0:
|
|
potential_targets.append(hit_position - 10)
|
|
# Down
|
|
if row < 9:
|
|
potential_targets.append(hit_position + 10)
|
|
# Left
|
|
if col > 0:
|
|
potential_targets.append(hit_position - 1)
|
|
# Right
|
|
if col < 9:
|
|
potential_targets.append(hit_position + 1)
|
|
|
|
# Filter out already hit positions
|
|
filtered_targets = []
|
|
for pos in potential_targets:
|
|
if pos not in ai_hits:
|
|
filtered_targets.append(pos)
|
|
return filtered_targets
|
|
|
|
# Get the user's shot
|
|
try:
|
|
user_shot = int(metadata.get("user_shot"))
|
|
except (IndexError, ValueError) as e:
|
|
user_shot = -1
|
|
|
|
if game_over:
|
|
script_result = {}
|
|
elif 0 <= user_shot < 100 and user_shot not in user_shots:
|
|
# The move is valid
|
|
user_shots.append(user_shot)
|
|
user_hit_result = "miss"
|
|
if ai_board[user_shot] != -1:
|
|
user_hits.append(user_shot)
|
|
user_hit_result = "hit"
|
|
|
|
# AI makes a move
|
|
ai_shot = choose_ai_shot()
|
|
ai_shots.append(ai_shot)
|
|
|
|
# Check if any AI ship is sunk
|
|
for ship_name in ship_sizes.keys():
|
|
if check_sunk(ai_board, user_hits, ship_name) and ship_name not in user_sunk_ships:
|
|
user_sunk_ships.append(ship_name)
|
|
user_sunk_ship_this_round = ship_name
|
|
|
|
# Check if any User ship is sunk
|
|
for ship_name in ship_sizes.keys():
|
|
if check_sunk(user_board, ai_hits, ship_name) and ship_name not in ai_sunk_ships:
|
|
ai_sunk_ships.append(ship_name)
|
|
ai_sunk_ship_this_round = ship_name
|
|
|
|
# Check if all AI ships are hit
|
|
all_ai_ships_hit = True
|
|
for pos in range(100):
|
|
if ai_board[pos] != -1 and pos not in user_hits:
|
|
all_ai_ships_hit = False
|
|
break
|
|
if all_ai_ships_hit:
|
|
game_over = True
|
|
user_wins = True
|
|
ai_wins = False
|
|
|
|
# Check if all User ships are hit
|
|
all_user_ships_hit = True
|
|
for pos in range(100):
|
|
if user_board[pos] != -1 and pos not in ai_hits:
|
|
all_user_ships_hit = False
|
|
break
|
|
if all_user_ships_hit:
|
|
game_over = True
|
|
user_wins = False
|
|
ai_wins = True
|
|
|
|
# Plot the boards
|
|
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
|
|
fig.suptitle("Battleship", fontsize=16)
|
|
|
|
# User's view of AI's board
|
|
axs[0].set_xlim(0, 10)
|
|
axs[0].set_ylim(0, 10)
|
|
axs[0].set_xticks([])
|
|
axs[0].set_yticks([])
|
|
axs[0].grid(True)
|
|
axs[0].set_title("Your Shots", fontsize=12)
|
|
|
|
# Plot user shots on AI's board
|
|
for i in range(100):
|
|
x, y = i % 10, 9 - i // 10
|
|
if i in user_shots:
|
|
if i in user_hits:
|
|
axs[0].text(x + 0.5, y + 0.5, 'X', fontsize=12, ha='center', va='center', color='red')
|
|
else:
|
|
axs[0].text(x + 0.5, y + 0.5, 'O', fontsize=12, ha='center', va='center', color='black')
|
|
axs[0].text(x + 0.5, y + 0.5, str(i), fontsize=8, ha='center', va='center', color='gray')
|
|
|
|
# AI's view of User's board
|
|
axs[1].set_xlim(0, 10)
|
|
axs[1].set_ylim(0, 10)
|
|
axs[1].set_xticks([])
|
|
axs[1].set_yticks([])
|
|
axs[1].grid(True)
|
|
axs[1].set_title("Your Ships", fontsize=12)
|
|
|
|
# Plot user ships
|
|
for i, ship in enumerate(user_board):
|
|
x, y = i % 10, 9 - i // 10
|
|
if ship != -1:
|
|
axs[1].add_patch(plt.Rectangle((x, y), 1, 1, color=ship_colors[ship], alpha=0.5))
|
|
|
|
# Plot AI shots on User's board
|
|
for i in range(100):
|
|
x, y = i % 10, 9 - i // 10
|
|
if i in ai_shots:
|
|
if i in ai_hits:
|
|
axs[1].text(x + 0.5, y + 0.5, 'X', fontsize=12, ha='center', va='center', color='red')
|
|
else:
|
|
axs[1].text(x + 0.5, y + 0.5, 'O', fontsize=12, ha='center', va='center', color='black')
|
|
axs[1].text(x + 0.5, y + 0.5, str(i), fontsize=8, ha='center', va='center', color='gray')
|
|
|
|
# Draw lines across sunk ships
|
|
for ship_name in user_sunk_ships:
|
|
draw_line(axs[0], ai_board, ship_name)
|
|
|
|
for ship_name in ai_sunk_ships:
|
|
draw_line(axs[1], user_board, ship_name)
|
|
|
|
# Add legend
|
|
handles = []
|
|
for color in ship_colors.values():
|
|
handles.append(plt.Rectangle((0, 0), 1, 1, color=color, alpha=0.5))
|
|
axs[1].legend(handles, ship_colors.keys(), loc='upper right', fontsize=8)
|
|
|
|
buf = io.BytesIO()
|
|
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1)
|
|
plt.close(fig)
|
|
buf.seek(0)
|
|
plot_image = base64.b64encode(buf.getvalue()).decode('utf-8')
|
|
|
|
# gpt-4: If "plot_image" is in the result, set it as the background image
|
|
script_result = {
|
|
"plot_image": plot_image,
|
|
"set_background": True,
|
|
"metadata": {
|
|
"user_board": user_board,
|
|
"ai_board": ai_board,
|
|
"user_shot": user_shot,
|
|
"ai_shot": ai_shot,
|
|
"user_shots": user_shots,
|
|
"ai_shots": ai_shots,
|
|
"user_hits": user_hits,
|
|
"ai_hits": ai_hits,
|
|
"game_over": game_over,
|
|
"user_wins": user_wins,
|
|
"ai_wins": ai_wins,
|
|
"user_hit_result": user_hit_result,
|
|
"ai_hit_result": ai_hit_result,
|
|
"user_sunk_ships": user_sunk_ships,
|
|
"ai_sunk_ships": ai_sunk_ships,
|
|
"user_sunk_ship_this_round": user_sunk_ship_this_round,
|
|
"ai_sunk_ship_this_round": ai_sunk_ship_this_round,
|
|
"ai_mode": ai_mode,
|
|
"probability_matrix": probability_matrix,
|
|
"hits": hits,
|
|
"misses": misses,
|
|
"sunk_ships": sunk_ships
|
|
}
|
|
}
|
|
else:
|
|
script_result = {
|
|
"error": f"Invalid shot: {metadata.get('user_shot')}",
|
|
"metadata": {}
|
|
}
|
|
|
|
buckets:
|
|
- valid_move
|
|
- invalid_move
|
|
- exit
|
|
- restart
|
|
transitions:
|
|
valid_move:
|
|
run_processing_script: True
|
|
ai_feedback:
|
|
tokens_for_ai: |
|
|
The user shot seems valid.
|
|
metadata_tmp_add:
|
|
user_shot: "the-users-response"
|
|
next_section_and_step: "section_1:step_2"
|
|
invalid_move:
|
|
content_blocks:
|
|
- "That move is invalid. Please choose a position between 0 and 99."
|
|
metadata_tmp_add:
|
|
user_shot: "the-users-response"
|
|
next_section_and_step: "section_1:step_2"
|
|
exit:
|
|
next_section_and_step: "section_1:step_3"
|
|
restart:
|
|
content_blocks:
|
|
- "Restarting the game. Let's start fresh!"
|
|
metadata_clear: True
|
|
next_section_and_step: "section_1:step_0"
|
|
|
|
- step_id: "step_3"
|
|
title: "Goodbye"
|
|
content_blocks:
|
|
- "Thank you for playing Battleship! 🎉"
|
|
- "Feel free to come back anytime for another game."
|