Remove prescriptive ship destruction descriptions and let the AI be creative. Since skip_condition ensures these prompts only run when ships are actually destroyed, we can make the prompts more concise and focused on the outcome.
924 lines
43 KiB
YAML
924 lines
43 KiB
YAML
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 (0–99),
|
||
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 a battleship narrator. Each prompt has its own specific role - follow the individual prompt instructions precisely.
|
||
feedback_prompts:
|
||
- name: "Shot Report"
|
||
tokens_for_ai: |
|
||
🎯 Report ONLY the hit/miss results for both shots this turn. DO NOT report ship sinking or game over.
|
||
|
||
Check metadata:
|
||
- user_shot: Player's target position
|
||
- user_hit_result: "hit" or "miss"
|
||
- ai_shot: AI's target position
|
||
- ai_hit_result: "hit" or "miss"
|
||
|
||
Format: "🎯 Your shot at position [user_shot]: [user_hit_result]! 🤖 Enemy shot at position [ai_shot]: [ai_hit_result]!"
|
||
metadata_filter:
|
||
- user_shot
|
||
- ai_shot
|
||
- user_hit_result
|
||
- ai_hit_result
|
||
- user_response
|
||
|
||
- name: "Ship Status"
|
||
tokens_for_ai: |
|
||
A ship has been destroyed! Generate a dramatic 2-3 sentence description.
|
||
|
||
Metadata tells you which ship(s) were sunk:
|
||
- user_sunk_ship_this_round: The ship YOU destroyed (your victory)
|
||
- ai_sunk_ship_this_round: The ship that was destroyed by the enemy (your loss)
|
||
|
||
Format:
|
||
- If user destroyed a ship: "💥 You've sunk their [ship]! [2-3 dramatic sentences imagining how this warship meets its doom]"
|
||
- If AI destroyed your ship: "🔥 Your [ship] has been destroyed! [2-3 dramatic sentences about its destruction]"
|
||
- If both: Include both messages
|
||
metadata_filter:
|
||
- user_sunk_ship_this_round
|
||
- ai_sunk_ship_this_round
|
||
skip_condition: "all_null"
|
||
|
||
- name: "Game Over"
|
||
tokens_for_ai: |
|
||
The naval battle has ended! Generate an epic conclusion.
|
||
|
||
Based on the metadata:
|
||
- If user_wins is true: "🎉 TOTAL VICTORY! You have destroyed all enemy ships! The seas belong to you, Admiral. Your tactical brilliance has secured complete naval supremacy."
|
||
- If ai_wins is true: "💀 DEFEAT! All your ships have been destroyed. Your fleet lies scattered across the ocean floor. The enemy's superior strategy has prevailed."
|
||
|
||
Make it dramatic and final - this is the end of the battle!
|
||
metadata_filter:
|
||
- game_over
|
||
- user_wins
|
||
- ai_wins
|
||
skip_condition: "all_false"
|
||
|
||
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"
|
||
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'.
|
||
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."
|