WMICraft/algorithms/genetic/genome.py

167 lines
6.6 KiB
Python
Raw Normal View History

2022-06-02 00:52:16 +02:00
import math
import random
from copy import deepcopy
2022-05-30 23:34:28 +02:00
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
2022-05-30 23:34:28 +02:00
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]
2022-06-02 00:52:16 +02:00
fitness: int
sand_islands: List[Position]
tree_islands: List[Position]
water_islands: List[Position]
2022-05-30 23:34:28 +02:00
def __init__(self):
self.grid = np.zeros((ROWS, COLUMNS), dtype=int)
2022-06-02 00:52:16 +02:00
self.fitness = 0
2022-05-30 23:34:28 +02:00
self.knights_red = spawn_objects_in_given_area(
grid=self.grid,
2022-05-31 01:03:00 +02:00
object_alias=MAP_ALIASES.get("KNIGHT_RED"),
2022-05-30 23:34:28 +02:00
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,
2022-05-31 01:03:00 +02:00
object_alias=MAP_ALIASES.get("KNIGHT_BLUE"),
2022-05-30 23:34:28 +02:00
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
)
2022-05-31 01:03:00 +02:00
spawn_objects_in_given_area(
grid=self.grid,
object_alias=MAP_ALIASES.get("CASTLE"),
objects_count=4,
2022-06-02 00:52:16 +02:00
spawn_position_start=Position(row=CASTLE_SPAWN_FIRST_ROW, col=CASTLE_SPAWN_FIRST_COL),
2022-05-31 01:03:00 +02:00
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)
2022-05-31 01:03:00 +02:00
2022-06-02 00:52:16 +02:00
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)
2022-06-02 00:52:16 +02:00
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)
2022-06-06 14:59:24 +02:00
if len(monsters) != MONSTERS_COUNT:
2022-06-02 01:31:26 +02:00
self.fitness = 0
return
if len(sands) < SAND_COUNT or len(trees) < TREE_COUNT or len(waters) < WATER_COUNT:
self.fitness = 5
return
2022-06-02 00:52:16 +02:00
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()
2022-06-02 00:52:16 +02:00
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()
2022-06-02 00:52:16 +02:00
2022-05-30 23:34:28 +02:00
def is_empty(grid: npt.NDArray, position: Position) -> bool:
2022-05-31 01:03:00 +02:00
return grid[position.row, position.col] in [MAP_ALIASES.get("GRASS"), MAP_ALIASES.get("SAND")]
2022-05-30 23:34:28 +02:00
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
2022-05-31 01:03:00 +02:00
def spawn_objects_in_given_area(grid: npt.NDArray,
object_alias: str,
2022-05-30 23:34:28 +02:00
objects_count: int = 1,
spawn_position_start: Position = Position(row=0, col=0),
width: int = COLUMNS,
height: int = ROWS) -> List[Position]:
2022-05-30 23:34:28 +02:00
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