Battleship board generator and samplers.
modified: ../../../../.gitignore new file: battleship_boards.py modified: battleship_genetic.py deleted: battleship_genetic2.py modified: battleship_learning.json modified: compare_battleship.sh new file: elites.json new file: sample_from_tarball.sh new file: sample_one_from_tarball.sh
This commit is contained in:
parent
ef127236e1
commit
e365749c37
9 changed files with 3220 additions and 2083 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,6 +5,8 @@ __pycache__/
|
|||
# C extensions
|
||||
*.so
|
||||
|
||||
*.tar.gz
|
||||
|
||||
.DS_Store
|
||||
|
||||
*.swp
|
||||
|
|
|
|||
63
content/uploads/2024/battleship-solvers/battleship_boards.py
Normal file
63
content/uploads/2024/battleship-solvers/battleship_boards.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import json
|
||||
import multiprocessing
|
||||
import random
|
||||
from tqdm import tqdm
|
||||
|
||||
# Define ship configurations
|
||||
ships = {"Carrier": 5, "Battleship": 4, "Cruiser": 3, "Submarine": 3, "Destroyer": 2}
|
||||
|
||||
|
||||
def place_ships(grid_size=100):
|
||||
"""Randomly place ships on a grid."""
|
||||
grid = [""] * grid_size
|
||||
for ship, size in ships.items():
|
||||
placed = False
|
||||
while not placed:
|
||||
orientation = random.choice(["horizontal", "vertical"])
|
||||
if orientation == "horizontal":
|
||||
start = random.randint(0, grid_size - size)
|
||||
if all(grid[start + i] == "" for i in range(size)):
|
||||
for i in range(size):
|
||||
grid[start + i] = ship
|
||||
placed = True
|
||||
else:
|
||||
start = random.randint(0, grid_size - size * 10)
|
||||
if all(grid[start + i * 10] == "" for i in range(size)):
|
||||
for i in range(size):
|
||||
grid[start + i * 10] = ship
|
||||
placed = True
|
||||
return grid
|
||||
|
||||
|
||||
def worker(num_boards, output_queue):
|
||||
"""Worker function to generate boards."""
|
||||
for _ in range(num_boards):
|
||||
board = place_ships()
|
||||
output_queue.put(board)
|
||||
|
||||
|
||||
def main(total_boards=1000000, num_processes=4):
|
||||
"""Main function to manage multiprocessing and progress tracking."""
|
||||
boards_per_process = total_boards // num_processes
|
||||
output_queue = multiprocessing.Queue()
|
||||
processes = []
|
||||
|
||||
for _ in range(num_processes):
|
||||
p = multiprocessing.Process(
|
||||
target=worker, args=(boards_per_process, output_queue)
|
||||
)
|
||||
processes.append(p)
|
||||
p.start()
|
||||
|
||||
with open("battleship_boards.json", "w") as f:
|
||||
for _ in tqdm(range(total_boards), desc="Generating Boards"):
|
||||
board = output_queue.get()
|
||||
json.dump(board, f)
|
||||
f.write("\n")
|
||||
|
||||
for p in processes:
|
||||
p.join()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -3,116 +3,229 @@ import json
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
from tqdm import tqdm
|
||||
from multiprocessing import Pool
|
||||
|
||||
GRID_SIZE = 10
|
||||
SHIPS = {"Carrier": 5, "Battleship": 4, "Cruiser": 3, "Submarine": 3, "Destroyer": 2}
|
||||
POPULATION_SIZE = 200
|
||||
GENERATIONS = 100
|
||||
MUTATION_RATE = 0.1
|
||||
GAMES_PER_INDIVIDUAL = 10
|
||||
GENERATIONS = 50
|
||||
BASE_MUTATION_RATE = 0.1
|
||||
GAMES_PER_INDIVIDUAL = 20
|
||||
LEARNING_FILE = "battleship_learning.json"
|
||||
|
||||
MUTATION_MULTIPLIERS = {
|
||||
"adjacent_hit_weight": 0.5,
|
||||
"ship_size_weight": 1.5,
|
||||
"parity_weight": 1.5,
|
||||
"hunt_mode_threshold": 1.0,
|
||||
"prob_grid_weight": 1.0,
|
||||
"avoid_edges_weight": 1.0,
|
||||
}
|
||||
|
||||
|
||||
class Board:
|
||||
def __init__(self):
|
||||
self.grid = [[None for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
|
||||
self.ships_left = set(SHIPS.keys())
|
||||
self.place_all_ships()
|
||||
|
||||
def place_all_ships(self):
|
||||
for ship, size in SHIPS.items():
|
||||
while True:
|
||||
row = random.randint(0, GRID_SIZE - 1)
|
||||
col = random.randint(0, GRID_SIZE - 1)
|
||||
horizontal = random.choice([True, False])
|
||||
if self.is_valid_placement(row, col, size, horizontal):
|
||||
self.place_ship(row, col, size, horizontal, ship)
|
||||
break
|
||||
|
||||
def is_valid_placement(self, row, col, size, horizontal):
|
||||
if horizontal:
|
||||
if col + size > GRID_SIZE:
|
||||
return False
|
||||
return all(self.grid[row][c] is None for c in range(col, col + size))
|
||||
else:
|
||||
if row + size > GRID_SIZE:
|
||||
return False
|
||||
return all(self.grid[r][col] is None for r in range(row, row + size))
|
||||
|
||||
def place_ship(self, row, col, size, horizontal, ship):
|
||||
if horizontal:
|
||||
for c in range(col, col + size):
|
||||
self.grid[row][c] = ship
|
||||
else:
|
||||
for r in range(row, row + size):
|
||||
self.grid[r][col] = ship
|
||||
|
||||
def is_sunk(self, ship, hits):
|
||||
return all(
|
||||
(r, c) in hits
|
||||
for r in range(GRID_SIZE)
|
||||
for c in range(GRID_SIZE)
|
||||
if self.grid[r][c] == ship
|
||||
)
|
||||
|
||||
def update_sunk_ships(self, hits):
|
||||
for ship in list(self.ships_left):
|
||||
if self.is_sunk(ship, hits):
|
||||
self.ships_left.remove(ship)
|
||||
|
||||
def get_remaining_ships(self):
|
||||
return [size for ship, size in SHIPS.items() if ship in self.ships_left]
|
||||
|
||||
|
||||
class BattleshipStrategy:
|
||||
ADJACENT_HIT_WEIGHT_RANGE = (1, 20)
|
||||
SHIP_SIZE_WEIGHT_RANGE = (0, 10)
|
||||
PARITY_WEIGHT_RANGE = (0, 10)
|
||||
HUNT_MODE_THRESHOLD_RANGE = (0.1, 1)
|
||||
PROB_GRID_WEIGHT_RANGE = (0, 1)
|
||||
AVOID_EDGES_WEIGHT_RANGE = (0, 1)
|
||||
|
||||
def __init__(self):
|
||||
self.adjacent_hit_weight = random.uniform(1, 5)
|
||||
self.checkerboard_weight = random.uniform(0, 2)
|
||||
self.avoid_edges_weight = random.uniform(0, 1)
|
||||
self.hunt_mode_threshold = random.uniform(0.1, 0.5)
|
||||
self.adjacent_hit_weight = random.uniform(*self.ADJACENT_HIT_WEIGHT_RANGE)
|
||||
self.ship_size_weight = random.uniform(*self.SHIP_SIZE_WEIGHT_RANGE)
|
||||
self.parity_weight = random.uniform(*self.PARITY_WEIGHT_RANGE)
|
||||
self.hunt_mode_threshold = random.uniform(*self.HUNT_MODE_THRESHOLD_RANGE)
|
||||
self.prob_grid_weight = random.uniform(*self.PROB_GRID_WEIGHT_RANGE)
|
||||
self.avoid_edges_weight = random.uniform(*self.AVOID_EDGES_WEIGHT_RANGE)
|
||||
self._probability_grid = None
|
||||
|
||||
def serialize(self, include_grid=False):
|
||||
data = {
|
||||
"adjacent_hit_weight": self.adjacent_hit_weight,
|
||||
"ship_size_weight": self.ship_size_weight,
|
||||
"parity_weight": self.parity_weight,
|
||||
"hunt_mode_threshold": self.hunt_mode_threshold,
|
||||
"prob_grid_weight": self.prob_grid_weight,
|
||||
"avoid_edges_weight": self.avoid_edges_weight,
|
||||
}
|
||||
if include_grid:
|
||||
data["probability_grid"] = self.probability_grid
|
||||
return data
|
||||
|
||||
@property
|
||||
def probability_grid(self):
|
||||
if self._probability_grid is None:
|
||||
self.reset_probability_grid()
|
||||
return self._probability_grid
|
||||
|
||||
def reset_probability_grid(self):
|
||||
self._probability_grid = [
|
||||
[1 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)
|
||||
]
|
||||
|
||||
def update_probability_grid(self, hits, misses):
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if (i, j) in hits:
|
||||
self.probability_grid[i][j] = 0
|
||||
elif (i, j) in misses:
|
||||
self.probability_grid[i][j] = -1
|
||||
else:
|
||||
self.probability_grid[i][j] = 1
|
||||
|
||||
for i, j in hits:
|
||||
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
ni, nj = i + di, j + dj
|
||||
if (
|
||||
0 <= ni < GRID_SIZE
|
||||
and 0 <= nj < GRID_SIZE
|
||||
and (ni, nj) not in hits
|
||||
and (ni, nj) not in misses
|
||||
):
|
||||
self.probability_grid[ni][nj] *= self.adjacent_hit_weight
|
||||
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if i == 0 or i == GRID_SIZE - 1 or j == 0 or j == GRID_SIZE - 1:
|
||||
self.probability_grid[i][j] *= 1 - self.avoid_edges_weight
|
||||
|
||||
max_prob = max(max(row) for row in self.probability_grid if max(row) > 0)
|
||||
if max_prob > 0:
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if self.probability_grid[i][j] > 0:
|
||||
self.probability_grid[i][j] = int(
|
||||
(self.probability_grid[i][j] / max_prob) * 100
|
||||
)
|
||||
|
||||
def get_next_move(self, board, hits, misses):
|
||||
scores = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
|
||||
self.update_probability_grid(hits, misses)
|
||||
probabilities = [
|
||||
[self.probability_grid[i][j] for j in range(GRID_SIZE)]
|
||||
for i in range(GRID_SIZE)
|
||||
]
|
||||
remaining_ships = [size for ship, size in SHIPS.items() if size > len(hits)]
|
||||
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if (i, j) in hits or (i, j) in misses:
|
||||
probabilities[i][j] = 0
|
||||
continue
|
||||
|
||||
# Adjacent to hit
|
||||
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
if (
|
||||
0 <= i + di < GRID_SIZE
|
||||
and 0 <= j + dj < GRID_SIZE
|
||||
and (i + di, j + dj) in hits
|
||||
):
|
||||
scores[i][j] += self.adjacent_hit_weight
|
||||
for size in remaining_ships:
|
||||
if self.can_fit_ship(board, i, j, size):
|
||||
probabilities[i][j] *= self.ship_size_weight
|
||||
|
||||
# Checkerboard pattern
|
||||
if (i + j) % 2 == 0:
|
||||
scores[i][j] += self.checkerboard_weight
|
||||
probabilities[i][j] *= self.parity_weight
|
||||
|
||||
# Avoid edges
|
||||
if i in [0, GRID_SIZE - 1] or j in [0, GRID_SIZE - 1]:
|
||||
scores[i][j] -= self.avoid_edges_weight
|
||||
max_prob = max(max(row) for row in probabilities)
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
probabilities[i][j] *= self.prob_grid_weight
|
||||
if max_prob > 0:
|
||||
probabilities[i][j] += (1 - self.prob_grid_weight) * (
|
||||
probabilities[i][j] / max_prob
|
||||
)
|
||||
|
||||
max_score = max(max(row) for row in scores)
|
||||
max_prob = max(max(row) for row in probabilities)
|
||||
candidates = [
|
||||
(i, j)
|
||||
for i in range(GRID_SIZE)
|
||||
for j in range(GRID_SIZE)
|
||||
if scores[i][j] == max_score and (i, j) not in hits and (i, j) not in misses
|
||||
if probabilities[i][j] == max_prob
|
||||
]
|
||||
|
||||
return (
|
||||
random.choice(candidates)
|
||||
if candidates
|
||||
else random.choice(
|
||||
[
|
||||
(i, j)
|
||||
for i in range(GRID_SIZE)
|
||||
for j in range(GRID_SIZE)
|
||||
if (i, j) not in hits and (i, j) not in misses
|
||||
]
|
||||
)
|
||||
)
|
||||
return random.choice(candidates)
|
||||
|
||||
def can_fit_ship(self, board, row, col, size):
|
||||
if col + size <= GRID_SIZE and all(
|
||||
board[row][c] == 0 for c in range(col, col + size)
|
||||
):
|
||||
return True
|
||||
if row + size <= GRID_SIZE and all(
|
||||
board[r][col] == 0 for r in range(row, row + size)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_random_board():
|
||||
board = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
|
||||
for ship, size in SHIPS.items():
|
||||
while True:
|
||||
row = random.randint(0, GRID_SIZE - 1)
|
||||
col = random.randint(0, GRID_SIZE - 1)
|
||||
horizontal = random.choice([True, False])
|
||||
if is_valid_placement(board, row, col, size, horizontal):
|
||||
place_ship(board, row, col, size, horizontal)
|
||||
break
|
||||
return board
|
||||
|
||||
|
||||
def is_valid_placement(board, row, col, size, horizontal):
|
||||
if horizontal:
|
||||
if col + size > GRID_SIZE:
|
||||
return False
|
||||
return all(board[row][c] == 0 for c in range(col, col + size))
|
||||
else:
|
||||
if row + size > GRID_SIZE:
|
||||
return False
|
||||
return all(board[r][col] == 0 for r in range(row, row + size))
|
||||
|
||||
|
||||
def place_ship(board, row, col, size, horizontal):
|
||||
if horizontal:
|
||||
for c in range(col, col + size):
|
||||
board[row][c] = 1
|
||||
else:
|
||||
for r in range(row, row + size):
|
||||
board[r][col] = 1
|
||||
|
||||
|
||||
def play_game(strategy):
|
||||
board = create_random_board()
|
||||
def play_game(strategy, use_ensemble=False):
|
||||
board = Board()
|
||||
hits = set()
|
||||
misses = set()
|
||||
turns = 0
|
||||
ships_left = sum(SHIPS.values())
|
||||
|
||||
while ships_left > 0 and turns < 100:
|
||||
i, j = strategy.get_next_move(board, hits, misses)
|
||||
elite_strategies = load_elite_strategies() if use_ensemble else None
|
||||
|
||||
if strategy is not None:
|
||||
strategy.reset_probability_grid()
|
||||
|
||||
while board.ships_left and turns < 100:
|
||||
if use_ensemble:
|
||||
i, j = get_ensemble_move(elite_strategies, board, hits, misses)
|
||||
else:
|
||||
i, j = strategy.get_next_move(board.grid, hits, misses)
|
||||
strategy.update_probability_grid(hits, misses)
|
||||
|
||||
turns += 1
|
||||
if board[i][j] == 1:
|
||||
if board.grid[i][j] is not None:
|
||||
hits.add((i, j))
|
||||
ships_left -= 1
|
||||
ship = board.grid[i][j]
|
||||
board.update_sunk_ships(hits)
|
||||
else:
|
||||
misses.add((i, j))
|
||||
|
||||
|
|
@ -121,38 +234,69 @@ def play_game(strategy):
|
|||
|
||||
def crossover(parent1, parent2):
|
||||
child = BattleshipStrategy()
|
||||
child.avoid_edges_weight = random.choice(
|
||||
[parent1.avoid_edges_weight, parent2.avoid_edges_weight]
|
||||
child.adjacent_hit_weight = random.choice(
|
||||
[parent1.adjacent_hit_weight, parent2.adjacent_hit_weight]
|
||||
)
|
||||
child.ship_size_weight = random.choice(
|
||||
[parent1.ship_size_weight, parent2.ship_size_weight]
|
||||
)
|
||||
child.parity_weight = random.choice([parent1.parity_weight, parent2.parity_weight])
|
||||
child.hunt_mode_threshold = random.choice(
|
||||
[parent1.hunt_mode_threshold, parent2.hunt_mode_threshold]
|
||||
)
|
||||
|
||||
if parent1.adjacent_hit_weight < parent2.adjacent_hit_weight:
|
||||
child.adjacent_hit_weight = parent1.adjacent_hit_weight
|
||||
else:
|
||||
child.adjacent_hit_weight = parent2.adjacent_hit_weight
|
||||
|
||||
if parent1.checkerboard_weight < parent2.checkerboard_weight:
|
||||
child.checkerboard_weight = parent1.checkerboard_weight
|
||||
else:
|
||||
child.checkerboard_weight = parent2.checkerboard_weight
|
||||
|
||||
child.prob_grid_weight = random.choice(
|
||||
[parent1.prob_grid_weight, parent2.prob_grid_weight]
|
||||
)
|
||||
child.avoid_edges_weight = random.choice(
|
||||
[parent1.avoid_edges_weight, parent2.avoid_edges_weight]
|
||||
)
|
||||
return child
|
||||
|
||||
|
||||
def mutate(individual):
|
||||
if random.random() < MUTATION_RATE * 2:
|
||||
individual.avoid_edges_weight = random.uniform(0, 1)
|
||||
if random.random() < MUTATION_RATE * 1.5:
|
||||
individual.hunt_mode_threshold = random.uniform(0.1, 0.5)
|
||||
if random.random() < MUTATION_RATE * 0.8:
|
||||
individual.adjacent_hit_weight = random.uniform(1, 5)
|
||||
if random.random() < MUTATION_RATE * 0.8:
|
||||
individual.checkerboard_weight = random.uniform(0, 2)
|
||||
if (
|
||||
random.random()
|
||||
< BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["adjacent_hit_weight"]
|
||||
):
|
||||
individual.adjacent_hit_weight = random.uniform(
|
||||
*BattleshipStrategy.ADJACENT_HIT_WEIGHT_RANGE
|
||||
)
|
||||
if random.random() < BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["ship_size_weight"]:
|
||||
individual.ship_size_weight = random.uniform(
|
||||
*BattleshipStrategy.SHIP_SIZE_WEIGHT_RANGE
|
||||
)
|
||||
if random.random() < BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["parity_weight"]:
|
||||
individual.parity_weight = random.uniform(
|
||||
*BattleshipStrategy.PARITY_WEIGHT_RANGE
|
||||
)
|
||||
if (
|
||||
random.random()
|
||||
< BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["hunt_mode_threshold"]
|
||||
):
|
||||
individual.hunt_mode_threshold = random.uniform(
|
||||
*BattleshipStrategy.HUNT_MODE_THRESHOLD_RANGE
|
||||
)
|
||||
if random.random() < BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["prob_grid_weight"]:
|
||||
individual.prob_grid_weight = random.uniform(
|
||||
*BattleshipStrategy.PROB_GRID_WEIGHT_RANGE
|
||||
)
|
||||
if (
|
||||
random.random()
|
||||
< BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["avoid_edges_weight"]
|
||||
):
|
||||
individual.avoid_edges_weight = random.uniform(
|
||||
*BattleshipStrategy.AVOID_EDGES_WEIGHT_RANGE
|
||||
)
|
||||
return individual
|
||||
|
||||
|
||||
def evaluate_strategy(strategy):
|
||||
total_turns = 0
|
||||
for _ in range(GAMES_PER_INDIVIDUAL):
|
||||
total_turns += play_game(strategy)
|
||||
return total_turns
|
||||
|
||||
|
||||
def genetic_algorithm(continue_training=False):
|
||||
if continue_training and os.path.exists(LEARNING_FILE):
|
||||
with open(LEARNING_FILE, "r") as f:
|
||||
|
|
@ -160,23 +304,26 @@ def genetic_algorithm(continue_training=False):
|
|||
population = [BattleshipStrategy() for _ in range(POPULATION_SIZE)]
|
||||
for i, strategy_data in enumerate(data):
|
||||
population[i].__dict__.update(strategy_data)
|
||||
print("Continuing training from existing population.")
|
||||
tqdm.write("Continuing training from existing population.")
|
||||
else:
|
||||
if os.path.exists(LEARNING_FILE):
|
||||
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
||||
os.rename(LEARNING_FILE, f"{LEARNING_FILE}.{timestamp}")
|
||||
print(f"Moved existing model to {LEARNING_FILE}.{timestamp}")
|
||||
tqdm.write(f"Moved existing model to {LEARNING_FILE}.{timestamp}")
|
||||
population = [BattleshipStrategy() for _ in range(POPULATION_SIZE)]
|
||||
print("Starting with a new population.")
|
||||
tqdm.write("Starting with a new population.")
|
||||
|
||||
best_fitness = float("inf")
|
||||
generations_without_improvement = 0
|
||||
|
||||
progress_bar = tqdm(
|
||||
total=GENERATIONS * POPULATION_SIZE, file=sys.stderr, desc="Overall Progress"
|
||||
)
|
||||
|
||||
for generation in range(GENERATIONS):
|
||||
fitness_scores = [
|
||||
sum(play_game(strategy) for _ in range(GAMES_PER_INDIVIDUAL))
|
||||
for strategy in population
|
||||
]
|
||||
with Pool() as pool:
|
||||
fitness_scores = list(pool.imap(evaluate_strategy, population))
|
||||
progress_bar.update(POPULATION_SIZE)
|
||||
|
||||
population = [
|
||||
x
|
||||
|
|
@ -194,25 +341,51 @@ def genetic_algorithm(continue_training=False):
|
|||
else:
|
||||
generations_without_improvement += 1
|
||||
|
||||
print(
|
||||
tqdm.write(
|
||||
f"Generation {generation + 1}: Best fitness = {current_best_fitness}, Avg fitness = {avg_fitness:.2f}, Best overall = {best_fitness}"
|
||||
)
|
||||
|
||||
if generations_without_improvement >= 20:
|
||||
print("No improvement for 20 generations. Stopping early.")
|
||||
break
|
||||
|
||||
new_population = population[:2]
|
||||
elitism_count = POPULATION_SIZE // 20
|
||||
new_population = population[:elitism_count]
|
||||
|
||||
while len(new_population) < POPULATION_SIZE:
|
||||
parent1, parent2 = random.sample(population[:50], 2)
|
||||
tournament_size = 5
|
||||
parent1 = min(
|
||||
random.sample(population, tournament_size),
|
||||
key=lambda x: fitness_scores[population.index(x)],
|
||||
)
|
||||
parent2 = min(
|
||||
random.sample(population, tournament_size),
|
||||
key=lambda x: fitness_scores[population.index(x)],
|
||||
)
|
||||
|
||||
child = crossover(parent1, parent2)
|
||||
new_population.append(mutate(child))
|
||||
|
||||
population = new_population
|
||||
|
||||
with open(LEARNING_FILE, "w") as f:
|
||||
json.dump([strategy.__dict__ for strategy in population], f)
|
||||
elite_strategies = population[:elitism_count]
|
||||
elite_data = [
|
||||
strategy.serialize(include_grid=True) for strategy in elite_strategies
|
||||
]
|
||||
|
||||
elite_file = "elites.json"
|
||||
with open(elite_file, "w") as f:
|
||||
json.dump(elite_data, f, indent=4)
|
||||
|
||||
try:
|
||||
with open(LEARNING_FILE, "w") as f:
|
||||
json.dump(
|
||||
[strategy.serialize() for strategy in population], f, indent=4
|
||||
)
|
||||
except Exception as e:
|
||||
tqdm.write(f"Error saving generation {generation + 1}: {e}")
|
||||
|
||||
if generations_without_improvement >= 20:
|
||||
tqdm.write("No improvement for 20 generations. Stopping early.")
|
||||
break
|
||||
|
||||
progress_bar.close()
|
||||
|
||||
return population[0]
|
||||
|
||||
|
|
@ -228,10 +401,67 @@ def load_best_strategy():
|
|||
return BattleshipStrategy()
|
||||
|
||||
|
||||
def load_elite_strategies(num_elites=10):
|
||||
if os.path.exists(LEARNING_FILE):
|
||||
with open(LEARNING_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
elite_strategies = []
|
||||
for strategy_data in data[:num_elites]:
|
||||
strategy = BattleshipStrategy()
|
||||
strategy.__dict__.update(strategy_data)
|
||||
elite_strategies.append(strategy)
|
||||
return elite_strategies
|
||||
else:
|
||||
return [BattleshipStrategy()]
|
||||
|
||||
|
||||
def get_ensemble_move(elite_strategies, board, hits, misses):
|
||||
probabilities = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
|
||||
remaining_ships = board.get_remaining_ships()
|
||||
|
||||
for strategy in elite_strategies:
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if (i, j) in hits or (i, j) in misses:
|
||||
continue
|
||||
|
||||
prob = 1
|
||||
|
||||
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
if (
|
||||
0 <= i + di < GRID_SIZE
|
||||
and 0 <= j + dj < GRID_SIZE
|
||||
and (i + di, j + dj) in hits
|
||||
):
|
||||
prob *= strategy.adjacent_hit_weight
|
||||
|
||||
for size in remaining_ships:
|
||||
if strategy.can_fit_ship(board.grid, i, j, size):
|
||||
prob *= strategy.ship_size_weight
|
||||
|
||||
if (i + j) % 2 == 0:
|
||||
prob *= strategy.parity_weight
|
||||
|
||||
if i == 0 or i == GRID_SIZE - 1 or j == 0 or j == GRID_SIZE - 1:
|
||||
prob *= 1 - strategy.avoid_edges_weight
|
||||
|
||||
probabilities[i][j] += prob * strategy.prob_grid_weight
|
||||
|
||||
max_prob = max(max(row) for row in probabilities)
|
||||
candidates = [
|
||||
(i, j)
|
||||
for i in range(GRID_SIZE)
|
||||
for j in range(GRID_SIZE)
|
||||
if probabilities[i][j] == max_prob
|
||||
]
|
||||
|
||||
return random.choice(candidates)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(
|
||||
"Usage: python battleship_genetic.py [train|train_continue|sample|sample_multi]"
|
||||
"Usage: python battleship_genetic2.py [train|train_continue|sample|sample_multi|sample_ensemble]"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
|
@ -253,9 +483,12 @@ def main():
|
|||
total_turns = sum(play_game(best_strategy) for _ in range(num_games))
|
||||
avg_turns = total_turns / num_games
|
||||
print(f"Average turns over {num_games} games: {avg_turns:.2f}")
|
||||
elif mode == "sample_ensemble":
|
||||
turns = play_game(None, use_ensemble=True)
|
||||
print(turns)
|
||||
else:
|
||||
print(
|
||||
"Invalid mode. Use 'train', 'train_continue', 'sample', or 'sample_multi'."
|
||||
"Invalid mode. Use 'train', 'train_continue', 'sample', 'sample_multi', or 'sample_ensemble'."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,487 +0,0 @@
|
|||
import random
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from tqdm import tqdm
|
||||
from multiprocessing import Pool
|
||||
|
||||
GRID_SIZE = 10
|
||||
SHIPS = {"Carrier": 5, "Battleship": 4, "Cruiser": 3, "Submarine": 3, "Destroyer": 2}
|
||||
POPULATION_SIZE = 200
|
||||
GENERATIONS = 50
|
||||
BASE_MUTATION_RATE = 0.1
|
||||
GAMES_PER_INDIVIDUAL = 20
|
||||
LEARNING_FILE = "battleship_learning.json"
|
||||
|
||||
MUTATION_MULTIPLIERS = {
|
||||
"adjacent_hit_weight": 0.5,
|
||||
"ship_size_weight": 1.5,
|
||||
"parity_weight": 1.5,
|
||||
"hunt_mode_threshold": 1.0,
|
||||
"prob_grid_weight": 1.0,
|
||||
"avoid_edges_weight": 1.0, # Added for avoid_edges_weight
|
||||
}
|
||||
|
||||
|
||||
class BattleshipStrategy:
|
||||
# Define ranges as class attributes
|
||||
ADJACENT_HIT_WEIGHT_RANGE = (1, 20)
|
||||
SHIP_SIZE_WEIGHT_RANGE = (0, 10)
|
||||
PARITY_WEIGHT_RANGE = (0, 10)
|
||||
HUNT_MODE_THRESHOLD_RANGE = (0.1, 1)
|
||||
PROB_GRID_WEIGHT_RANGE = (0, 1)
|
||||
AVOID_EDGES_WEIGHT_RANGE = (0, 1) # New weight range
|
||||
|
||||
def __init__(self):
|
||||
self.adjacent_hit_weight = random.uniform(*self.ADJACENT_HIT_WEIGHT_RANGE)
|
||||
self.ship_size_weight = random.uniform(*self.SHIP_SIZE_WEIGHT_RANGE)
|
||||
self.parity_weight = random.uniform(*self.PARITY_WEIGHT_RANGE)
|
||||
self.hunt_mode_threshold = random.uniform(*self.HUNT_MODE_THRESHOLD_RANGE)
|
||||
self.prob_grid_weight = random.uniform(*self.PROB_GRID_WEIGHT_RANGE)
|
||||
self.avoid_edges_weight = random.uniform(
|
||||
*self.AVOID_EDGES_WEIGHT_RANGE
|
||||
) # Initialize new weight
|
||||
self._probability_grid = None
|
||||
|
||||
def serialize(self, include_grid=False):
|
||||
data = {
|
||||
"adjacent_hit_weight": self.adjacent_hit_weight,
|
||||
"ship_size_weight": self.ship_size_weight,
|
||||
"parity_weight": self.parity_weight,
|
||||
"hunt_mode_threshold": self.hunt_mode_threshold,
|
||||
"prob_grid_weight": self.prob_grid_weight,
|
||||
"avoid_edges_weight": self.avoid_edges_weight, # Include in serialization
|
||||
}
|
||||
if include_grid:
|
||||
data["probability_grid"] = self.probability_grid
|
||||
return data
|
||||
|
||||
@property
|
||||
def probability_grid(self):
|
||||
if self._probability_grid is None:
|
||||
self.reset_probability_grid()
|
||||
return self._probability_grid
|
||||
|
||||
def reset_probability_grid(self):
|
||||
self._probability_grid = [
|
||||
[1 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)
|
||||
]
|
||||
|
||||
def update_probability_grid(self, hits, misses):
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if (i, j) in hits:
|
||||
self.probability_grid[i][j] = 0
|
||||
elif (i, j) in misses:
|
||||
self.probability_grid[i][j] = -1
|
||||
else:
|
||||
self.probability_grid[i][j] = 1
|
||||
|
||||
for i, j in hits:
|
||||
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
ni, nj = i + di, j + dj
|
||||
if (
|
||||
0 <= ni < GRID_SIZE
|
||||
and 0 <= nj < GRID_SIZE
|
||||
and (ni, nj) not in hits
|
||||
and (ni, nj) not in misses
|
||||
):
|
||||
self.probability_grid[ni][nj] *= self.adjacent_hit_weight
|
||||
|
||||
# Apply avoid_edges_weight
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if i == 0 or i == GRID_SIZE - 1 or j == 0 or j == GRID_SIZE - 1:
|
||||
self.probability_grid[i][j] *= 1 - self.avoid_edges_weight
|
||||
|
||||
max_prob = max(max(row) for row in self.probability_grid if max(row) > 0)
|
||||
if max_prob > 0:
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if self.probability_grid[i][j] > 0:
|
||||
self.probability_grid[i][j] = int(
|
||||
(self.probability_grid[i][j] / max_prob) * 100
|
||||
)
|
||||
|
||||
def get_next_move(self, board, hits, misses):
|
||||
self.update_probability_grid(hits, misses)
|
||||
probabilities = [
|
||||
[self.probability_grid[i][j] for j in range(GRID_SIZE)]
|
||||
for i in range(GRID_SIZE)
|
||||
]
|
||||
remaining_ships = [size for ship, size in SHIPS.items() if size > len(hits)]
|
||||
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if (i, j) in hits or (i, j) in misses:
|
||||
probabilities[i][j] = 0
|
||||
continue
|
||||
|
||||
for size in remaining_ships:
|
||||
if self.can_fit_ship(board, i, j, size):
|
||||
probabilities[i][j] *= self.ship_size_weight
|
||||
|
||||
if (i + j) % 2 == 0:
|
||||
probabilities[i][j] *= self.parity_weight
|
||||
|
||||
max_prob = max(max(row) for row in probabilities)
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
probabilities[i][j] *= self.prob_grid_weight
|
||||
if max_prob > 0:
|
||||
probabilities[i][j] += (1 - self.prob_grid_weight) * (
|
||||
probabilities[i][j] / max_prob
|
||||
)
|
||||
|
||||
max_prob = max(max(row) for row in probabilities)
|
||||
candidates = [
|
||||
(i, j)
|
||||
for i in range(GRID_SIZE)
|
||||
for j in range(GRID_SIZE)
|
||||
if probabilities[i][j] == max_prob
|
||||
]
|
||||
|
||||
return random.choice(candidates)
|
||||
|
||||
def can_fit_ship(self, board, row, col, size):
|
||||
if col + size <= GRID_SIZE and all(
|
||||
board[row][c] == 0 for c in range(col, col + size)
|
||||
):
|
||||
return True
|
||||
if row + size <= GRID_SIZE and all(
|
||||
board[r][col] == 0 for r in range(row, row + size)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_random_board():
|
||||
board = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
|
||||
for ship, size in SHIPS.items():
|
||||
while True:
|
||||
row = random.randint(0, GRID_SIZE - 1)
|
||||
col = random.randint(0, GRID_SIZE - 1)
|
||||
horizontal = random.choice([True, False])
|
||||
if is_valid_placement(board, row, col, size, horizontal):
|
||||
place_ship(board, row, col, size, horizontal)
|
||||
break
|
||||
return board
|
||||
|
||||
|
||||
def is_valid_placement(board, row, col, size, horizontal):
|
||||
if horizontal:
|
||||
if col + size > GRID_SIZE:
|
||||
return False
|
||||
return all(board[row][c] == 0 for c in range(col, col + size))
|
||||
else:
|
||||
if row + size > GRID_SIZE:
|
||||
return False
|
||||
return all(board[r][col] == 0 for r in range(row, row + size))
|
||||
|
||||
|
||||
def place_ship(board, row, col, size, horizontal):
|
||||
if horizontal:
|
||||
for c in range(col, col + size):
|
||||
board[row][c] = 1
|
||||
else:
|
||||
for r in range(row, row + size):
|
||||
board[r][col] = 1
|
||||
|
||||
|
||||
def play_game(strategy, use_ensemble=False):
|
||||
board = create_random_board()
|
||||
hits = set()
|
||||
misses = set()
|
||||
turns = 0
|
||||
ships_left = sum(SHIPS.values())
|
||||
|
||||
elite_strategies = load_elite_strategies() if use_ensemble else None
|
||||
|
||||
# Reset the probability grid at the start of each game
|
||||
strategy.reset_probability_grid()
|
||||
|
||||
while ships_left > 0 and turns < 100:
|
||||
if use_ensemble:
|
||||
i, j = get_ensemble_move(elite_strategies, board, hits, misses)
|
||||
else:
|
||||
i, j = strategy.get_next_move(board, hits, misses)
|
||||
|
||||
# Update the probability grid after each move
|
||||
strategy.update_probability_grid(hits, misses)
|
||||
|
||||
turns += 1
|
||||
if board[i][j] == 1:
|
||||
hits.add((i, j))
|
||||
ships_left -= 1
|
||||
else:
|
||||
misses.add((i, j))
|
||||
|
||||
return turns
|
||||
|
||||
|
||||
def crossover(parent1, parent2):
|
||||
child = BattleshipStrategy()
|
||||
child.adjacent_hit_weight = random.choice(
|
||||
[parent1.adjacent_hit_weight, parent2.adjacent_hit_weight]
|
||||
)
|
||||
child.ship_size_weight = random.choice(
|
||||
[parent1.ship_size_weight, parent2.ship_size_weight]
|
||||
)
|
||||
child.parity_weight = random.choice([parent1.parity_weight, parent2.parity_weight])
|
||||
child.hunt_mode_threshold = random.choice(
|
||||
[parent1.hunt_mode_threshold, parent2.hunt_mode_threshold]
|
||||
)
|
||||
child.prob_grid_weight = random.choice(
|
||||
[parent1.prob_grid_weight, parent2.prob_grid_weight]
|
||||
)
|
||||
child.avoid_edges_weight = random.choice(
|
||||
[parent1.avoid_edges_weight, parent2.avoid_edges_weight]
|
||||
)
|
||||
return child
|
||||
|
||||
|
||||
def mutate(individual):
|
||||
if (
|
||||
random.random()
|
||||
< BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["adjacent_hit_weight"]
|
||||
):
|
||||
individual.adjacent_hit_weight = random.uniform(
|
||||
*BattleshipStrategy.ADJACENT_HIT_WEIGHT_RANGE
|
||||
)
|
||||
if random.random() < BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["ship_size_weight"]:
|
||||
individual.ship_size_weight = random.uniform(
|
||||
*BattleshipStrategy.SHIP_SIZE_WEIGHT_RANGE
|
||||
)
|
||||
if random.random() < BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["parity_weight"]:
|
||||
individual.parity_weight = random.uniform(
|
||||
*BattleshipStrategy.PARITY_WEIGHT_RANGE
|
||||
)
|
||||
if (
|
||||
random.random()
|
||||
< BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["hunt_mode_threshold"]
|
||||
):
|
||||
individual.hunt_mode_threshold = random.uniform(
|
||||
*BattleshipStrategy.HUNT_MODE_THRESHOLD_RANGE
|
||||
)
|
||||
if random.random() < BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["prob_grid_weight"]:
|
||||
individual.prob_grid_weight = random.uniform(
|
||||
*BattleshipStrategy.PROB_GRID_WEIGHT_RANGE
|
||||
)
|
||||
if (
|
||||
random.random()
|
||||
< BASE_MUTATION_RATE * MUTATION_MULTIPLIERS["avoid_edges_weight"]
|
||||
):
|
||||
individual.avoid_edges_weight = random.uniform(
|
||||
*BattleshipStrategy.AVOID_EDGES_WEIGHT_RANGE
|
||||
)
|
||||
return individual
|
||||
|
||||
|
||||
def evaluate_strategy(strategy):
|
||||
total_turns = 0
|
||||
for _ in range(GAMES_PER_INDIVIDUAL):
|
||||
total_turns += play_game(strategy)
|
||||
return total_turns
|
||||
|
||||
|
||||
def genetic_algorithm(continue_training=False):
|
||||
if continue_training and os.path.exists(LEARNING_FILE):
|
||||
with open(LEARNING_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
population = [BattleshipStrategy() for _ in range(POPULATION_SIZE)]
|
||||
for i, strategy_data in enumerate(data):
|
||||
population[i].__dict__.update(strategy_data)
|
||||
tqdm.write("Continuing training from existing population.")
|
||||
else:
|
||||
if os.path.exists(LEARNING_FILE):
|
||||
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
||||
os.rename(LEARNING_FILE, f"{LEARNING_FILE}.{timestamp}")
|
||||
tqdm.write(f"Moved existing model to {LEARNING_FILE}.{timestamp}")
|
||||
population = [BattleshipStrategy() for _ in range(POPULATION_SIZE)]
|
||||
tqdm.write("Starting with a new population.")
|
||||
|
||||
best_fitness = float("inf")
|
||||
generations_without_improvement = 0
|
||||
|
||||
progress_bar = tqdm(
|
||||
total=GENERATIONS * POPULATION_SIZE, file=sys.stderr, desc="Overall Progress"
|
||||
)
|
||||
|
||||
for generation in range(GENERATIONS):
|
||||
with Pool() as pool:
|
||||
fitness_scores = list(pool.imap(evaluate_strategy, population))
|
||||
progress_bar.update(POPULATION_SIZE)
|
||||
|
||||
population = [
|
||||
x
|
||||
for _, x in sorted(
|
||||
zip(fitness_scores, population), key=lambda pair: pair[0]
|
||||
)
|
||||
]
|
||||
|
||||
current_best_fitness = min(fitness_scores)
|
||||
avg_fitness = sum(fitness_scores) / len(fitness_scores)
|
||||
|
||||
if current_best_fitness < best_fitness:
|
||||
best_fitness = current_best_fitness
|
||||
generations_without_improvement = 0
|
||||
else:
|
||||
generations_without_improvement += 1
|
||||
|
||||
# Use tqdm.write to output generation results
|
||||
tqdm.write(
|
||||
f"Generation {generation + 1}: Best fitness = {current_best_fitness}, Avg fitness = {avg_fitness:.2f}, Best overall = {best_fitness}"
|
||||
)
|
||||
|
||||
elitism_count = POPULATION_SIZE // 20
|
||||
new_population = population[:elitism_count]
|
||||
|
||||
while len(new_population) < POPULATION_SIZE:
|
||||
tournament_size = 5
|
||||
parent1 = min(
|
||||
random.sample(population, tournament_size),
|
||||
key=lambda x: fitness_scores[population.index(x)],
|
||||
)
|
||||
parent2 = min(
|
||||
random.sample(population, tournament_size),
|
||||
key=lambda x: fitness_scores[population.index(x)],
|
||||
)
|
||||
|
||||
child = crossover(parent1, parent2)
|
||||
new_population.append(mutate(child))
|
||||
|
||||
population = new_population
|
||||
|
||||
# Dump elite strategies with probability grids after all games are played
|
||||
elite_strategies = population[:elitism_count]
|
||||
elite_data = [
|
||||
strategy.serialize(include_grid=True) for strategy in elite_strategies
|
||||
]
|
||||
|
||||
# Reuse the same file for elites
|
||||
elite_file = "elites.json"
|
||||
with open(elite_file, "w") as f:
|
||||
json.dump(elite_data, f, indent=4)
|
||||
|
||||
# Serialize the final population without probability grids
|
||||
try:
|
||||
with open(LEARNING_FILE, "w") as f:
|
||||
json.dump(
|
||||
[strategy.serialize() for strategy in population], f, indent=4
|
||||
)
|
||||
# tqdm.write(f"Generation {generation + 1} saved successfully.")
|
||||
except Exception as e:
|
||||
tqdm.write(f"Error saving generation {generation + 1}: {e}")
|
||||
|
||||
if generations_without_improvement >= 20:
|
||||
tqdm.write("No improvement for 20 generations. Stopping early.")
|
||||
break
|
||||
|
||||
progress_bar.close()
|
||||
|
||||
return population[0]
|
||||
|
||||
|
||||
def load_best_strategy():
|
||||
if os.path.exists(LEARNING_FILE):
|
||||
with open(LEARNING_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
best_strategy = BattleshipStrategy()
|
||||
best_strategy.__dict__.update(data[0])
|
||||
return best_strategy
|
||||
else:
|
||||
return BattleshipStrategy()
|
||||
|
||||
|
||||
def load_elite_strategies(num_elites=10):
|
||||
if os.path.exists(LEARNING_FILE):
|
||||
with open(LEARNING_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
elite_strategies = []
|
||||
for strategy_data in data[:num_elites]:
|
||||
strategy = BattleshipStrategy()
|
||||
strategy.__dict__.update(strategy_data)
|
||||
elite_strategies.append(strategy)
|
||||
return elite_strategies
|
||||
else:
|
||||
return [BattleshipStrategy()]
|
||||
|
||||
|
||||
def get_ensemble_move(elite_strategies, board, hits, misses):
|
||||
probabilities = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
|
||||
remaining_ships = [size for ship, size in SHIPS.items() if size > len(hits)]
|
||||
|
||||
for strategy in elite_strategies:
|
||||
for i in range(GRID_SIZE):
|
||||
for j in range(GRID_SIZE):
|
||||
if (i, j) in hits or (i, j) in misses:
|
||||
continue
|
||||
|
||||
prob = 1
|
||||
|
||||
for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
if (
|
||||
0 <= i + di < GRID_SIZE
|
||||
and 0 <= j + dj < GRID_SIZE
|
||||
and (i + di, j + dj) in hits
|
||||
):
|
||||
prob *= strategy.adjacent_hit_weight
|
||||
|
||||
for size in remaining_ships:
|
||||
if strategy.can_fit_ship(board, i, j, size):
|
||||
prob *= strategy.ship_size_weight
|
||||
|
||||
if (i + j) % 2 == 0:
|
||||
prob *= strategy.parity_weight
|
||||
|
||||
probabilities[i][j] += prob * strategy.prob_grid_weight
|
||||
|
||||
max_prob = max(max(row) for row in probabilities)
|
||||
candidates = [
|
||||
(i, j)
|
||||
for i in range(GRID_SIZE)
|
||||
for j in range(GRID_SIZE)
|
||||
if probabilities[i][j] == max_prob
|
||||
]
|
||||
|
||||
return random.choice(candidates)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(
|
||||
"Usage: python battleship_genetic2.py [train|train_continue|sample|sample_multi|sample_ensemble]"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
mode = sys.argv[1]
|
||||
|
||||
if mode == "train":
|
||||
best_strategy = genetic_algorithm(continue_training=False)
|
||||
print("Training completed. Best strategy saved.")
|
||||
elif mode == "train_continue":
|
||||
best_strategy = genetic_algorithm(continue_training=True)
|
||||
print("Continued training completed. Best strategy saved.")
|
||||
elif mode == "sample":
|
||||
best_strategy = load_best_strategy()
|
||||
turns = play_game(best_strategy)
|
||||
print(turns)
|
||||
elif mode == "sample_multi":
|
||||
best_strategy = load_best_strategy()
|
||||
num_games = 100
|
||||
total_turns = sum(play_game(best_strategy) for _ in range(num_games))
|
||||
avg_turns = total_turns / num_games
|
||||
print(f"Average turns over {num_games} games: {avg_turns:.2f}")
|
||||
elif mode == "sample_ensemble":
|
||||
turns = play_game(None, use_ensemble=True)
|
||||
print(turns)
|
||||
else:
|
||||
print(
|
||||
"Invalid mode. Use 'train', 'train_continue', 'sample', 'sample_multi', or 'sample_ensemble'."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
RUNS=100
|
||||
PROB_FILE="prob_results.txt"
|
||||
#PROB_FILE2="prob_results2.txt"
|
||||
GENETIC_FILE="genetic_results.txt"
|
||||
GENETIC_FILE2="genetic_results_ensemble.txt"
|
||||
|
||||
|
|
@ -45,16 +46,22 @@ calculate_stats() {
|
|||
# Run probability grid simulation
|
||||
run_simulation "battleship_prob.py" $PROB_FILE "Probability Grid"
|
||||
|
||||
# Run genetic algorithm simulation
|
||||
run_simulation "battleship_genetic2.py" $GENETIC_FILE "Genetic Algorithm"
|
||||
# Run probability grid simulation
|
||||
#run_simulation "battleship_prob2.py" $PROB_FILE2 "Probability Grid 2"
|
||||
|
||||
# Run genetic algorithm simulation
|
||||
run_simulation "battleship_genetic2.py" $GENETIC_FILE2 "Genetic Algorithm Ensemble"
|
||||
run_simulation "battleship_genetic.py" $GENETIC_FILE "Genetic Algorithm"
|
||||
|
||||
# Run genetic algorithm simulation
|
||||
# temporarily commented out because the single top elite often works better...
|
||||
#run_simulation "battleship_genetic.py" $GENETIC_FILE2 "Genetic Algorithm Ensemble"
|
||||
|
||||
# Calculate and display statistics
|
||||
calculate_stats $PROB_FILE "Probability Grid"
|
||||
#calculate_stats $PROB_FILE2 "Probability Grid 2"
|
||||
calculate_stats $GENETIC_FILE "Genetic Algorithm"
|
||||
calculate_stats $GENETIC_FILE2 "Genetic Algorithm Ensemble"
|
||||
#calculate_stats $GENETIC_FILE2 "Genetic Algorithm Ensemble"
|
||||
|
||||
# Clean up
|
||||
rm $PROB_FILE $GENETIC_FILE $GENETIC_FILE2
|
||||
#rm $PROB_FILE $PROB_FILE2 $GENETIC_FILE $GENETIC_FILE2
|
||||
rm $PROB_FILE $GENETIC_FILE
|
||||
|
|
|
|||
1302
content/uploads/2024/battleship-solvers/elites.json
Normal file
1302
content/uploads/2024/battleship-solvers/elites.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,10 @@
|
|||
# Variables
|
||||
TARBALL="battleship_boards.json.tar.gz"
|
||||
SAMPLE_FILE="sampled_boards.json"
|
||||
NUM_SAMPLES=1000 # Number of random samples to extract
|
||||
|
||||
# Stream the tarball and sample lines
|
||||
tar -xzOf "$TARBALL" | shuf -n "$NUM_SAMPLES" > "$SAMPLE_FILE"
|
||||
|
||||
# Output the location of the sampled file
|
||||
echo "Random samples saved to $SAMPLE_FILE"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Variable
|
||||
TARBALL="battleship_boards.json.tar.gz"
|
||||
|
||||
# Stream the tarball and sample one random line
|
||||
tar -xzOf "$TARBALL" | shuf -n 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue