Compare commits

..

No commits in common. "master" and "SURV-003" have entirely different histories.

75 changed files with 249 additions and 51839 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 B

100
data.txt
View File

@ -1,100 +0,0 @@
{"weight": 9, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 11, "eatable": true, "toughness": 0, "resource": "wood"}
{"weight": 5, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 11, "eatable": true, "toughness": 0, "resource": "wood"}
{"weight": 7, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 7, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 5, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 5, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 1, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 2, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 4, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 1, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 3, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 6, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 7, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 1, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 6, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 2, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 10, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 6, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 11, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 2, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 3, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 6, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 8, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 6, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 9, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 9, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 11, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 4, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 4, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 4, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 1, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 5, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 3, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 0, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 10, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 9, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 4, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 10, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 10, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 11, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 8, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 7, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 1, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 11, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 11, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 3, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 2, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 4, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 6, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 2, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 10, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 0, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 10, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 9, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 2, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 8, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 9, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 6, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 5, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 4, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 0, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 8, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 2, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 2, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 11, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 0, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 9, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 0, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 5, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 3, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 0, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 7, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 4, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 4, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 3, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 8, "eatable": false, "toughness": 3, "resource": "wood"}
{"weight": 10, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 3, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 3, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 1, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 1, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 5, "eatable": true, "toughness": 1, "resource": "food"}
{"weight": 5, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 9, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 3, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 6, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 7, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 9, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 10, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 9, "eatable": false, "toughness": 0, "resource": "wood"}
{"weight": 11, "eatable": true, "toughness": 0, "resource": "wood"}
{"weight": 10, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 5, "eatable": true, "toughness": 3, "resource": "food"}
{"weight": 10, "eatable": false, "toughness": 1, "resource": "wood"}
{"weight": 10, "eatable": true, "toughness": 2, "resource": "food"}
{"weight": 5, "eatable": true, "toughness": 0, "resource": "water"}
{"weight": 0, "eatable": false, "toughness": 2, "resource": "wood"}
{"weight": 10, "eatable": false, "toughness": 2, "resource": "wood"}

View File

@ -1,28 +0,0 @@
import random
def generate_data():
f = open("data.txt", "w")
for i in range(100):
weight = random.randint(0, 11)
eatable = bool(random.randint(0, 1))
toughness = random.randint(0, 3)
f.write('{')
f.write(
f'"weight": {weight}, "eatable": {str(eatable).lower()}, "toughness": {toughness}, "resource": "{get_resource_type(weight, eatable, toughness)}"')
f.write('}')
f.write('\n')
f.close()
def get_resource_type(weight, eatable, toughness):
if weight > 10 or eatable is False:
return "wood"
if toughness < 1:
return "water"
return "food"
generate_data()

View File

@ -1,58 +1,45 @@
import pygame
from survival.ai.genetic_algorithm import GeneticAlgorithm
from survival.components.inventory_component import InventoryComponent
from survival.game.game_map import GameMap
from settings import SCREEN_WIDTH, SCREEN_HEIGHT
from survival.camera import Camera
from survival.game_map import GameMap
from survival.generators.building_generator import BuildingGenerator
from survival.generators.player_generator import PlayerGenerator
from survival.generators.resource_generator import ResourceGenerator
from survival.generators.world_generator import WorldGenerator
from survival.settings import SCREEN_WIDTH, SCREEN_HEIGHT, MUTATE_NETWORKS, LEARN
from survival.systems.draw_system import DrawSystem
from survival.systems.neural_system import NeuralSystem
class Game:
def __init__(self):
self.world_generator = WorldGenerator(win, self.reset)
self.game_map, self.world, self.camera = self.world_generator.create_world()
self.run = True
if LEARN and MUTATE_NETWORKS:
self.genetic_algorithm = GeneticAlgorithm(self.world.get_processor(NeuralSystem), self.finish_training)
def reset(self):
if LEARN and MUTATE_NETWORKS:
self.genetic_algorithm.train()
self.world_generator.reset_world()
def finish_training(self):
self.run = False
def update(self, ms):
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
self.run = False
if pygame.key.get_pressed()[pygame.K_DELETE]:
self.reset()
win.fill((0, 0, 0))
self.game_map.draw(self.camera)
self.world.process(ms)
pygame.display.update()
if __name__ == '__main__':
pygame.init()
pygame.font.init()
win = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("AI Project")
clock = pygame.time.Clock()
game = Game()
while game.run:
game.update(clock.tick(500))
game_map = GameMap(int(SCREEN_WIDTH / 32) * 2, 2 * int(SCREEN_HEIGHT / 32) + 1)
camera = Camera(game_map.width * 32, game_map.height * 32, win)
world = WorldGenerator().create_world(camera, game_map)
player = PlayerGenerator().create_player(world, game_map)
building = BuildingGenerator().create_home(world, game_map)
ResourceGenerator(world, game_map).generate_resources()
run = True
while run:
# Set the framerate
ms = clock.tick(60)
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
win.fill((0, 0, 0))
game_map.draw(camera)
world.process(ms)
pygame.display.update()

View File

@ -1,60 +0,0 @@
import json
from joblib import dump, load
from matplotlib import pyplot as plt
from sklearn import tree
from sklearn.feature_extraction import DictVectorizer
class DecisionTree:
def __init__(self):
self.clf = None
self.vec = None
def build(self, depth: int):
path = "tree_data.json"
samples = []
results = []
with open(path, "r") as training_file:
for sample in training_file:
sample, result = self.process_input(sample)
samples.append(sample)
results.append(result)
self.vec = DictVectorizer()
self.clf = tree.DecisionTreeClassifier(max_depth=depth)
self.clf = self.clf.fit(self.vec.fit_transform(samples).toarray(), results)
def save_model(self, clf_file, vec_file):
dump(self.clf, clf_file)
dump(self.vec, vec_file)
def load_model(self, clf_file, vec_file):
self.clf = load(clf_file)
self.vec = load(vec_file)
def predict_answer(self, params):
return self.clf.predict(self.vec.transform(params).toarray())
def plot_tree(self):
print('Plotting tree...')
fig = plt.figure(figsize=(36, 27))
_ = tree.plot_tree(self.clf,
feature_names=self.vec.get_feature_names(),
filled=True)
fig.savefig("decistion_tree.png")
print('Success!')
@staticmethod
def process_input(line):
data = json.loads(line.strip())
result = data['result']
del data['result']
del data['food_result']
del data['water_result']
del data['wood_result']
sample = data
return sample, result

View File

@ -1,124 +0,0 @@
import random
from typing import Dict
from survival.ai.decision_tree.decision_tree import DecisionTree
from survival.generators.resource_type import ResourceType
class TreeDataGenerator:
INV_RANGE = (1, 100)
VISIBLE = (True, False)
DISTANCE_RANGE = (3, 7)
DISTANCE_FACTOR = 0.2
COUNT = (1, 2, 3)
def generate(self, count=1000):
full_data = []
self.process(count, full_data)
self.write_data_to_file(full_data)
return full_data
def process(self, count, full_data):
for i in range(count):
# if i % 10000 == 0:
# print(i)
package = {}
# Create resource data for each resource type.
for resource in ResourceType:
package[resource] = self.create_resource_data()
# Get the resource with highest result among all generated resource types.
best_resource = self.get_best_resource(package)
# Unpack packaged resources.
(food, water, wood) = (
package[ResourceType.FOOD], package[ResourceType.WATER], package[ResourceType.WOOD])
# Create dictionary filled with data.
data = {"food_inv": food[0], 'food_visible': str(food[1]), 'food_distance': food[2],
'food_count': food[3], 'food_result': food[4],
'water_inv': water[0], 'water_visible': str(water[1]), 'water_distance': water[2],
'water_count': water[3], 'water_result': water[4],
'wood_inv': wood[0], 'wood_visible': str(wood[1]), 'wood_distance': wood[2],
'wood_count': wood[3], 'wood_result': wood[4],
'result': best_resource.name.lower()}
full_data.append(data)
@staticmethod
def write_data_to_file(full_data):
print("Writing to file...")
# Open the target file to which the data will be saved and write all the data to it.
with open('tree_data.json', 'w') as f:
for data in full_data:
data_str = str(data).replace("'", '"').replace('"False"', 'false').replace('"True"', 'true')
f.write(data_str)
f.write('\n')
print("Success!")
def create_resource_data(self):
is_visible = random.choice(self.VISIBLE)
inventory = random.randint(min(self.INV_RANGE), max(self.INV_RANGE))
if is_visible:
cnt = random.choice(self.COUNT)
distance = random.randint(min(self.DISTANCE_RANGE), max(self.DISTANCE_RANGE))
else:
cnt = 0
distance = 0
# Equation determining the results processed by decision tree.
result = (self.INV_RANGE[1] / inventory) * (1 * cnt if is_visible else 0.9) + (
max(self.DISTANCE_RANGE) / distance if is_visible else 0.5) * self.DISTANCE_FACTOR
return [inventory, is_visible, distance, cnt, result]
@staticmethod
def get_best_resource(package: Dict) -> ResourceType:
best_resource = None
for resource, data in package.items():
if best_resource is None or data[:-1] < package[best_resource][:-1]:
best_resource = resource
return best_resource
@staticmethod
def print_data(full_data):
for data in full_data:
print(TreeDataGenerator.format_words(["Data", "Apple", "Water", "Wood"]))
print(TreeDataGenerator.format_words(["Inventory", data["food_inv"], data["water_inv"], data["wood_inv"]]))
print(TreeDataGenerator.format_words(
["Visible", data["food_visible"], data["water_visible"], data["wood_visible"]]))
print(TreeDataGenerator.format_words(
["Distance", data["food_distance"], data["water_distance"], data["wood_distance"]]))
print(
TreeDataGenerator.format_words(["Count", data["food_count"], data["water_count"], data["wood_count"]]))
print(TreeDataGenerator.format_words(
["Result", round(data["food_result"], 3), round(data["water_result"], 3),
round(data["wood_result"], 3)]))
print(f'Best resource: {data["result"]}')
print('--------------------------------------------------------------')
@staticmethod
def format_words(words):
return '{:>12} {:>12} {:>12} {:>12}'.format(words[0], words[1], words[2], words[3])
# Train tree
generator = TreeDataGenerator()
data = generator.generate(50000)
generator.print_data(data)
tree = DecisionTree()
tree.build(1000)
tree.plot_tree()
tree.save_model('classifier.joblib', 'vectorizer.joblib')
# ----------------------------------------------------------- #
# Use trained tree
# tree = DecisionTree()
# tree.load_model('classifier.joblib', 'vectorizer.joblib')
#
# answ = tree.predict_answer({'food_inv': 40, 'water_inv': 10, 'wood_inv': 20,
# 'food_distance': 2, 'water_distance': -1, 'wood_distance': 4,
# 'food_visible': True, 'water_visible': False, 'wood_visible': True,
# 'food_count': 1, 'water_count': 1, 'wood_count': 1})
# print(answ)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +0,0 @@
import sys
from survival.ai.model import LinearQNetwork
from survival.ai.optimizer import Optimizer
class GeneticAlgorithm:
GAMES_PER_NETWORK = 40
PLOTS_COUNTER = 0
CURRENT_GENERATION = 1
def __init__(self, neural_system, callback):
self.callback = callback
self.logs_file = open('genetic_logs.txt', 'w')
self.original_stdout = sys.stdout
sys.stdout = self.logs_file
self.neural_system = neural_system
self.generations = 20
self.population = 10 # Minimum 5 needed to allow breeding
self.nn_params = {
'neurons': [128, 192, 256, 384, 512],
'layers': [0, 1, 2, 3],
'activation': ['relu', 'elu', 'tanh'],
'ratio': [0.0007, 0.0009, 0.0011, 0.0013, 0.0015],
'optimizer': ['RMSprop', 'Adam', 'SGD', 'Adagrad', 'Adadelta'],
}
self.optimizer = Optimizer(self.nn_params)
self.networks: list[LinearQNetwork] = self.optimizer.create_population(self.population)
self.finished = False
self.trained_counter = 0
self.iterations = 0
self.trained_generations = 0
print('Started generation 1...')
self.change_network(self.networks[0])
def train(self):
if self.iterations < GeneticAlgorithm.GAMES_PER_NETWORK - 1:
self.iterations += 1
return
self.iterations = 0
print(f'Network score: {self.optimizer.fitness(self.networks[self.trained_counter])}')
self.trained_counter += 1
# If all networks in current population were trained
if self.trained_counter >= self.population:
# Get average score in current population
avg_score = self.calculate_average_score(self.networks)
print(f'Average population score: {avg_score}.')
results_file = open('genetic_results.txt', 'w')
for network in self.networks:
results_file.write(
f'Network {network.id} params {network.network_params}. Avg score = {sum(network.scores) / len(network.scores)}\n')
results_file.close()
if self.trained_generations >= self.generations - 1:
# Sort the final population
self.networks = sorted(self.networks, key=lambda x: sum(x.scores) / len(x.scores), reverse=True)
self.finished = True
self.logs_file.close()
sys.stdout = self.original_stdout
self.callback()
return
self.trained_generations += 1
GeneticAlgorithm.CURRENT_GENERATION = self.trained_generations + 1
print(f'Started generation {GeneticAlgorithm.CURRENT_GENERATION}...')
self.networks = self.optimizer.evolve(self.networks)
self.trained_counter = 0
self.change_network(self.networks[self.trained_counter])
def calculate_average_score(self, networks):
sums = 0
lengths = 0
for network in networks:
sums += self.optimizer.fitness(network)
lengths += len(network.scores)
return sums / lengths
def change_network(self, net):
GeneticAlgorithm.PLOTS_COUNTER += 1
print(f"Changed network to {GeneticAlgorithm.PLOTS_COUNTER} {net.network_params}")
self.logs_file.flush()
net.id = GeneticAlgorithm.PLOTS_COUNTER
self.neural_system.load_model(net)

