WMICraft/algorithms/genetic/genome.py
2022-06-06 14:59:24 +02:00

167 lines
6.6 KiB
Python

import math
import random
from copy import deepcopy
from random import randrange
from typing import List
import numpy as np
import numpy.typing as npt
from common import Position, get_islands, AREAS_TO_CROSS, find_neighbours, get_tiles_positions
from const import *
class Genome:
grid: npt.NDArray
knights_red: List[Position]
knights_blue: List[Position]
waters: List[Position]
trees: List[Position]
sands: List[Position]
monsters: List[Position]
fitness: int
sand_islands: List[Position]
tree_islands: List[Position]
water_islands: List[Position]
def __init__(self):
self.grid = np.zeros((ROWS, COLUMNS), dtype=int)
self.fitness = 0
self.knights_red = spawn_objects_in_given_area(
grid=self.grid,
object_alias=MAP_ALIASES.get("KNIGHT_RED"),
objects_count=KNIGHTS_PER_TEAM_COUNT,
spawn_position_start=Position(row=LEFT_KNIGHTS_SPAWN_FIRST_ROW, col=LEFT_KNIGHTS_SPAWN_FIRST_COL),
width=KNIGHTS_SPAWN_WIDTH,
height=KNIGHTS_SPAWN_HEIGHT
)
self.knights_blue = spawn_objects_in_given_area(
grid=self.grid,
object_alias=MAP_ALIASES.get("KNIGHT_BLUE"),
objects_count=KNIGHTS_PER_TEAM_COUNT,
spawn_position_start=Position(row=RIGHT_KNIGHTS_SPAWN_FIRST_ROW, col=RIGHT_KNIGHTS_SPAWN_FIRST_COL),
width=KNIGHTS_SPAWN_WIDTH,
height=KNIGHTS_SPAWN_HEIGHT
)
spawn_objects_in_given_area(
grid=self.grid,
object_alias=MAP_ALIASES.get("CASTLE"),
objects_count=4,
spawn_position_start=Position(row=CASTLE_SPAWN_FIRST_ROW, col=CASTLE_SPAWN_FIRST_COL),
width=2,
height=2
)
self.waters = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("WATER"),
objects_count=WATER_COUNT)
self.trees = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("TREE"),
objects_count=TREE_COUNT)
self.sands = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("SAND"),
objects_count=SAND_COUNT)
self.monsters = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("MONSTER"),
objects_count=MONSTERS_COUNT)
self.sand_islands = get_islands(self.grid, self.sands)
self.tree_islands = get_islands(self.grid, self.trees)
self.water_islands = get_islands(self.grid, self.waters)
def update_map(self):
self.sands, self.trees, self.waters, self.monsters = get_tiles_positions(self.grid)
self.sand_islands = get_islands(self.grid, self.sands)
self.tree_islands = get_islands(self.grid, self.trees)
self.water_islands = get_islands(self.grid, self.waters)
def calc_fitness(self):
score = SAND_COUNT + TREE_COUNT + WATER_COUNT
score = score - len(self.sand_islands) - len(self.tree_islands) - len(self.water_islands)
sands, trees, waters, monsters = get_tiles_positions(self.grid)
if len(monsters) != MONSTERS_COUNT:
self.fitness = 0
return
if len(sands) < SAND_COUNT or len(trees) < TREE_COUNT or len(waters) < WATER_COUNT:
self.fitness = 5
return
self.fitness = score
def crossover(self, partner):
# replace a randomly selected part of the grid with partner's part
child = Genome()
child.grid = deepcopy(self.grid)
area_to_cross = random.choice(AREAS_TO_CROSS)
for row in range(area_to_cross.position.row, area_to_cross.position.row + area_to_cross.height):
for col in range(area_to_cross.position.col, area_to_cross.position.col + area_to_cross.width):
child.grid[row][col] = partner.grid[row][col]
child.update_map()
return child
def mutate(self, mutation_rate: float):
# remove 1 item from a random island and add a neighbor to another island
if random.random() < mutation_rate:
# select islands of the same, random type
islands_of_same_type = random.choice([self.sand_islands, self.tree_islands, self.water_islands])
random_index = random.randint(0, len(islands_of_same_type) - 1)
island = islands_of_same_type[random_index]
next_island = islands_of_same_type[(random_index + 1) % len(islands_of_same_type)]
free_tiles_nearby = find_neighbours(self.grid, next_island.col, next_island.row)
tile_type = self.grid[island.row][island.col]
self.grid[island.row][island.col] = MAP_ALIASES.get('GRASS')
# todo: if there are no free tiles around then randomize another next_island
if len(free_tiles_nearby) > 0:
random_free_tile = random.choice(free_tiles_nearby)
island.row = random_free_tile.row
island.col = random_free_tile.col
self.grid[island.row][island.col] = tile_type
self.update_map()
def is_empty(grid: npt.NDArray, position: Position) -> bool:
return grid[position.row, position.col] in [MAP_ALIASES.get("GRASS"), MAP_ALIASES.get("SAND")]
def is_invalid_area(spawn_position_start, height, width) -> bool:
return spawn_position_start.row + height - 1 < 0 or \
spawn_position_start.row + height - 1 >= ROWS or \
spawn_position_start.col + width - 1 < 0 or \
spawn_position_start.col + width - 1 >= COLUMNS
def spawn_objects_in_given_area(grid: npt.NDArray,
object_alias: str,
objects_count: int = 1,
spawn_position_start: Position = Position(row=0, col=0),
width: int = COLUMNS,
height: int = ROWS) -> List[Position]:
if is_invalid_area(spawn_position_start, height, width):
raise ValueError("Invalid spawn area")
objects_remaining = int(objects_count)
positions = []
while objects_remaining > 0:
row = randrange(spawn_position_start.row, spawn_position_start.row + height)
col = randrange(spawn_position_start.col, spawn_position_start.col + width)
position = Position(row=row, col=col)
if is_empty(grid=grid, position=position):
grid[position.row, position.col] = object_alias
positions.append(position)
objects_remaining -= 1
return positions