opencompletion.com/research/activity29-battleship.yaml
Russell Ballestrini 49cc545a4c Battleship Hunter Mode!
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
2024-09-07 13:18:42 -04:00

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."