View File

@ -1,120 +0,0 @@
import numpy as np
from IPython import display
from matplotlib import pyplot as plt
from survival.settings import MUTATE_NETWORKS
from survival.ai.genetic_algorithm import GeneticAlgorithm
from survival.components.learning_component import LearningComponent
from survival.components.position_component import PositionComponent
from survival.game.enums import Direction
from survival.ai.graph_search import Action
class LearningUtils:
def __init__(self):
self.plot_scores = []
self.plot_mean_scores = []
self.total_score = 0
self.last_actions: [Action, [int, int]] = []
self.plots = 0
def add_scores(self, learning: LearningComponent, games_count: int):
self.plot_scores.append(learning.score)
self.total_score += learning.score
mean_score = self.total_score / games_count
self.plot_mean_scores.append(mean_score)
def plot(self):
# display.clear_output(wait=True)
# display.display(plt.gcf())
plt.clf()
plt.title('Results')
plt.xlabel('Number of Games')
plt.ylabel('Score')
plt.plot(self.plot_scores)
plt.plot(self.plot_mean_scores)
plt.ylim(ymin=0)
plt.text(len(self.plot_scores) - 1, self.plot_scores[-1], str(self.plot_scores[-1]))
plt.text(len(self.plot_mean_scores) - 1, self.plot_mean_scores[-1], str(self.plot_mean_scores[-1]))
self.plots += 1
if MUTATE_NETWORKS:
plt.savefig(f'model/plots/{GeneticAlgorithm.PLOTS_COUNTER}_{self.plots}.png')
else:
plt.savefig(f'model/plots/{self.plots}.png')
plt.show(block=False)
# plt.pause(.1)
def append_action(self, action: Action, pos: PositionComponent):
self.last_actions.append([action, pos.grid_position])
def check_last_actions(self, learning):
"""
Checks if all the last five actions were repeated and imposes the potential penalty.
:param learning:
"""
if len(self.last_actions) > 5:
self.last_actions.pop(0)
last_action: [Action, [int, int]] = self.last_actions[0]
last_grid_pos: [int, int] = last_action[1]
rotations = 0
collisions = 0
for action in self.last_actions:
if action != Action.MOVE:
rotations += 1
else:
current_grid_pos = action[1]
if current_grid_pos[0] == last_grid_pos[0] and current_grid_pos[1] == last_grid_pos[1]:
collisions += 1
if rotations > 4 or collisions > 4:
learning.reward -= 2
def get_state(system, player, resource):
pos: PositionComponent = system.world.component_for_entity(player, PositionComponent)
if resource is None or resource[0] is None:
res_l = False
res_r = False
res_u = False
res_d = False
else:
resource_pos: PositionComponent = system.world.component_for_entity(resource[0], PositionComponent)
res_l = resource_pos.grid_position[0] < pos.grid_position[0]
res_r = resource_pos.grid_position[0] > pos.grid_position[0]
res_u = resource_pos.grid_position[1] < pos.grid_position[1]
res_d = resource_pos.grid_position[1] > pos.grid_position[1]
dir_l = pos.direction == Direction.LEFT
dir_r = pos.direction == Direction.RIGHT
dir_u = pos.direction == Direction.UP
dir_d = pos.direction == Direction.DOWN
pos_l = [pos.grid_position[0] - 1, pos.grid_position[1]]
pos_r = [pos.grid_position[0] + 1, pos.grid_position[1]]
pos_u = [pos.grid_position[0], pos.grid_position[1] - 1]
pos_d = [pos.grid_position[0], pos.grid_position[1] + 1]
col_l = system.game_map.in_bounds(
pos_l) # self.game_map.is_colliding(pos_l) and self.game_map.get_entity(pos_l) is None
col_r = system.game_map.in_bounds(
pos_r) # self.game_map.is_colliding(pos_r) and self.game_map.get_entity(pos_r) is None
col_u = system.game_map.in_bounds(
pos_u) # self.game_map.is_colliding(pos_u) and self.game_map.get_entity(pos_u) is None
col_d = system.game_map.in_bounds(
pos_d) # self.game_map.is_colliding(pos_d) and self.game_map.get_entity(pos_d) is None
state = [
# Collision ahead
(dir_r and col_r) or (dir_l and col_l) or (dir_u and col_u) or (dir_d and col_d),
# Collision on the right
(dir_u and col_r) or (dir_r and col_d) or (dir_d and col_l) or (dir_l and col_u),
# Collision on the left
(dir_u and col_l) or (dir_l and col_d) or (dir_d and col_r) or (dir_r and col_u),
# Movement direction
dir_l, dir_r, dir_u, dir_d,
# Resource location
res_l, res_r, res_u, res_d
]
return np.array(state, dtype=int)

View File

@ -1,110 +0,0 @@
import os
import random
import torch
from torch import nn, optim
import torch.nn.functional as functional
class LinearQNetwork(nn.Module):
TORCH_ACTiVATIONS = 'tanh'
def __init__(self, nn_params, input_size, output_size, randomize=True, params=None):
super().__init__()
self.id = 0
if params is None:
params = {}
self.params_choice = nn_params
self.scores = []
self.network_params = params
if randomize:
self.randomize()
self.layers = nn.ModuleList()
if self.network_params['layers'] == 0:
self.layers.append(nn.Linear(input_size, output_size))
else:
self.layers.append(nn.Linear(input_size, self.network_params['neurons']))
for i in range(self.network_params['layers'] - 1):
self.layers.append(nn.Linear(self.network_params['neurons'], self.network_params['neurons']))
if self.network_params['layers'] > 0:
self.ending_linear = nn.Linear(self.network_params['neurons'], output_size)
self.layers.append(self.ending_linear)
if self.network_params['activation'] in self.TORCH_ACTiVATIONS:
self.forward_func = getattr(torch, self.network_params['activation'])
else:
self.forward_func = getattr(functional, self.network_params['activation'])
def randomize(self):
"""
Sets random parameters for network.
"""
for key in self.params_choice:
self.network_params[key] = random.choice(self.params_choice[key])
def forward(self, x):
for i in range(len(self.layers) - 1):
x = self.forward_func(self.layers[i](x))
x = self.layers[-1](x)
return x
def save(self, file_name='model.pth'):
model_directory = 'model'
if not os.path.exists(model_directory):
os.makedirs(model_directory)
file_path = os.path.join(model_directory, file_name)
torch.save(self.state_dict(), file_path)
@staticmethod
def load(params, input_size, output_size, file_name='model.pth'):
model_directory = 'model'
file_path = os.path.join(model_directory, file_name)
if os.path.isfile(file_path):
model = LinearQNetwork(params, input_size, output_size, True)
model.load_state_dict(torch.load(file_path))
model.eval()
return model
raise Exception(f'Could not find file {file_path}.')
class QTrainer:
def __init__(self, model, lr, gamma, optimizer):
self.model = model
self.lr = lr
self.gamma = gamma
self.optimizer = getattr(optim, optimizer)(model.parameters(), lr=self.lr)
# self.optimizer = optim.Adam(model.parameters(), lr=self.lr)
self.criterion = nn.MSELoss() # Mean squared error
def train_step(self, state, action, reward, next_state, done):
state = torch.tensor(state, dtype=torch.float)
next_state = torch.tensor(next_state, dtype=torch.float)
action = torch.tensor(action, dtype=torch.long)
reward = torch.tensor(reward, dtype=torch.float)
if len(state.shape) == 1:
# reshape the state to make its values an (n, x) tuple
state = torch.unsqueeze(state, 0)
next_state = torch.unsqueeze(next_state, 0)
action = torch.unsqueeze(action, 0)
reward = torch.unsqueeze(reward, 0)
done = (done,)
# Prediction based on simplified Bellman's equation
# Predict Q values for current state
prediction = self.model(state)
target = prediction.clone()
for idx in range(len(done)):
Q = reward[idx]
if not done[idx]:
Q = reward[idx] + self.gamma * torch.max(self.model(next_state[idx]))
# set the target of the maximum value of the action to Q
target[idx][torch.argmax(action).item()] = Q
# Apply the loss function
self.optimizer.zero_grad()
loss = self.criterion(target, prediction)
loss.backward()
self.optimizer.step()

