opencompletion.com/research/activity29-battleship.yaml
Russell Ballestrini e28dc11f04 Improve user experience with battleship feedback and auto-play TTS
- Fix battleship feedback perspective confusion with better Hermes prompting
- Add auto-play TTS button with localStorage persistence and queueing system
- Move activity controls below model/voice selectors in sidebar
- Add activity controls to mobile hamburger menu
- Fix model/activity dropdowns to stay within container bounds
- Filter activities API to only show .yaml/.yml files
- Clean up system message labels by moving to usernames (System (Feedback), System (Question))
- Apply black formatting to app.py
2025-08-11 09:34:29 -04:00

901 lines
42 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

default_max_attempts_per_step: 9
tokens_for_ai_rubric: |
based on the game without knowing where each ship was, score the process each player used to target ships.
be sure to look for moves or strategies in the game play that where _not_ smart given the obvious information uncovered.
use chain-of-thought to reason about the progression of the game and the winner.
first summarize the game, we don't need the turn by turn plays.
the game was battleship. the moves were done 1 by 1.
the grid is 0-99.
did any player blunder as the information was learned?
There was a user and an AI playing.
Depending on the game mode the player chooses they are going up against a different algo,
* random
* always plays randomly
* hunter
* keeps track of hits and targets every cell around it no matter what, randomly, else random
* super human hunter
* keeps track of hits and uses a probability grid normalized to 100 and always picks the max or random of any 100.
* hermes reasoner
* uses the probability algorithm paired with hermes to reason for 3 sentences about what the next 0-99 move should be given the current game state and turn number
Did any player miss sinking a ship that was found? was it due to end game or a blunder?
Do not mix up ships, keep careful track of the order they were found and sunk.
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, Super Human Hunter, or Hermes Reasoner?"
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'.
If the user chooses Hermes Reasoner, categorize as 'hermes_reasoner_mode'.
feedback_tokens_for_ai: |
If the user chooses Random, say: "Random mode selected! The AI will make completely random moves."
If the user chooses Hunter, say: "Hunter mode selected! The AI will systematically hunt around hits."
If the user chooses Super Human Hunter, say: "Super Human Hunter mode selected! The AI will use advanced probability analysis."
If the user chooses Hermes Reasoner, say: "Hermes Reasoner mode selected! The AI will use probability analysis combined with reasoning to make strategic decisions."
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() # AI also gets randomly placed ships
script_result = {
"metadata": {
"user_board": user_board,
"ai_board": ai_board
}
}
buckets:
- random_mode
- hunter_mode
- super_hunter_mode
- hermes_reasoner_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"
hermes_reasoner_mode:
run_processing_script: True
ai_feedback:
tokens_for_ai: "Hermes Reasoner Mode enabled for the AI. The AI will use probability analysis combined with reasoning to make strategic decisions."
metadata_add:
ai_mode: "hermes_reasoner"
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)."
pre_script: |
# Check if moves match winning moves from previous turn
user_winning_move = metadata.get("user_winning_move")
ai_winning_move = metadata.get("ai_winning_move")
user_shot_input = metadata.get("user_response", "")
# print(f"PRE-SCRIPT DEBUG: user_shot_input = '{user_shot_input}', user_winning_move = {user_winning_move}, ai_winning_move = {ai_winning_move}")
ai_shot = metadata.get("ai_shot")
is_game_ending_move = False
# Check if user move wins
if user_shot_input and user_shot_input.isdigit():
user_move = int(user_shot_input)
if user_winning_move is not None and user_move == user_winning_move:
is_game_ending_move = True
# print(f"PRE-SCRIPT: User winning move detected! user_move={user_move} matches user_winning_move={user_winning_move}")
# Check if AI move wins (from previous turn)
if ai_shot is not None and ai_winning_move is not None and ai_shot == ai_winning_move:
is_game_ending_move = True
# print(f"PRE-SCRIPT: AI winning move detected! ai_shot={ai_shot} matches ai_winning_move={ai_winning_move}")
script_result = {
"metadata": {
"is_game_ending_move": is_game_ending_move
}
}
tokens_for_ai: |
1) If the user reply is *only* digits, and corresponds to a grid cell (099),
treat it as a valid move:
If the response matches the regex /^\d+$/ and 0 ≤ int(response) < 100, categorize as 'valid_move'.
2) Otherwise fall back to the usual buckets:
If the user wants to restart or play again, categorize as 'restart'.
If the user wants to exit, categorize as 'exit'.
Otherwise, categorize as 'invalid_move'.
feedback_tokens_for_ai: |
You are the naval battle narrator. Look at the metadata provided and report what happened.
STEP 1 - CHECK SHIP DESTRUCTION (MANDATORY):
Look in the metadata for these exact fields:
- user_sunk_ship_this_round: If this contains a ship name like "Carrier" or "Battleship", say: "💥 SHIP DESTROYED! You have sunk the enemy's [ship name]! The enemy vessel explodes and sinks! Victory!"
- ai_sunk_ship_this_round: If this contains a ship name, say: "🔥 YOUR SHIP SUNK! The enemy destroyed your [ship name]! Your vessel burns and sinks!"
STEP 2 - REPORT SHOTS:
- Your shot result (user_hit_result): "hit" or "miss"
- Enemy shot result (ai_hit_result): "hit" or "miss"
EXAMPLE RESPONSE FORMAT:
If user_sunk_ship_this_round = "Carrier": "💥 SHIP DESTROYED! You have sunk the enemy's Carrier! [shot details]"
If ai_sunk_ship_this_round = "Destroyer": "🔥 YOUR SHIP SUNK! The enemy destroyed your Destroyer! [shot details]"
Always check the metadata for user_sunk_ship_this_round and ai_sunk_ship_this_round first. These are the most important events to report.
processing_script: |
import random
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import io
import base64
import requests
import json
# 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")
# Normal processing code
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")
# Initialize probability matrix with realistic ship placement probabilities
if "probability_matrix" not in metadata:
probability_matrix = [[0] * 10 for _ in range(10)]
# Calculate how many ship placements use each cell
ship_lengths = [5, 4, 3, 3, 2]
for y in range(10):
for x in range(10):
count = 0
for ship_len in ship_lengths:
# Horizontal ships that would cover this cell
for start_x in range(max(0, x - ship_len + 1), min(x + 1, 10 - ship_len + 1)):
count += 1
# Vertical ships that would cover this cell
for start_y in range(max(0, y - ship_len + 1), min(y + 1, 10 - ship_len + 1)):
count += 1
probability_matrix[y][x] = count
# print("DEBUG: Initial probability matrix created")
# Debug print the initial grid
# print("DEBUG: Initial grid:")
# for row in probability_matrix:
# print(f" {' '.join(f'{x:2d}' for x in row)}")
else:
probability_matrix = metadata.get("probability_matrix")
# print("DEBUG: Using existing probability matrix")
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
# Function to generate Hermes reasoning
def hermes_reason_move(game_state, turn_number, top_candidates):
global ai_hits, ai_shots, ai_sunk_ships, probability_matrix
import os
import requests
import json
# Get Hermes endpoint from environment
hermes_endpoint = os.environ.get('MODEL_ENDPOINT_1', 'https://hermes.ai.unturf.com/v1')
hermes_api_key = os.environ.get('MODEL_API_KEY_1', '')
# Prepare game state summary
hits_summary = f"AI hits so far: {len(ai_hits)} positions hit"
misses_summary = f"AI misses so far: {len(ai_shots) - len(ai_hits)} positions missed"
sunk_ships_summary = f"Ships sunk: {len(ai_sunk_ships)} out of 5"
available_positions = [i for i in range(100) if i not in ai_shots]
top_six_candidates = top_candidates[0:6] if len(top_candidates) >= 6 else top_candidates
# Create reasoning prompt
prompt = (
f"You are an expert Battleship AI. Turn {turn_number}.\n\n"
f"CRITICAL: You MUST choose from these TOP probability positions: {top_six_candidates}\n\n"
f"Game Data:\n"
f"- {hits_summary}\n"
f"- {misses_summary}\n"
f"- {sunk_ships_summary}\n\n"
f"INSTRUCTIONS: Pick ONE number from {top_six_candidates} - these are the mathematically optimal targets.\n\n"
f"Format your response EXACTLY like this:\n\n"
f"ANALYSIS: [3 sentences explaining why you chose from the top probability positions]\n\n"
f"MOVE: [ONE number from this list: {top_six_candidates}]\n\n"
f"You MUST pick from {top_six_candidates} - do not pick any other number."
)
try:
headers = {
'Authorization': f'Bearer {hermes_api_key}',
'Content-Type': 'application/json'
}
data = {
'model': 'adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic',
'messages': [{'role': 'user', 'content': prompt}],
'max_tokens': 300,
'temperature': 0.5
}
response = requests.post(f'{hermes_endpoint}/chat/completions',
headers=headers, json=data, timeout=10)
# print(f"DEBUG: API Status: {response.status_code}")
if response.status_code == 200:
result = response.json()
reasoning = result['choices'][0]['message']['content'].strip()
# print(f"DEBUG: Real API response: {reasoning}")
return reasoning
else:
# print(f"DEBUG: API failed with status {response.status_code}: {response.text[:200]}")
fallback_move = top_candidates[0] if top_candidates else random.choice([i for i in range(100) if i not in ai_shots])
return f"ANALYSIS: Turn {turn_number} suggests targeting high-probability zones based on mathematical analysis. The current hit pattern indicates potential ship orientations that guide strategic decisions. Focusing on adjacent unexplored cells maximizes discovery potential.\n\nMOVE: {fallback_move}"
except Exception as e:
# print(f"DEBUG: API exception: {str(e)}")
fallback_move = top_candidates[0] if top_candidates else random.choice([i for i in range(100) if i not in ai_shots])
return f"ANALYSIS: After {turn_number} turns, probability analysis guides optimal targeting strategies. Current data suggests focusing on clustered high-value positions for maximum efficiency. Strategic patience combined with mathematical precision will yield victory.\n\nMOVE: {fallback_move}"
# 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, hermes_reason_move, ship_sizes, ai_sunk_ships
if ai_mode == "hermes_reasoner":
# Use probability algorithm + Hermes reasoning
# Update probability matrix based on shots
remaining_ships = [ship for ship in ship_sizes.keys() if ship not in ai_sunk_ships]
remaining_ship_sizes = [ship_sizes[ship] for ship in remaining_ships]
# print(f"DEBUG: Remaining ships: {remaining_ships}")
# print(f"DEBUG: Total shots: {len(ai_shots)}, Hits: {len(ai_hits)}, Misses: {len(ai_shots) - len(ai_hits)}")
# Recalculate entire probability matrix
new_probability_matrix = [[0] * 10 for _ in range(10)]
for y in range(10):
for x in range(10):
pos = y * 10 + x
if pos in ai_shots:
new_probability_matrix[y][x] = 0 # Already shot
else:
# Count how many ship placements could use this cell
for ship_size in remaining_ship_sizes:
# Check horizontal placements
for start_x in range(max(0, x - ship_size + 1), min(x + 1, 10 - ship_size + 1)):
valid = True
includes_hit = False
for dx in range(ship_size):
check_pos = y * 10 + (start_x + dx)
if check_pos in ai_shots and check_pos not in ai_hits:
valid = False # Ship can't go through a miss
break
if check_pos in ai_hits:
includes_hit = True
if valid:
# Base probability for valid placement
new_probability_matrix[y][x] += 1
# Bonus if it includes a hit
if includes_hit:
new_probability_matrix[y][x] += 10
# Check vertical placements
for start_y in range(max(0, y - ship_size + 1), min(y + 1, 10 - ship_size + 1)):
valid = True
includes_hit = False
for dy in range(ship_size):
check_pos = (start_y + dy) * 10 + x
if check_pos in ai_shots and check_pos not in ai_hits:
valid = False # Ship can't go through a miss
break
if check_pos in ai_hits:
includes_hit = True
if valid:
# Base probability for valid placement
new_probability_matrix[y][x] += 1
# Bonus if it includes a hit
if includes_hit:
new_probability_matrix[y][x] += 10
# Replace the old matrix with the new one
probability_matrix = new_probability_matrix
# Boost probabilities around unsunk hits
for hit_pos in ai_hits:
hit_x, hit_y = hit_pos % 10, hit_pos // 10
# Check if this hit is part of a sunk ship
hit_is_sunk = False
for ship_name in ai_sunk_ships:
# This would need ship position tracking to work properly
pass # Skip for now, assume all hits need chasing
if not hit_is_sunk:
# Boost adjacent cells
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
adj_x, adj_y = hit_x + dx, hit_y + dy
if 0 <= adj_x < 10 and 0 <= adj_y < 10:
adj_pos = adj_y * 10 + adj_x
if adj_pos not in ai_shots:
# Only boost if not already boosted
if probability_matrix[adj_y][adj_x] < 50:
probability_matrix[adj_y][adj_x] = 50 # Set to fixed high value instead of adding
# Find top 6 highest probability positions
position_probs = []
for i in range(100):
if i not in ai_shots: # Only consider unshot positions
x, y = i % 10, i // 10
position_probs.append((probability_matrix[y][x], i))
# Sort by probability (descending) and take top positions
position_probs.sort(reverse=True)
candidates = [pos for prob, pos in position_probs[:20]] # Take top 20 for variety
max_prob = position_probs[0][0] if position_probs else 0
# Fallback if no candidates found
if not candidates:
candidates = [i for i in range(100) if i not in ai_shots]
# Debug: Log what we're working with
turn_number = len(ai_shots) + 1
# print(f"DEBUG: Turn {turn_number}, Max prob: {max_prob}")
# print("DEBUG: Probability grid:")
# for y in range(10):
# row = [f"{probability_matrix[y][x]:2d}" for x in range(10)]
# print(f" {' '.join(row)}")
# print(f"DEBUG: Top candidates: {candidates[:10]}")
reasoning_response = hermes_reason_move("battleship", turn_number, candidates)
# Analysis already logged in hermes_reason_move function
# Extract move from response - try multiple parsing methods
try:
if "MOVE:" in reasoning_response:
move_part = reasoning_response.split("MOVE:")[1].strip()
ai_shot = int(move_part.split()[0])
# print(f"DEBUG: Hermes Move: {ai_shot} (from 'MOVE: {move_part.split()[0]}')")
else:
# Fallback: extract any number from the response that's in candidates
import re
numbers = re.findall(r'\b(\d+)\b', reasoning_response)
valid_moves = [int(n) for n in numbers if int(n) in candidates and int(n) not in ai_shots]
if valid_moves:
ai_shot = valid_moves[0]
# print(f"DEBUG: Hermes Move (parsed): {ai_shot} from numbers {numbers}")
else:
raise Exception(f"ERROR: Hermes response had no valid moves! Response: {reasoning_response}, Candidates: {candidates}")
# Validate the shot is legal
if ai_shot in ai_shots or ai_shot < 0 or ai_shot > 99:
ai_shot = random.choice(candidates)
# print(f"DEBUG: Invalid shot, using fallback: {ai_shot}")
except Exception as e:
ai_shot = random.choice(candidates)
# print(f"DEBUG: Parse error: {e}, using fallback: {ai_shot}")
elif ai_mode == "super_hunter":
# Use probabilistic grid algorithm
max_prob = 0
candidates = []
for i in range(100):
if i not in ai_shots: # Exclude already-fired cells
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_shots)
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" or ai_mode == "hermes_reasoner":
update_probability(ai_shot % 10, ai_shot // 10, True)
else:
ai_hit_result = "miss"
if ai_mode == "super_hunter" or ai_mode == "hermes_reasoner":
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_shots):
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 fired positions
filtered_targets = []
for pos in potential_targets:
if pos not in ai_shots:
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 = {
"metadata": {
"game_over": True,
"user_wins": user_wins,
"ai_wins": ai_wins
}
}
# print(f"DEBUG: Game over detected! User wins: {user_wins}, AI wins: {ai_wins}")
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
# print(f"DEBUG: USER SUNK AI SHIP: {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
# print(f"DEBUG: AI SUNK USER SHIP: {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
# 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_ai_ships_hit:
game_over = True
user_wins = True
ai_wins = False
# print(f"DEBUG: USER WINS! All AI ships destroyed. Game over.")
elif all_user_ships_hit:
game_over = True
user_wins = False
ai_wins = True
# print(f"DEBUG: AI WINS! All user ships destroyed. Game over.")
# Only track winning move if there's exactly 1 position left (for next turn's categorization)
user_winning_move = None
ai_winning_move = None
# Check which user move would win the game (AI ship positions left)
ai_ship_positions_left = [pos for pos in range(100) if ai_board[pos] != -1 and pos not in user_hits]
if len(ai_ship_positions_left) == 1:
user_winning_move = ai_ship_positions_left[0]
# print(f"DEBUG: User has exactly 1 winning move at position {user_winning_move}")
else:
# print(f"DEBUG: User has {len(ai_ship_positions_left)} AI positions left - no winning move")
pass
# Check which AI move would win the game (user ship positions left)
user_ship_positions_left = [pos for pos in range(100) if user_board[pos] != -1 and pos not in ai_hits]
if len(user_ship_positions_left) == 1:
ai_winning_move = user_ship_positions_left[0]
# print(f"DEBUG: AI has exactly 1 winning move at position {ai_winning_move}")
else:
# print(f"DEBUG: AI has {len(user_ship_positions_left)} user positions left - no winning move")
pass
# 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
# print(f"DEBUG: Setting metadata for feedback - user_sunk_ship_this_round: {user_sunk_ship_this_round}, ai_sunk_ship_this_round: {ai_sunk_ship_this_round}")
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,
"user_winning_move": user_winning_move,
"ai_winning_move": ai_winning_move
}
}
# Check if this was a winning move and override transition
if game_over:
script_result["next_section_and_step"] = "section_1:step_3"
# print(f"POST-SCRIPT: Game over detected, overriding transition to step_3")
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"
metadata_feedback_filter:
- user_hit_result
- ai_hit_result
- ai_shot
- user_shot
- user_sunk_ship_this_round
- ai_sunk_ship_this_round
- game_over
- user_wins
- ai_wins
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_4"
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: "Game Over"
question: "Would you like to restart and play again, or would you prefer to exit?"
tokens_for_ai: |
If the user wants to restart or play again, categorize as 'restart'.
If the user wants to exit, categorize as 'exit'.
feedback_tokens_for_ai: |
Acknowledge the user's choice appropriately.
buckets:
- restart
- exit
transitions:
restart:
content_blocks:
- "Restarting the game. Let's start fresh!"
metadata_clear: True
next_section_and_step: "section_1:step_0"
exit:
content_blocks:
- "Thank you for playing Battleship! 🎉"
- "Feel free to come back anytime for another game."
next_section_and_step: "section_1:step_4"
- step_id: "step_4"
title: "Goodbye"
content_blocks:
- "Thanks for playing! Hope you enjoyed the battle at sea."