In this mode the AI will switch from random to hunting all the positions around the latest hit. It's still not as smart as a human but you will start to feel hunted as the game progresses versus the other game mode. modified: research/activity29-battleship.yaml
444 lines
18 KiB
YAML
444 lines
18 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: "Do you want to enable Hunter Mode for the AI? (yes/no)"
|
|
tokens_for_ai: |
|
|
If the user wants Hunter Mode, categorize as 'enable_hunter_mode'.
|
|
If the user does not want Hunter Mode, categorize as 'disable_hunter_mode'.
|
|
feedback_tokens_for_ai: |
|
|
If the user chooses Hunter Mode, acknowledge the choice.
|
|
If the user does not choose Hunter Mode, 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:
|
|
- enable_hunter_mode
|
|
- disable_hunter_mode
|
|
transitions:
|
|
enable_hunter_mode:
|
|
run_processing_script: True
|
|
ai_feedback:
|
|
tokens_for_ai: "Hunter Mode enabled for the AI."
|
|
metadata_add:
|
|
hunter_mode: true
|
|
next_section_and_step: "section_1:step_2"
|
|
disable_hunter_mode:
|
|
run_processing_script: True
|
|
ai_feedback:
|
|
tokens_for_ai: "Hunter Mode disabled for the AI."
|
|
metadata_add:
|
|
hunter_mode: false
|
|
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 colors for ships
|
|
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
|
|
hunter_mode = metadata.get("hunter_mode", False)
|
|
hunt_mode_active = metadata.get("hunt_mode_active", False)
|
|
hunt_targets = metadata.get("hunt_targets", [])
|
|
last_hit = metadata.get("last_hit", None)
|
|
direction = metadata.get("direction", None)
|
|
|
|
# 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 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
|
|
|
|
# AI chooses a shot
|
|
def choose_ai_shot():
|
|
global generate_hunt_targets, hunter_mode, hunt_mode_active, hunt_targets, last_hit, direction, ai_shots, random, user_board, ai_hits, ai_hit_result
|
|
|
|
if hunt_mode_active and hunt_targets:
|
|
# Choose the next target from hunt targets
|
|
ai_shot = hunt_targets.pop(0)
|
|
else:
|
|
# Randomly select a position from available positions
|
|
available_positions = []
|
|
for i in range(100):
|
|
if i not in ai_shots:
|
|
available_positions.append(i)
|
|
ai_shot = random.choice(available_positions)
|
|
|
|
# Update AI state after the shot
|
|
if user_board[ai_shot] != -1:
|
|
ai_hits.append(ai_shot)
|
|
ai_hit_result = "hit"
|
|
|
|
# Switch to hunt mode
|
|
hunt_mode_active = True
|
|
last_hit = ai_shot
|
|
hunt_targets.extend(generate_hunt_targets(last_hit, ai_hits))
|
|
|
|
# Confirm direction if not already set
|
|
if direction is None and last_hit is not None:
|
|
if ai_shot == last_hit + 1 or ai_shot == last_hit - 1:
|
|
direction = "horizontal"
|
|
elif ai_shot == last_hit + 10 or ai_shot == last_hit - 10:
|
|
direction = "vertical"
|
|
else:
|
|
ai_hit_result = "miss"
|
|
|
|
return ai_shot
|
|
|
|
# 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 colors.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 colors.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=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 colors.values():
|
|
handles.append(plt.Rectangle((0, 0), 1, 1, color=color, alpha=0.5))
|
|
axs[1].legend(handles, 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,
|
|
"hunter_mode": hunter_mode,
|
|
"hunt_mode_active": hunt_mode_active,
|
|
"hunt_targets": hunt_targets,
|
|
"last_hit": last_hit,
|
|
"direction": direction
|
|
}
|
|
}
|
|
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."
|