View File

@ -1,144 +0,0 @@
from functools import reduce
from operator import add
import random
from typing import List
from survival.ai.model import LinearQNetwork
from survival.settings import NEURAL_INPUT_SIZE, NEURAL_OUTPUT_SIZE
class Optimizer:
def __init__(self, params, retain=0.4,
random_select=0.1, mutation_chance=0.2):
self.mutation_chance = mutation_chance
self.random_select = random_select
self.retain = retain
self.nn_params = params
def create_population(self, count: int):
"""
Creates 'count' networks from random parameters.
:param count:
:return:
"""
pop = []
for _ in range(0, count):
# Create a random network.
network = LinearQNetwork(self.nn_params, NEURAL_INPUT_SIZE, NEURAL_OUTPUT_SIZE)
# Add network to the population.
pop.append(network)
return pop
@staticmethod
def fitness(network: LinearQNetwork):
return sum(network.scores) / len(network.scores)
def grade(self, pop: List[LinearQNetwork]) -> float:
"""
Finds average fitness for given population.
"""
summed = reduce(add, (self.fitness(network) for network in pop))
return summed / float((len(pop)))
def breed(self, parent_one, parent_two):
"""
Creates a new network from given parents.
:param parent_one:
:param parent_two:
:return:
"""
children = []
for _ in range(2):
child = {}
# Loop through the parameters and pick params for the kid.
for param in self.nn_params:
child[param] = random.choice(
[parent_one.network_params[param], parent_two.network_params[param]]
)
# Create new network object.
network = LinearQNetwork(self.nn_params, NEURAL_INPUT_SIZE, NEURAL_OUTPUT_SIZE)
network.network_params = child
children.append(network)
return children
def mutate(self, network: LinearQNetwork):
"""
Randomly mutates one parameter of the given network.
:param network:
:return:
"""
mutation = random.choice(list(self.nn_params.keys()))
# Mutate one of the params.
network.network_params[mutation] = random.choice(self.nn_params[mutation])
return network
def evolve(self, pop):
"""
Evolves a population of networks.
"""
# Get scores for each network.
scores = [(self.fitness(network), network) for network in pop]
# Sort the scores.
scores = [x[1] for x in sorted(scores, key=lambda x: x[0], reverse=True)]
# Get the number we want to keep for the next gen.
retain_length = int(len(scores) * self.retain)
# Keep the best networks as parents for next generation.
parents = scores[:retain_length]
# Keep some other networks
for network in scores[retain_length:]:
if self.random_select > random.random():
parents.append(network)
# Reset kept networks
reseted_networks = []
for network in parents:
net = LinearQNetwork(self.nn_params, NEURAL_INPUT_SIZE, NEURAL_OUTPUT_SIZE)
net.network_params = network.network_params
reseted_networks.append(net)
parents = reseted_networks
# Randomly mutate some of the networks.
for parent in parents:
if self.mutation_chance > random.random():
parent = self.mutate(parent)
# Determine the number of freed spots for the next generation.
parents_length = len(parents)
desired_length = len(pop) - parents_length
children = []
# Fill missing spots with new children.
while len(children) < desired_length:
# Get random parents.
p1 = random.randint(0, parents_length - 1)
p2 = random.randint(0, parents_length - 1)
# Ensure they are not the same network.
if p1 != p2:
p1 = parents[p1]
p2 = parents[p2]
# Breed networks.
babies = self.breed(p1, p2)
# Add children one at a time.
for baby in babies:
# Don't grow larger than the desired length.
if len(children) < desired_length:
children.append(baby)
parents.extend(children)
return parents

View File

@ -1,93 +0,0 @@
import torch
import pygad
from pygad.torchga import torchga
def fitness_func(solution, sol_idx):
global data_inputs, data_outputs, torch_ga, model, loss_function
model_weights_dict = torchga.model_weights_as_dict(model=model,
weights_vector=solution)
# Use the current solution as the model parameters.
model.load_state_dict(model_weights_dict)
predictions = model(data_inputs)
abs_error = loss_function(predictions, data_outputs).detach().numpy() + 0.00000001
solution_fitness = 1.0 / abs_error
return solution_fitness
def callback_generation(ga_instance):
print("Generation = {generation}".format(generation=ga_instance.generations_completed))
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution()[1]))
# Create the PyTorch model.
input_layer = torch.nn.Linear(3, 2)
relu_layer = torch.nn.ReLU()
output_layer = torch.nn.Linear(2, 1)
model = torch.nn.Sequential(input_layer,
relu_layer,
output_layer)
# print(model)
# Create an instance of the pygad.torchga.TorchGA class to build the initial population.
torch_ga = torchga.TorchGA(model=model,
num_solutions=10)
loss_function = torch.nn.L1Loss()
# Data inputs
data_inputs = torch.tensor([[0.02, 0.1, 0.15],
[0.7, 0.6, 0.8],
[1.5, 1.2, 1.7],
[3.2, 2.9, 3.1]])
# Data outputs
data_outputs = torch.tensor([[0.1],
[0.6],
[1.3],
[2.5]])
# Prepare the PyGAD parameters. Check the documentation for more information: https://pygad.readthedocs.io/en/latest/README_pygad_ReadTheDocs.html#pygad-ga-class
num_generations = 250 # Number of generations.
num_parents_mating = 5 # Number of solutions to be selected as parents in the mating pool.
initial_population = torch_ga.population_weights # Initial population of network weights
parent_selection_type = "sss" # Type of parent selection.
crossover_type = "single_point" # Type of the crossover operator.
mutation_type = "random" # Type of the mutation operator.
mutation_percent_genes = 10 # Percentage of genes to mutate. This parameter has no action if the parameter mutation_num_genes exists.
keep_parents = -1 # Number of parents to keep in the next population. -1 means keep all parents and 0 means keep nothing.
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
initial_population=initial_population,
fitness_func=fitness_func,
parent_selection_type=parent_selection_type,
crossover_type=crossover_type,
mutation_type=mutation_type,
mutation_percent_genes=mutation_percent_genes,
keep_parents=keep_parents,
on_generation=callback_generation)
ga_instance.run()
# After the generations complete, some plots are showed that summarize how the outputs/fitness values evolve over generations.
ga_instance.plot_result(title="PyGAD & PyTorch - Iteration vs. Fitness", linewidth=4)
# Returning the details of the best solution.
solution, solution_fitness, solution_idx = ga_instance.best_solution()
print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
print("Index of the best solution : {solution_idx}".format(solution_idx=solution_idx))
# Fetch the parameters of the best solution.
best_solution_weights = torchga.model_weights_as_dict(model=model,
weights_vector=solution)
model.load_state_dict(best_solution_weights)
predictions = model(data_inputs)
print("Predictions : \n", predictions.detach().numpy())
abs_error = loss_function(predictions, data_outputs)
print("Absolute Error : ", abs_error.detach().numpy())

View File

@ -1,4 +1,4 @@
from survival.game.biomes.biome_preset import BiomePreset
from survival.biomes.biome_preset import BiomePreset
class BiomeData:

View File

@ -1,7 +1,7 @@
import random
from typing import List
from survival.game.tile import Tile
from survival.tile import Tile
class BiomePreset:

View File

@ -1,6 +1,6 @@
from pygame.rect import Rect
from survival.settings import SCREEN_WIDTH, SCREEN_HEIGHT
from survival import SCREEN_WIDTH, SCREEN_HEIGHT
class Camera:

View File

@ -0,0 +1,10 @@
class OnCollisionComponent:
def __init__(self, callbacks: [] = []):
self.callbacks = callbacks
def callAll(self):
for func in self.callbacks:
func()
def addCallback(self, fn):
self.callbacks.append(fn)

View File

@ -1,6 +0,0 @@
from survival.generators.resource_type import ResourceType
class ConsumptionComponent:
def __init__(self):
self.status = {ResourceType.FOOD: 1, ResourceType.WOOD: 1, ResourceType.WATER: 1}

View File

@ -1,4 +1,4 @@
from survival.game.enums import Direction
from survival.enums import Direction
class DirectionChangeComponent:

View File

@ -1,9 +1,9 @@
class InventoryComponent:
def __init__(self, maxitems=100):
def __init__(self, maxitems = 10):
self.maxitems = maxitems
self.items = {}
def add_item(self, item, count):
def addItem(self, item, count):
if item not in self.items:
self.items[item] = count
else:
@ -11,23 +11,14 @@ class InventoryComponent:
if self.items[item] > self.maxitems:
self.items[item] = self.maxitems
def remove_item(self, item, count):
if item in self.items:
def removeItem(self, item, count):
if self.items:
self.items[item] = self.items[item] - count
if self.items[item] < 0:
self.items[item] = 0
if self.items[item] < 0:
self.items[item] = 0
def count(self, item):
return self.items[item]
def has_item(self, item):
return item in self.items and self.items[item] != 0
def total_items_count(self):
total = 0
for item, value in self.items.items():
total += value
return total
def clear(self):
self.items = {}
def hasItem(self, item):
if self.items[item] != 0:
return True
else:
return False

View File

@ -1,32 +0,0 @@
from survival.components.time_component import TimeComponent
class LearningComponent:
def __init__(self):
self.made_step = False
self.old_state = None
self.action = None
self.resource = None
self.reward = 0
self.done = False
self.score = 0
self.record = 0
def load_step(self, old_state, action, resource):
self.old_state = old_state
self.action = action
if resource is None:
self.resource = None
else:
self.resource = resource
self.made_step = True
def reset(self):
self.made_step = False
self.old_state = None
self.action = None
self.resource = None
self.reward = 0
self.done = False

View File

@ -1,15 +0,0 @@
from functools import partial
class OnCollisionComponent:
def __init__(self, callbacks=None):
if callbacks is None:
callbacks = []
self.callbacks = callbacks
def call_all(self):
for func in self.callbacks:
func()
def add_callback(self, fn, **kwargs):
self.callbacks.append(partial(fn, **kwargs))

View File

@ -1,4 +1,4 @@
from survival.game.enums import Direction
from survival.enums import Direction
class PositionComponent:

View File

@ -1,29 +0,0 @@
import random
from survival.generators.resource_type import ResourceType
class ResourceComponent:
def __init__(self, resource_type):
self.resource_type = resource_type
w, e, t = self.generate_attributes(resource_type)
self.weight = w
self.eatable = e
self.toughness = t
@staticmethod
def generate_attributes(resource_type):
if resource_type == ResourceType.WOOD:
weight = random.randint(10, 15)
eatable = False
toughness = random.randint(10, 15)
elif resource_type == ResourceType.WATER:
weight = random.randint(1, 2)
eatable = True
toughness = 0
else:
weight = random.randint(1, 7)
eatable = True
toughness = random.randint(2, 5)
return weight, eatable, toughness

View File

@ -1,4 +1,4 @@
from survival.game.image import Image
from survival.image import Image
class SpriteComponent:

View File

@ -1,5 +1,5 @@
class TimeComponent:
def __init__(self, minute=0, hour=0, day=0, timer=0):
def __init__(self, minute, hour, day, timer):
self.minute = minute
self.hour = hour
self.day = day
@ -16,17 +16,5 @@ class TimeComponent:
self.hour = temp2
self.minute = temp
def total_minutes(self):
return self.minute + self.hour * 60 + self.day * 1440
def __str__(self):
return f'Day {self.day}, {self.hour}:{self.minute}'
def __eq__(self, other):
return self.total_minutes() == other.total_minutes()
def __gt__(self, other):
return self.total_minutes() > other.total_minutes()
def __lt__(self, other):
return self.total_minutes() < other.total_minutes()

View File

@ -1,34 +0,0 @@
from pygame import Surface
from survival.settings import AGENT_VISION_RANGE, SCREEN_WIDTH, SCREEN_HEIGHT
class VisionComponent:
def __init__(self):
self.agent_vision = AGENT_VISION_RANGE * 32 * 2
self.width = SCREEN_WIDTH * 2
self.height = SCREEN_HEIGHT * 2
self.surface_l = Surface(((self.width - self.agent_vision) / 2, self.height))
self.surface_r = Surface(((self.width - self.agent_vision) / 2, self.height))
self.surface_t = Surface((self.agent_vision, (self.height - self.agent_vision) / 2))
self.surface_b = Surface((self.agent_vision, (self.height - self.agent_vision) / 2))
self.surface_l.fill((0, 0, 0))
self.surface_l.set_alpha(200)
self.surface_r.fill((0, 0, 0))
self.surface_r.set_alpha(200)
self.surface_t.fill((0, 0, 0))
self.surface_t.set_alpha(200)
self.surface_b.fill((0, 0, 0))
self.surface_b.set_alpha(200)
self.l_pos = (0, 0)
self.r_pos = (0, 0)
self.t_pos = (0, 0)
self.b_pos = (0, 0)
def update_positions(self, position: [int, int]):
new_position = (position[0] - self.width / 2 + 16, position[1] - self.height / 2 + 16)
self.l_pos = new_position
self.r_pos = (new_position[0] + (self.width + self.agent_vision) / 2, new_position[1])
self.t_pos = (new_position[0] + (self.width - self.agent_vision) / 2, new_position[1])
self.b_pos = (new_position[0] + (self.width - self.agent_vision) / 2,
new_position[1] + (self.height + self.agent_vision) / 2)

View File

@ -18,7 +18,7 @@ class EntityLayer:
def remove_entity(self, pos):
self.tiles[pos[1]][pos[0]] = None
def get_entity(self, pos):
def get_entity(self, pos) -> int:
return self.tiles[pos[1]][pos[0]]
def is_colliding(self, pos):

View File

@ -28,9 +28,6 @@ class Processor:
def process(self, *args, **kwargs):
raise NotImplementedError
def reset(self, *args, **kwargs):
pass
class World:
"""A World object keeps track of all Entities, Components, and Processors.
@ -49,14 +46,6 @@ class World:
self.process_times = {}
self._process = self._timed_process
@property
def processors(self):
return self._processors
@property
def entities(self):
return self._entities
def clear_cache(self) -> None:
self.get_component.cache_clear()
self.get_components.cache_clear()

View File

@ -1,82 +0,0 @@
from survival.components.position_component import PositionComponent
from survival.components.resource_component import ResourceComponent
from survival.game.entity_layer import EntityLayer
from survival.esper import World
from survival.ai.graph_search import graph_search
from survival.settings import AGENT_VISION_RANGE
from survival.game.tile_layer import TileLayer
class GameMap:
def __init__(self, width, height):
self.width = width
self.height = height
self.tile_layer = TileLayer(width, height)
self.entity_layer = EntityLayer(width, height)
def draw(self, camera):
visible_area = camera.get_visible_area()
self.tile_layer.draw(camera, visible_area)
def add_entity(self, entity, pos):
self.entity_layer.add_entity(entity, pos.grid_position)
def move_entity(self, from_pos, to_pos):
self.entity_layer.move_entity(from_pos, to_pos)
def remove_entity(self, pos):
self.entity_layer.remove_entity(pos)
def get_entity(self, pos) -> int:
if not self.in_bounds(pos):
return None
return self.entity_layer.get_entity(pos)
def is_colliding(self, pos):
return not self.in_bounds(pos) or self.entity_layer.is_colliding(pos)
def in_bounds(self, pos):
return 0 <= pos[0] < self.width and 0 <= pos[1] < self.height
def get_cost(self, pos):
return self.tile_layer.get_cost(pos)
def find_nearby_resources(self, world: World, player: int, position: PositionComponent, search_range: int = 5):
entity_position = position.grid_position
x_range = [entity_position[0] - search_range, entity_position[0] + search_range]
y_range = [entity_position[1] - search_range, entity_position[1] + search_range]
# Check if range is not out of map bounds
if x_range[0] < 0:
x_range[0] = 0
if x_range[1] >= self.width:
x_range[1] = self.width - 1
if y_range[0] < 0:
y_range[0] = 0
if y_range[1] >= self.height:
y_range[1] = self.height - 1
found_resources = []
for y in range(y_range[0], y_range[1]):
for x in range(x_range[0], x_range[1]):
ent = self.get_entity([x, y])
if ent == player:
continue
if ent is not None and world.has_component(ent, ResourceComponent):
res_position = world.component_for_entity(ent, PositionComponent).grid_position
path, cost = graph_search(self, position, tuple(res_position), world)
found_resources.append([ent, path, cost])
return found_resources
def find_nearest_resource(self, world: World, player: int, position: PositionComponent):
resources = self.find_nearby_resources(world, player, position, AGENT_VISION_RANGE)
nearest = None
for resource in resources:
if nearest is None or resource[2] < nearest[2]:
nearest = resource
return nearest

View File

@ -1,47 +0,0 @@
import pygame.font
from survival.ai.genetic_algorithm import GeneticAlgorithm
from survival.settings import MUTATE_NETWORKS, SCREEN_HEIGHT, SCREEN_WIDTH
from survival.components.inventory_component import InventoryComponent
from survival.generators.resource_type import ResourceType
from survival.game.image import Image
class UserInterface:
def __init__(self, window):
self.width = SCREEN_WIDTH
self.height = SCREEN_HEIGHT
self.window = window
self.pos = (self.width - 240, 50)
self.scale = 2
self.inventory: InventoryComponent = None
self.images = {
ResourceType.FOOD: Image('apple.png', self.pos, self.scale),
ResourceType.WATER: Image('water.png', self.pos, self.scale),
ResourceType.WOOD: Image('wood.png', self.pos, self.scale)
}
i = 0
for key, value in self.images.items():
self.images[key].pos = (self.pos[0] + i * 32 * self.scale + 8 * i, self.pos[1])
i += 1
self.slot_image = Image('ui.png', self.pos, scale=2)
self.font = pygame.font.SysFont('Comic Sans MS', 20)
self.initialized = False
def load_inventory(self, inventory: InventoryComponent):
self.inventory = inventory
self.initialized = True
def update(self):
pass
def draw(self):
for key, image in self.images.items():
items_count = self.inventory.items[key] if self.inventory.has_item(key) else 0
self.slot_image.pos = image.pos
self.slot_image.draw_static(self.window)
image.draw_static(self.window)
textsurface = self.font.render(str(items_count), False, (255, 255, 255))
self.window.blit(textsurface, (image.pos[0] + 48, image.pos[1] + 36))

32
survival/game_map.py Normal file
View File

@ -0,0 +1,32 @@
from survival.entity_layer import EntityLayer
from survival.tile_layer import TileLayer
class GameMap:
def __init__(self, width, height):
self.width = width
self.height = height
self.tile_layer = TileLayer(width, height)
self.entity_layer = EntityLayer(width, height)
def draw(self, camera):
visible_area = camera.get_visible_area()
self.tile_layer.draw(camera, visible_area)
def add_entity(self, entity, pos):
self.entity_layer.add_entity(entity, pos.grid_position)
def move_entity(self, from_pos, to_pos):
self.entity_layer.move_entity(from_pos, to_pos)
def remove_entity(self, pos):
self.entity_layer.remove_entity(pos)
def get_entity(self, pos) -> int:
return self.entity_layer.get_entity(pos)
def is_colliding(self, pos):
return pos[0] < 0 or pos[0] >= self.width or pos[1] < 0 or pos[1] >= self.height or self.entity_layer.is_colliding(pos)
def get_cost(self, pos):
return self.tile_layer.get_cost(pos)

View File

@ -1,41 +1,27 @@
from survival.components.on_collision_component import OnCollisionComponent
from survival.components.OnCollisionComponent import OnCollisionComponent
from survival.components.camera_target_component import CameraTargetComponent
from survival.components.consumption_component import ConsumptionComponent
from survival.components.input_component import InputComponent
from survival.components.inventory_component import InventoryComponent
from survival.components.learning_component import LearningComponent
from survival.components.movement_component import MovementComponent
from survival.components.position_component import PositionComponent
from survival.components.sprite_component import SpriteComponent
from survival.components.time_component import TimeComponent
from survival.components.vision_component import VisionComponent
from survival.generators.resource_type import ResourceType
from survival.settings import PLAYER_START_POSITION, STARTING_RESOURCES_AMOUNT
class PlayerGenerator:
def create_player(self, world, game_map):
player = world.create_entity()
pos = PositionComponent([PLAYER_START_POSITION[0] * 32, PLAYER_START_POSITION[1] * 32],
PLAYER_START_POSITION)
pos = PositionComponent([0, 0], [0, 0])
world.add_component(player, pos)
world.add_component(player, MovementComponent())
world.add_component(player, InputComponent())
world.add_component(player, OnCollisionComponent())
inv = InventoryComponent()
for resource in ResourceType:
inv.add_item(resource, STARTING_RESOURCES_AMOUNT)
world.add_component(player, ConsumptionComponent())
world.add_component(player, inv)
camera_target = CameraTargetComponent(pos)
world.add_component(player, camera_target)
# world.add_component(player, AutomationComponent())
game_map.add_entity(player, pos)
sprite = SpriteComponent('stevenson.png')
sprite.set_scale(1)
world.add_component(player, sprite)
world.add_component(player, TimeComponent())
world.add_component(player, VisionComponent())
world.add_component(player, LearningComponent())
world.add_component(player, TimeComponent(0, 0, 0, 0))
return player

View File

@ -1,68 +1,34 @@
import random
from survival import GameMap
from survival.components.on_collision_component import OnCollisionComponent
from survival.components.inventory_component import InventoryComponent
from survival.components.learning_component import LearningComponent
from survival.components.OnCollisionComponent import OnCollisionComponent
from survival.components.position_component import PositionComponent
from survival.components.resource_component import ResourceComponent
from survival.components.sprite_component import SpriteComponent
from survival.esper import World
from survival.generators.resource_type import ResourceType
from survival.settings import RESOURCES_AMOUNT, PLAYER_START_POSITION
from survival.settings import RESOURCES_AMOUNT
class ResourceGenerator:
resources_amount = 0
def __init__(self, world, game_map):
self.world = world
self.map = game_map
def generate_resources(self, player: int):
ResourceGenerator.resources_amount = RESOURCES_AMOUNT
def generate_resources(self):
for x in range(RESOURCES_AMOUNT):
obj = self.world.create_entity()
sprites = {
ResourceType.FOOD: 'apple.png',
ResourceType.WATER: 'water.png',
ResourceType.WOOD: 'wood.png'
}
sprites = ['apple.png', 'water.png', 'wood.png']
empty_grid_pos = self.get_empty_grid_position()
empty_pos = [empty_grid_pos[0] * 32, empty_grid_pos[1] * 32]
pos = PositionComponent(empty_pos, empty_grid_pos)
resource_type = random.choice(list(ResourceType))
sprite = SpriteComponent(sprites[resource_type])
sprite = SpriteComponent(random.choice(sprites))
col = OnCollisionComponent()
col.add_callback(self.remove_resource, world=self.world, game_map=self.map, resource_ent=obj, player=player)
self.world.add_component(obj, pos)
self.world.add_component(obj, sprite)
self.world.add_component(obj, col)
self.world.add_component(obj, ResourceComponent(resource_type))
self.map.add_entity(obj, pos)
def get_empty_grid_position(self):
free_pos = [random.randrange(self.map.width), random.randrange(self.map.height)]
while self.map.is_colliding(free_pos) or (
free_pos[0] == PLAYER_START_POSITION[0] and free_pos[1] == PLAYER_START_POSITION[1]):
while self.map.is_colliding(free_pos):
free_pos = [random.randrange(self.map.width), random.randrange(self.map.height)]
return free_pos
@staticmethod
def remove_resource(world: World, game_map: GameMap, resource_ent: int, player: int):
pos = world.component_for_entity(resource_ent, PositionComponent)
resource = world.component_for_entity(resource_ent, ResourceComponent)
inventory = world.component_for_entity(player, InventoryComponent)
inventory.add_item(resource.resource_type, 1)
game_map.remove_entity(pos.grid_position)
world.delete_entity(resource_ent, immediate=True)
if world.has_component(player, LearningComponent):
learning = world.component_for_entity(player, LearningComponent)
learning.reward += 10
learning.score += 1
ResourceGenerator.resources_amount -= 1
if ResourceGenerator.resources_amount == 0:
learning.reward += 50
learning.done = True

View File

@ -1,18 +0,0 @@
from enum import Enum
class ResourceType(Enum):
FOOD = 1
WATER = 2
WOOD = 3
@staticmethod
def get_from_string(string):
if string == 'food':
return ResourceType.FOOD
elif string == 'water':
return ResourceType.WATER
elif string == 'wood':
return ResourceType.WOOD
else:
raise Exception("Unknown resource type")

View File

@ -1,12 +1,10 @@
import json
import random
from pathlib import Path
from typing import List
from survival.game.biomes.biome_data import BiomeData
from survival.game.biomes.biome_preset import BiomePreset
from survival.game.biomes.noise import generate_noise
from survival.game.tile import Tile
from survival.biomes.biome_data import BiomeData
from survival.biomes.biome_preset import BiomePreset
from survival.biomes.noise import generate_noise
from survival.tile import Tile
class TileGenerator:
@ -17,10 +15,6 @@ class TileGenerator:
"Grass4": Tile(origin=(96, 0), cost=1),
"Sand": Tile(origin=(64, 64), cost=20),
"Puddle": Tile(origin=(96, 64), cost=20),
"DarkGrass": Tile(origin=(64, 96), cost=2),
"Water": Tile(origin=(96, 96), cost=3),
"Ice": Tile(origin=(0, 96), cost=2),
"Ice2": Tile(origin=(32, 96), cost=2),
}
TilesValues = list(Tiles.values())
@ -28,11 +22,11 @@ class TileGenerator:
Biomes = [
BiomePreset("Desert", min_height=0.2, min_moisture=0, min_heat=0.5, tiles=[Tiles["Grass1"], Tiles["Grass2"],
Tiles["Grass3"], Tiles["Grass4"]]),
BiomePreset("Forest", min_height=0.2, min_moisture=0.4, min_heat=0.4, tiles=[Tiles["DarkGrass"]]),
BiomePreset("Forest", min_height=0.2, min_moisture=0.4, min_heat=0.4, tiles=[Tiles["Sand"]]),
BiomePreset("Grassland", min_height=0.2, min_moisture=0.5, min_heat=0.3, tiles=[Tiles["Sand"]]),
BiomePreset("Marsh", min_height=0.3, min_moisture=0.5, min_heat=0.62, tiles=[Tiles["Puddle"]]),
BiomePreset("Ocean", min_height=0, min_moisture=0, min_heat=0, tiles=[Tiles["Water"]]),
BiomePreset("Tundra", min_height=0.2, min_moisture=0, min_heat=0, tiles=[Tiles["Ice"], Tiles["Ice2"]])
BiomePreset("Ocean", min_height=0, min_moisture=0, min_heat=0, tiles=[Tiles["Sand"]]),
BiomePreset("Tundra", min_height=0.2, min_moisture=0, min_heat=0, tiles=[Tiles["Puddle"]])
]
@staticmethod
@ -46,25 +40,11 @@ class TileGenerator:
@staticmethod
def generate_biome_tiles(width: int, height: int):
# Use static seed to allow smooth learning of genetic algorithm
seed = 1
seed = random.randint(0, 9999999)
octaves = 10
file_name = f'seeds/{seed}.bin'
biomes_file = Path(file_name)
if biomes_file.is_file():
with open(file_name, 'r') as f:
data = json.load(f)
height_map = data[0]
moisture_map = data[1]
heat_map = data[2]
else:
height_map = generate_noise(width, height, octaves, seed)
moisture_map = generate_noise(width, height, octaves, seed)
heat_map = generate_noise(width, height, octaves, seed)
data = [height_map, moisture_map, heat_map]
Path('seeds').mkdir(exist_ok=True)
with open(file_name, 'w') as f:
json.dump(data, f)
height_map = generate_noise(width, height, octaves, seed)
moisture_map = generate_noise(width, height, octaves, seed)
heat_map = generate_noise(width, height, octaves, seed)
return [[TileGenerator.get_biome(height_map[y][x], moisture_map[y][x], heat_map[y][x]).get_new_tile() for x in
range(width)] for y in range(height)]

View File

@ -1,119 +1,25 @@
from pathlib import Path
from survival import esper, ResourceGenerator, PlayerGenerator
from survival.ai.model import LinearQNetwork
from survival.components.consumption_component import ConsumptionComponent
from survival.components.direction_component import DirectionChangeComponent
from survival.components.inventory_component import InventoryComponent
from survival.components.learning_component import LearningComponent
from survival.components.moving_component import MovingComponent
from survival.components.pathfinding_component import PathfindingComponent
from survival.components.position_component import PositionComponent
from survival.components.resource_component import ResourceComponent
from survival.components.time_component import TimeComponent
from survival.esper import World
from survival.game.camera import Camera
from survival.game.game_map import GameMap
from survival.generators.resource_type import ResourceType
from survival.settings import PLAYER_START_POSITION, STARTING_RESOURCES_AMOUNT, SCREEN_WIDTH, SCREEN_HEIGHT, \
MUTATE_NETWORKS, NETWORK_PARAMS, NEURAL_OUTPUT_SIZE, NEURAL_INPUT_SIZE
from survival.systems.automation_system import AutomationSystem
from survival import esper
from survival.systems.camera_system import CameraSystem
from survival.systems.collision_system import CollisionSystem
from survival.systems.consumption_system import ConsumptionSystem
from survival.systems.direction_system import DirectionSystem
from survival.systems.draw_system import DrawSystem
from survival.systems.input_system import InputSystem
from survival.systems.movement_system import MovementSystem
from survival.systems.neural_system import NeuralSystem
from survival.systems.pathfinding_movement_system import PathfindingMovementSystem
from survival.systems.time_system import TimeSystem
from survival.systems.vision_system import VisionSystem
class WorldGenerator:
def __init__(self, win, callback):
self.win = win
self.callback = callback
self.world: World = esper.World(timed=True)
self.game_map: GameMap = GameMap(int(SCREEN_WIDTH / 32) * 2, 2 * int(SCREEN_HEIGHT / 32) + 1)
self.camera = Camera(self.game_map.width * 32, self.game_map.height * 32, self.win)
self.resource_generator: ResourceGenerator = ResourceGenerator(self.world, self.game_map)
self.player: int = -1
def create_world(self):
self.world.add_processor(InputSystem(self.camera, self.game_map))
self.world.add_processor(CameraSystem(self.camera))
self.world.add_processor(MovementSystem(self.game_map), priority=20)
self.world.add_processor(CollisionSystem(self.game_map), priority=30)
self.world.add_processor(NeuralSystem(self.game_map, self.callback), priority=50)
if not MUTATE_NETWORKS:
model_path = Path("/model/model.pth")
if model_path.is_file():
self.world.get_processor(NeuralSystem).load_model(
LinearQNetwork.load(NETWORK_PARAMS, NEURAL_INPUT_SIZE, NEURAL_OUTPUT_SIZE))
else:
self.world.get_processor(NeuralSystem).load_model(
LinearQNetwork(NETWORK_PARAMS, NEURAL_INPUT_SIZE, NEURAL_OUTPUT_SIZE, False, NETWORK_PARAMS))
self.world.add_processor(DrawSystem(self.camera))
self.world.add_processor(TimeSystem())
self.world.add_processor(AutomationSystem(self.game_map))
# self.world.add_processor(PathfindingMovementSystem(self.game_map), priority=40)
self.world.add_processor(DirectionSystem())
self.world.add_processor(ConsumptionSystem(self.callback))
self.world.add_processor(VisionSystem(self.camera))
def create_world(self, camera, game_map):
world = esper.World()
world.add_processor(InputSystem(camera))
world.add_processor(CameraSystem(camera))
world.add_processor(MovementSystem(), priority=1)
world.add_processor(CollisionSystem(game_map), priority=2)
world.add_processor(DrawSystem(camera))
world.add_processor(TimeSystem())
world.add_processor(PathfindingMovementSystem(game_map), priority=3)
world.add_processor(DirectionSystem())
self.player = PlayerGenerator().create_player(self.world, self.game_map)
self.world.get_processor(DrawSystem).initialize_interface(
self.world.component_for_entity(self.player, InventoryComponent))
# BuildingGenerator().create_home(self.world, self.game_map)
self.resource_generator.generate_resources(self.player)
return self.game_map, self.world, self.camera
def reset_world(self):
for processor in self.world.processors:
processor.reset()
self.reset_player()
self.reset_resources()
def reset_resources(self):
for entity in self.world.entities:
if self.world.has_component(entity, ResourceComponent):
self.game_map.remove_entity(self.world.component_for_entity(entity, PositionComponent).grid_position)
self.world.delete_entity(entity)
continue
self.resource_generator.generate_resources(self.player)
def reset_player(self):
self.world.remove_component(self.player, TimeComponent)
self.world.add_component(self.player, TimeComponent())
inv = self.world.component_for_entity(self.player, InventoryComponent)
inv.clear()
for resource in ResourceType:
inv.add_item(resource, STARTING_RESOURCES_AMOUNT)
if self.world.has_component(self.player, ConsumptionComponent):
self.world.remove_component(self.player, ConsumptionComponent)
self.world.add_component(self.player, ConsumptionComponent())
pos = self.world.component_for_entity(self.player, PositionComponent)
old_pos = pos.grid_position
self.world.remove_component(self.player, PositionComponent)
self.world.add_component(self.player,
PositionComponent([PLAYER_START_POSITION[0] * 32, PLAYER_START_POSITION[1] * 32],
PLAYER_START_POSITION))
self.game_map.move_entity(old_pos, pos.grid_position)
if self.world.has_component(self.player, MovingComponent):
self.world.remove_component(self.player, MovingComponent)
if self.world.has_component(self.player, DirectionChangeComponent):
self.world.remove_component(self.player, DirectionChangeComponent)
if self.world.has_component(self.player, PathfindingComponent):
self.world.remove_component(self.player, PathfindingComponent)
if self.world.has_component(self.player, LearningComponent):
learning = self.world.component_for_entity(self.player, LearningComponent)
learning.reset()
return world

View File

@ -2,13 +2,9 @@ from enum import Enum
from queue import PriorityQueue
from typing import Tuple, List
from survival.components.direction_component import DirectionChangeComponent
from survival.components.moving_component import MovingComponent
from survival import GameMap
from survival.components.position_component import PositionComponent
from survival.components.resource_component import ResourceComponent
from survival.game.enums import Direction
from survival.esper import World
from survival.systems.consumption_system import ConsumeComponent
from survival.enums import Direction
class Action(Enum):
@ -16,35 +12,6 @@ class Action(Enum):
ROTATE_RIGHT = 1
MOVE = 2
@staticmethod
def from_array(action):
if action[0] == 1:
return Action.MOVE
if action[1] == 1:
return Action.ROTATE_LEFT
if action[2] == 1:
return Action.ROTATE_RIGHT
raise Exception("Unknown action.")
@staticmethod
def perform(world, entity, action):
if world.has_component(entity, MovingComponent):
raise Exception(f"Entity was already moving. Could not perform action: {action}")
if world.has_component(entity, DirectionChangeComponent):
raise Exception(f"Entity was already rotating. Could not perform action: {action}")
if action == Action.ROTATE_LEFT:
world.add_component(entity, DirectionChangeComponent(
Direction.rotate_left(world.component_for_entity(entity, PositionComponent).direction)))
world.add_component(entity, ConsumeComponent(0.2))
elif action == Action.ROTATE_RIGHT:
world.add_component(entity, DirectionChangeComponent(
Direction.rotate_right(world.component_for_entity(entity, PositionComponent).direction)))
world.add_component(entity, ConsumeComponent(0.2))
else:
world.add_component(entity, MovingComponent())
return action
class State:
def __init__(self, position: Tuple[int, int], direction: Direction):
@ -71,7 +38,7 @@ def get_moved_position(position: Tuple[int, int], direction: Direction):
return position[0] + vector[0], position[1] + vector[1]
def get_states(state: State, game_map, world: World) -> List[Tuple[Action, State, int]]:
def get_states(state: State, game_map: GameMap) -> List[Tuple[Action, State, int]]:
states = list()
states.append((Action.ROTATE_LEFT, State(state.position, state.direction.rotate_left(state.direction)), 1))
@ -80,34 +47,28 @@ def get_states(state: State, game_map, world: World) -> List[Tuple[Action, State
target_position = get_moved_position(state.position, state.direction)
if not game_map.is_colliding(target_position):
states.append((Action.MOVE, State(target_position, state.direction), game_map.get_cost(target_position)))
elif game_map.get_entity(target_position) is not None:
ent = game_map.get_entity(target_position)
if world.has_component(ent, ResourceComponent):
states.append((Action.MOVE, State(target_position, state.direction), 3))
return states
def build_path(node: Node):
cost = 0
actions = [node.action]
parent = node.parent
while parent is not None:
if parent.action is not None:
actions.append(parent.action)
cost += parent.cost
parent = parent.parent
actions.reverse()
return actions, cost
return actions
def heuristic(new_node: Node, goal: Tuple[int, int]):
return abs(new_node.state.position[0] - goal[0]) + abs(new_node.state.position[1] - goal[1])
def graph_search(game_map, start: PositionComponent, goal: tuple, world: World):
def graph_search(game_map: GameMap, start: PositionComponent, goal: tuple):
fringe = PriorityQueue()
explored = list()
@ -121,7 +82,7 @@ def graph_search(game_map, start: PositionComponent, goal: tuple, world: World):
while True:
# No solutions found
if fringe.empty():
return [], 0
return []
node = fringe.get()
node_priority = node[0]
@ -136,13 +97,13 @@ def graph_search(game_map, start: PositionComponent, goal: tuple, world: World):
explored_states.add((tuple(node.state.position), node.state.direction))
# Get all possible states
for state in get_states(node.state, game_map, world):
for state in get_states(node.state, game_map):
sub_state = (tuple(state[1].position), state[1].direction)
new_node = Node(state=state[1],
parent=node,
action=state[0],
cost=(state[2] + node.cost))
priority = new_node.cost + heuristic(new_node, goal)
if sub_state not in fringe_states and sub_state not in explored_states:
fringe.put((priority, new_node))

View File

@ -4,15 +4,12 @@ import pygame
class Image:
def __init__(self, filename='', pos=(0, 0), scale=1, surface=None):
if surface is None:
self.texture = pygame.image.load(os.path.join('../', 'assets', filename)).convert_alpha()
else:
self.texture = surface
def __init__(self, filename):
self.texture = pygame.image.load(os.path.join('..', 'assets', filename)).convert_alpha()
self.image = self.texture
self.origin = (0, 0)
self.pos = pos
self.set_scale(scale)
self.pos = (0, 0)
self.scale = 1
def set_scale(self, scale):
self.image = pygame.transform.scale(self.texture,
@ -23,8 +20,3 @@ class Image:
window.blit(self.image, camera.apply(self.pos),
pygame.Rect(self.origin[0] * self.scale, self.origin[1] * self.scale, 32 * self.scale,
32 * self.scale))
def draw_static(self, window):
window.blit(self.image, self.pos,
pygame.Rect(self.origin[0] * self.scale, self.origin[1] * self.scale, 32 * self.scale,
32 * self.scale))

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

78
survival/player.py Normal file
View File

@ -0,0 +1,78 @@
from random import randint
import pygame
class Player:
def __init__(self):
# self.pos = [1024, 512]
# self.velocity = [0, 0]
# self.image = Image('stevenson.png')
# self.image.set_scale(2)
# self.speed = 30
# self.movement_target = [self.pos[0], self.pos[1]]
# self.timer = 0
pass
def draw(self, camera):
self.image.pos = self.pos
camera.draw(self.image)
def is_moving(self):
return self.pos != self.movement_target
def move_in_random_direction(self):
value = randint(0, 3)
random_movement = {
0: self.move_up,
1: self.move_down,
2: self.move_left,
3: self.move_right
}
random_movement[value]()
def update(self, delta, pressed_keys):
if self.is_moving():
if self.velocity[0] != 0:
self.pos[0] += self.velocity[0] * self.speed * delta / 100
if abs(self.movement_target[0] - self.pos[0]) < 0.1 * self.speed:
self.velocity = [0, 0]
self.pos = self.movement_target
else:
self.pos[1] += self.velocity[1] * self.speed * delta / 100
if abs(self.pos[1] - self.movement_target[1]) < 0.1 * self.speed:
self.velocity = [0, 0]
self.pos = self.movement_target
return
self.timer += delta
if self.timer > 1000:
self.move_in_random_direction()
self.timer = 0
if pressed_keys[pygame.K_LEFT]:
self.move_left()
elif pressed_keys[pygame.K_RIGHT]:
self.move_right()
elif pressed_keys[pygame.K_DOWN]:
self.move_down()
elif pressed_keys[pygame.K_UP]:
self.move_up()
def move_left(self):
self.velocity = [-1, 0]
self.movement_target = [self.pos[0] - 32, self.pos[1]]
def move_right(self):
self.velocity = [1, 0]
self.movement_target = [self.pos[0] + 32, self.pos[1]]
def move_up(self):
self.velocity = [0, -1]
self.movement_target = [self.pos[0], self.pos[1] - 32]
def move_down(self):
self.velocity = [0, 1]
self.movement_target = [self.pos[0], self.pos[1] + 32]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,18 +1,4 @@
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 600
RESOURCES_AMOUNT = 175
DIRECTION_CHANGE_DELAY = 5
PLAYER_START_POSITION = [20, 10]
STARTING_RESOURCES_AMOUNT = 5
AGENT_VISION_RANGE = 5
NEURAL_INPUT_SIZE = 11
NEURAL_OUTPUT_SIZE = 3
LEARN = True
MUTATE_NETWORKS = True
NETWORK_PARAMS = {
"neurons": 256,
"layers": 1,
"activation": 'relu',
"ratio": 0.001,
"optimizer": 'Adam'
}
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
RESOURCES_AMOUNT = 300
DIRECTION_CHANGE_DELAY = 200

View File

@ -1,62 +0,0 @@
from survival import esper, GameMap
from survival.components.moving_component import MovingComponent
from survival.components.pathfinding_component import PathfindingComponent
from survival.components.position_component import PositionComponent
from survival.components.resource_component import ResourceComponent
class AutomationComponent:
pass
class AutomationSystem(esper.Processor):
def __init__(self, game_map: GameMap):
self.game_map = game_map
def process(self, dt):
for ent, (automation, pos) in self.world.get_components(AutomationComponent, PositionComponent):
if self.world.has_component(ent, PathfindingComponent):
continue
resource = self.detect_closest_resource(pos, ent)
if resource is None:
# TODO: Check if target position is not out of map bounds
self.world.add_component(ent, PathfindingComponent((pos.grid_position[0] * 32 + 64, pos.grid_position[1] * 32 + 64)))
# Move somewhere else
else:
target = self.world.component_for_entity(resource, PositionComponent).grid_position
self.world.add_component(ent, PathfindingComponent((target[0] * 32, target[1] * 32), True))
# Go collect target resource
def detect_closest_resource(self, position: PositionComponent, target_entity: int):
entity_position = position.grid_position
x_range = [entity_position[0] - 5, entity_position[0] + 5]
y_range = [entity_position[1] - 5, entity_position[1] + 5]
# Check if range is not out of map bounds
if x_range[0] < 0:
x_range[0] = 0
if x_range[1] >= self.game_map.width:
x_range[1] = self.game_map.width - 1
if y_range[0] < 0:
y_range[0] = 0
if y_range[1] >= self.game_map.height:
y_range[1] = self.game_map.height - 1
found_resource = [-1, 200000]
for y in range(y_range[0], y_range[1]):
for x in range(x_range[0], x_range[1]):
ent = self.game_map.get_entity([x, y])
if ent == target_entity:
continue
if ent is not None and self.world.has_component(ent, ResourceComponent):
res_position = self.world.component_for_entity(ent, PositionComponent).grid_position
distance = abs(entity_position[0] - res_position[0]) + abs(entity_position[1] - res_position[1])
if found_resource[1] > distance:
found_resource = [ent, distance]
if found_resource[0] == -1:
return None
else:
return found_resource[0]

View File

@ -1,11 +1,10 @@
import operator
from survival import esper
from survival.components.on_collision_component import OnCollisionComponent
from survival.components.OnCollisionComponent import OnCollisionComponent
from survival.components.moving_component import MovingComponent
from survival.components.position_component import PositionComponent
from survival.game.enums import Direction
from survival.systems.consumption_system import ConsumeComponent
from survival.enums import Direction
class CollisionSystem(esper.Processor):
@ -13,30 +12,24 @@ class CollisionSystem(esper.Processor):
self.map = game_map
def process(self, dt):
for ent, (pos, moving, onCol) in self.world.get_components(PositionComponent, MovingComponent,
OnCollisionComponent):
for ent, (pos, moving, onCol) in self.world.get_components(PositionComponent, MovingComponent, OnCollisionComponent):
if moving.target is not None:
continue
moving.checked_collision = True
vector = Direction.get_vector(pos.direction)
moving.target = tuple(map(operator.add, vector, pos.grid_position))
moving.direction_vector = vector
if self.check_collision(moving.target):
self.world.add_component(ent, ConsumeComponent(0.05))
self.world.remove_component(ent, MovingComponent)
onCol.call_all()
colliding_object: int = self.map.get_entity(moving.target)
if colliding_object is None or not self.world.entity_exists(colliding_object):
continue
onCol.callAll()
colliding_object : int = self.map.get_entity(moving.target)
if self.world.has_component(colliding_object, OnCollisionComponent):
self.world.component_for_entity(colliding_object, OnCollisionComponent).call_all()
self.world.component_for_entity(colliding_object, OnCollisionComponent).callAll()
else:
self.map.move_entity(pos.grid_position, moving.target)
self.world.add_component(ent, ConsumeComponent(self.map.get_cost(moving.target)))
pos.grid_position = moving.target
def check_collision(self, pos):

View File

@ -1,64 +0,0 @@
import random
from survival import esper
from survival.components.consumption_component import ConsumptionComponent
from survival.components.inventory_component import InventoryComponent
from survival.components.learning_component import LearningComponent
from survival.components.moving_component import MovingComponent
from survival.generators.resource_type import ResourceType
class ConsumeComponent:
def __init__(self, cost):
self.cost = cost
class ConsumptionSystem(esper.Processor):
CONSUMPTION_FACTOR = 0.05
CONSUMPTION_RANGE = 0.07
def __init__(self, callback):
self.callback = callback
def process(self, dt):
cons: ConsumptionComponent
inventory: InventoryComponent
c: ConsumeComponent
for ent, (cons, inventory, c) in self.world.get_components(ConsumptionComponent, InventoryComponent,
ConsumeComponent):
for resource in cons.status.keys():
cons.status[resource] -= c.cost * self.CONSUMPTION_FACTOR + random.uniform(-self.CONSUMPTION_RANGE,
self.CONSUMPTION_RANGE)
if cons.status[resource] < 0:
inventory.items[resource] -= 1
cons.status[resource] = 1
if self.world.has_component(ent, LearningComponent):
for resource in cons.status.keys():
if inventory.items[resource] <= 0 and self.world.has_component(ent, LearningComponent):
# If entity has run out of items
learning: LearningComponent = self.world.component_for_entity(ent, LearningComponent)
learning.reward -= 1
learning.done = True
break
else:
self.callback(ent)
self.world.remove_component(ent, ConsumeComponent)
# cons.timer -= dt
# if cons.timer > 0:
# continue
# cons.timer = cons.timer_value
#
# if self.world.has_component(ent, LearningComponent):
# # If no item was picked up
# if cons.last_inventory_state == inventory.total_items_count():
# learning: LearningComponent = self.world.component_for_entity(ent, LearningComponent)
# learning.reward += -10
# learning.done = True
# cons.last_inventory_state = inventory.total_items_count()
# else:
# if inventory.has_item(ResourceType.FOOD):
# inventory.remove_item(ResourceType.FOOD, 1)
# else:
# self.callback()

View File

@ -1,22 +1,14 @@
from survival import esper
from survival.components.position_component import PositionComponent
from survival.components.sprite_component import SpriteComponent
from survival.game.user_interface import UserInterface
class DrawSystem(esper.Processor):
def __init__(self, camera):
self.camera = camera
self.ui = UserInterface(self.camera.window)
def initialize_interface(self, inventory):
self.ui.load_inventory(inventory)
def process(self, dt):
for ent, (sprite, pos) in self.world.get_components(SpriteComponent, PositionComponent):
sprite.image.pos = pos.position
sprite.image.origin = (32 * pos.direction.value, 0)
self.camera.draw(sprite.image)
if self.ui.initialized:
self.ui.update()
self.ui.draw()

View File

@ -1,18 +1,16 @@
import pygame
from survival import esper, GameMap
from survival import esper
from survival.components.direction_component import DirectionChangeComponent
from survival.components.input_component import InputComponent
from survival.components.moving_component import MovingComponent
from survival.components.pathfinding_component import PathfindingComponent
from survival.components.position_component import PositionComponent
from survival.components.resource_component import ResourceComponent
class InputSystem(esper.Processor):
def __init__(self, camera, game_map: GameMap):
def __init__(self, camera):
self.camera = camera
self.game_map = game_map
def process(self, dt):
for ent, (inp, pos) in self.world.get_components(InputComponent, PositionComponent):
@ -22,11 +20,7 @@ class InputSystem(esper.Processor):
pos = pygame.mouse.get_pos()
pos = (pos[0] - self.camera.camera.left, pos[1] - self.camera.camera.top)
if not self.world.has_component(ent, PathfindingComponent):
target_ent = self.game_map.get_entity([int(pos[0] / 32), int(pos[1]/ 32)])
if target_ent is not None and self.world.has_component(target_ent, ResourceComponent):
self.world.add_component(ent, PathfindingComponent(pos))
else:
self.world.add_component(ent, PathfindingComponent(pos))
self.world.add_component(ent, PathfindingComponent(pos))
if self.world.has_component(ent, MovingComponent):
continue

View File

@ -1,4 +1,4 @@
from survival import esper, GameMap
from survival import esper
from survival.components.movement_component import MovementComponent
from survival.components.moving_component import MovingComponent
from survival.components.position_component import PositionComponent
@ -6,20 +6,18 @@ from survival.components.sprite_component import SpriteComponent
class MovementSystem(esper.Processor):
def __init__(self, game_map: GameMap):
self.map = game_map
def __init__(self):
self.map = None
def process(self, dt):
for ent, (mov, pos, moving, sprite) in self.world.get_components(MovementComponent, PositionComponent,
MovingComponent,
SpriteComponent):
# cost = self.map.get_cost(moving.target)
# pos.position[0] += moving.direction_vector[0] * mov.speed * dt / 100 / cost
# pos.position[1] += moving.direction_vector[1] * mov.speed * dt / 100 / cost
#
# if abs(moving.target[0] * 32 - pos.position[0]) < 1 * mov.speed and abs(
# pos.position[1] - moving.target[1] * 32) < 1 * mov.speed:
# pos.position = [moving.target[0] * 32, moving.target[1] * 32]
# self.world.remove_component(ent, MovingComponent)
pos.position = [moving.target[0] * 32, moving.target[1] * 32]
self.world.remove_component(ent, MovingComponent)
pos.position[0] += moving.direction_vector[0] * mov.speed * dt / 100
pos.position[1] += moving.direction_vector[1] * mov.speed * dt / 100
if abs(moving.target[0] * 32 - pos.position[0]) < 0.1 * mov.speed and abs(
pos.position[1] - moving.target[1] * 32) < 0.1 * mov.speed:
pos.position = [moving.target[0] * 32, moving.target[1] * 32]
self.world.remove_component(ent, MovingComponent)

View File

@ -1,142 +0,0 @@
import random
from collections import deque
import torch
from survival import esper, GameMap
from survival.ai.genetic_algorithm import GeneticAlgorithm
from survival.components.direction_component import DirectionChangeComponent
from survival.components.inventory_component import InventoryComponent
from survival.components.moving_component import MovingComponent
from survival.components.position_component import PositionComponent
from survival.components.learning_component import LearningComponent
from survival.components.time_component import TimeComponent
from survival.ai.graph_search import Action
from survival.ai.learning_utils import get_state, LearningUtils
from survival.ai.model import LinearQNetwork, QTrainer
from survival.settings import LEARN, MUTATE_NETWORKS
MAX_MEMORY = 100_000
BATCH_SIZE = 1000
class NeuralSystem(esper.Processor):
def __init__(self, game_map: GameMap, callback):
self.game_map = game_map
self.reset_game = callback
self.n_games = 0 # number of games played
if MUTATE_NETWORKS:
self.starting_epsilon = GeneticAlgorithm.GAMES_PER_NETWORK / 2
else:
self.starting_epsilon = 100
self.epsilon = 0 # controlls the randomness
self.gamma = 0.9 # discount rate
self.memory = deque(maxlen=MAX_MEMORY) # exceeding memory removes the left elements to make more space
self.model = None # self.model = LinearQNetwork.load(11, 256, 3)
self.trainer = None # QTrainer(self.model, lr=LR, gamma=self.gamma)
self.utils = LearningUtils()
self.best_action = None
def load_model(self, model: LinearQNetwork):
self.model = model
self.trainer = QTrainer(self.model, self.model.network_params['ratio'], self.gamma,
self.model.network_params['optimizer'])
self.utils = LearningUtils()
self.memory = deque(maxlen=MAX_MEMORY)
self.starting_epsilon = GeneticAlgorithm.GAMES_PER_NETWORK / 2
self.n_games = 0
def remember(self, state, action, reward, next_state, done):
self.memory.append((state, action, reward, next_state, done))
def train_short_memory(self, state, action, reward, next_state, done):
self.trainer.train_step(state, action, reward, next_state, done)
def train_long_memory(self):
if len(self.memory) > BATCH_SIZE:
mini_sample = random.sample(self.memory, BATCH_SIZE)
else:
mini_sample = self.memory
states, actions, rewards, next_states, dones = zip(*mini_sample)
self.trainer.train_step(states, actions, rewards, next_states, dones)
def get_action(self, state):
self.epsilon = self.starting_epsilon - self.n_games
final_move = [0, 0, 0]
if random.randint(0, 200) < self.epsilon:
move = random.randint(0, 2)
final_move[move] = 1
else:
state_zero = torch.tensor(state, dtype=torch.float)
prediction = self.model(state_zero)
move = torch.argmax(prediction).item()
final_move[move] = 1
return final_move
def process(self, dt):
for ent, (pos, inventory, time, learning) in self.world.get_components(PositionComponent, InventoryComponent,
TimeComponent, LearningComponent):
if not learning.made_step:
learning.reset()
self.best_action = None
# Get the closest resource | [entity, path, cost]
resource: [int, list, int] = self.game_map.find_nearest_resource(self.world, ent, pos)
if resource is not None:
# If resource was found get the best move chosen by A*
self.best_action = resource[1][0]
# Get current entity state
old_state = get_state(self, ent, resource)
# Predict the action
action = self.get_action(old_state)
# Save the action
learning.load_step(old_state, action, resource)
# Perform the action
act = Action.perform(self.world, ent, Action.from_array(action))
self.utils.append_action(act, pos)
# Add reward if chosen action was the best action
if act == self.best_action:
learning.reward += 1
continue
# Wait for the action to complete
if self.world.has_component(ent, DirectionChangeComponent) or self.world.has_component(ent,
MovingComponent):
continue
self.utils.check_last_actions(learning)
resource = learning.resource
if resource is None or not self.world.entity_exists(resource[0]):
# Find a new resource if no resource was found or the last one was consumed
resource = self.game_map.find_nearest_resource(self.world, ent, pos)
# Get new state
new_state = get_state(self, ent, resource)
# Train agent's memory
self.train_short_memory(learning.old_state, learning.action, learning.reward, new_state, learning.done)
self.remember(learning.old_state, learning.action, learning.reward, new_state, learning.done)
learning.made_step = False
if learning.done:
self.n_games += 1
if LEARN:
self.train_long_memory()
if learning.score > learning.record:
learning.record = learning.score
if LEARN and not MUTATE_NETWORKS:
self.model.save()
# print('Game', self.n_games, 'Score', learning.score, 'Record', learning.record)
self.utils.add_scores(learning, self.n_games)
self.model.scores.append(learning.score)
learning.score = 0
self.utils.plot()
self.reset_game()

View File

@ -3,20 +3,21 @@ from survival.components.direction_component import DirectionChangeComponent
from survival.components.movement_component import MovementComponent
from survival.components.moving_component import MovingComponent
from survival.components.position_component import PositionComponent
from survival.game.enums import Direction
from survival.ai.graph_search import graph_search, Action
from survival.enums import Direction
from survival.graph_search import graph_search, Action
from survival.systems.input_system import PathfindingComponent
class PathfindingMovementSystem(esper.Processor):
def __init__(self, game_map):
self.game_map = game_map
pass
def process(self, dt):
for ent, (pos, pathfinding, movement) in self.world.get_components(PositionComponent, PathfindingComponent,
MovementComponent):
if pathfinding.path is None:
pathfinding.path, cost = graph_search(self.game_map, pos, pathfinding.target_grid_pos, self.world)
pathfinding.path = graph_search(self.game_map, pos, pathfinding.target_grid_pos)
if len(pathfinding.path) < 1:
self.world.remove_component(ent, PathfindingComponent)

View File

@ -9,3 +9,4 @@ class TimeSystem(esper.Processor):
if time.timer > 1000:
time.add_time(1)
time.timer = 0
print(time)

View File

@ -1,18 +0,0 @@
from survival import esper
from survival.components.position_component import PositionComponent
from survival.components.vision_component import VisionComponent
class VisionSystem(esper.Processor):
def __init__(self, camera):
self.camera = camera
def process(self, dt):
pos: PositionComponent
vision: VisionComponent
for ent, (pos, vision) in self.world.get_components(PositionComponent, VisionComponent):
vision.update_positions(pos.position)
self.camera.window.blit(vision.surface_l, self.camera.apply(vision.l_pos))
self.camera.window.blit(vision.surface_r, self.camera.apply(vision.r_pos))
self.camera.window.blit(vision.surface_t, self.camera.apply(vision.t_pos))
self.camera.window.blit(vision.surface_b, self.camera.apply(vision.b_pos))

View File

@ -1,6 +1,6 @@
from survival.generators.tile_generator import TileGenerator
from survival.game.image import Image
from survival.game.tile import Tile
from survival.image import Image
from survival.tile import Tile
class TileLayer:
@ -8,6 +8,7 @@ class TileLayer:
self.width = width
self.height = height
self.tiles: list[list[Tile]] = TileGenerator.generate_biome_tiles(width, height)
# self.tiles: list[list[Tile]] = TileGenerator.generate_random_tiles(width, height)
self.image = Image('atlas.png')
def draw(self, camera, visible_area):