parent
8cc44f0044
commit
6a14ccab48
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,122 @@
|
||||
"""
|
||||
Neural network module
|
||||
"""
|
||||
|
||||
import copy
|
||||
import random
|
||||
import numpy as np
|
||||
from scipy.stats import truncnorm
|
||||
from config import min_mutation_rate, mutation_chance
|
||||
|
||||
# Generate random numbers
|
||||
def truncated_normal(mean=0, sd=1, low=0, upp=10):
|
||||
"""
|
||||
returns a truncated normal
|
||||
"""
|
||||
return truncnorm((low - mean) / sd, (upp - mean) / sd, loc=mean, scale=sd)
|
||||
|
||||
# changes value by random value in mutation range
|
||||
# modulo to keep value in range
|
||||
def mutate_by(value, mutation_rate):
|
||||
"""
|
||||
mutates a variable using a scale and a normal distribution
|
||||
"""
|
||||
if random.uniform(0, 1) < mutation_chance:
|
||||
return value + np.random.normal(0, mutation_rate)
|
||||
return value
|
||||
|
||||
# Neural network class
|
||||
class Nnetwork:
|
||||
"""
|
||||
Neural network class
|
||||
contains all the layers and weights and clone and evaluate function
|
||||
"""
|
||||
def __init__(self,
|
||||
numbers_of_nodes,
|
||||
mutation_rate):
|
||||
self.no_of_in_nodes = numbers_of_nodes[0]
|
||||
self.no_of_out_nodes = numbers_of_nodes[1]
|
||||
self.no_of_hidden_nodes = numbers_of_nodes[2]
|
||||
self.no_of_hidden_layers = numbers_of_nodes[3]
|
||||
self.mutation_rate = mutation_rate
|
||||
self.create_weight_matrices()
|
||||
|
||||
def create_weight_matrices(self):
|
||||
"""
|
||||
A method to initialize the weight matrices of the neural network
|
||||
"""
|
||||
# Input
|
||||
rad = 1 / np.sqrt(self.no_of_in_nodes)
|
||||
x = truncated_normal(mean=0, sd=1, low=-rad, upp=rad)
|
||||
self.weights_in_hidden = x.rvs(
|
||||
(self.no_of_hidden_nodes, self.no_of_in_nodes))
|
||||
|
||||
# Hidden layers
|
||||
rad = 1 / np.sqrt(self.no_of_hidden_nodes)
|
||||
self.hidden_layers = []
|
||||
for _ in range(self.no_of_hidden_layers):
|
||||
x = truncated_normal(mean=0, sd=1, low=-rad, upp=rad)
|
||||
weights_hidden_hidden = x.rvs(
|
||||
(self.no_of_hidden_nodes, self.no_of_hidden_nodes))
|
||||
self.hidden_layers.append(weights_hidden_hidden)
|
||||
|
||||
# Output
|
||||
x = truncated_normal(mean=0, sd=1, low=-rad, upp=rad)
|
||||
self.weights_hidden_out = x.rvs(
|
||||
(self.no_of_out_nodes, self.no_of_hidden_nodes))
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
returns a mutated copy of the neural network
|
||||
"""
|
||||
network_copy = copy.deepcopy(self)
|
||||
|
||||
# input weights
|
||||
for i, weight in enumerate(network_copy.weights_in_hidden):
|
||||
network_copy.weights_in_hidden[i] = mutate_by(weight, self.mutation_rate)
|
||||
|
||||
# hiddens weights
|
||||
for i, layer in enumerate(network_copy.hidden_layers):
|
||||
for j, weight in enumerate(layer):
|
||||
network_copy.hidden_layers[i][j] = mutate_by(weight, self.mutation_rate)
|
||||
|
||||
# output weights
|
||||
for i, weight in enumerate(network_copy.weights_hidden_out):
|
||||
network_copy.weights_hidden_out[i] = mutate_by(weight, self.mutation_rate)
|
||||
|
||||
# change mutation rate
|
||||
network_copy.mutation_rate = abs(mutate_by(self.mutation_rate, self.mutation_rate))
|
||||
network_copy.mutation_rate = max(network_copy.mutation_rate, min_mutation_rate)
|
||||
|
||||
return network_copy
|
||||
|
||||
|
||||
def evaluate(self, input_vector):
|
||||
"""
|
||||
running the network with an input vector 'input_vector'.
|
||||
'input_vector' can be tuple, list or ndarray
|
||||
"""
|
||||
# Turn the input vector into a column vector:
|
||||
input_vector = np.array(input_vector, ndmin=2).T
|
||||
input_hidden = self.weights_in_hidden @ input_vector
|
||||
|
||||
for _, layer in enumerate(self.hidden_layers):
|
||||
input_hidden = layer @ input_hidden
|
||||
|
||||
output_vector = self.weights_hidden_out @ input_hidden
|
||||
return output_vector
|
||||
|
||||
|
||||
def run(self, input_data):
|
||||
"""
|
||||
Returns the resulting action
|
||||
decided by biggest value
|
||||
"""
|
||||
output_vector = self.evaluate(input_data)
|
||||
maximum = np.amax(output_vector)
|
||||
|
||||
for i, value in enumerate(output_vector):
|
||||
if value == maximum:
|
||||
return i
|
||||
return 0
|
||||
|
||||
@ -0,0 +1,219 @@
|
||||
"""
|
||||
The main app module
|
||||
contains the genetic algorithm
|
||||
"""
|
||||
|
||||
import threading
|
||||
import numpy as np
|
||||
import pygame
|
||||
import ai
|
||||
import tetris
|
||||
import gui
|
||||
from button import Button
|
||||
from config import s_width, s_height, start_btn_image, start_btn_position, start_btn_scale, load_btn_image, load_btn_position, load_btn_scale, run_ten_btn_image, run_ten_btn_position, run_ten_btn_scale, \
|
||||
number_of_networks, no_of_out_nodes, no_of_in_nodes, mutation_rate, num_of_parts, graph_position, graph_size, \
|
||||
save_file, col
|
||||
import saves
|
||||
from menu import show_menu
|
||||
|
||||
# false positive
|
||||
# pylint: disable=no-member
|
||||
|
||||
def simulation_thread(neural_networks, variables, setting, dimensions):
|
||||
"""
|
||||
simulates a single game with parallel to others
|
||||
"""
|
||||
(index, show_gui, progress) = variables
|
||||
neural_networks[index]["score"] = (0, 0)
|
||||
for _ in range (3):
|
||||
setting[5] += 1
|
||||
subsurface = window.subsurface(0, 0, s_width / 2, s_height)
|
||||
(score, block_count) = tetris.game(subsurface, neural_networks[index]["network"].run, setting, dimensions, show_gui) # play one game
|
||||
#neural_networks[index]["score"] += (neural_networks[index]["score"][0] + score, neural_networks[index]["score"][1] + block_count)
|
||||
neural_networks[index]["score"] = (neural_networks[index]["score"][0] + float(score), neural_networks[index]["score"][1] + float(block_count))
|
||||
progress[0] += 1/3
|
||||
gui.draw_simulation_progress(
|
||||
subsurface, progress[0], dimensions[0], dimensions[1], dimensions[2])
|
||||
|
||||
def calculate_score(score):
|
||||
"""
|
||||
Calculates the final score
|
||||
"""
|
||||
return score[0] + score[1]
|
||||
#return score[0]
|
||||
|
||||
# plays a game for each neural network and saves it's score
|
||||
def simulate_generation(neural_networks, settings, block_size, play_width, play_height):
|
||||
"""
|
||||
runs many games in parallel and simulates a whole generation
|
||||
"""
|
||||
|
||||
# simulation progress counter
|
||||
progress = [-1]
|
||||
|
||||
# run parallel simulations
|
||||
simulation_threads = []
|
||||
|
||||
# first network will show gui
|
||||
simulation_threads.append(threading.Thread(target=simulation_thread, args=(
|
||||
neural_networks, (0, True, progress), settings, (block_size, play_width, play_height))))
|
||||
simulation_threads[0].start()
|
||||
|
||||
pool_size = int(number_of_networks / num_of_parts) * (num_of_parts - 1)
|
||||
for i in range(pool_size):
|
||||
simulation_threads.append(threading.Thread(target=simulation_thread, args=(
|
||||
# list of neural networks
|
||||
neural_networks,
|
||||
# index, gui, progress bar
|
||||
(number_of_networks - pool_size + i, False, progress),
|
||||
# game settings
|
||||
settings, (block_size, play_width, play_height))))
|
||||
simulation_threads[i + 1].start()
|
||||
|
||||
simulation_threads[0].join()
|
||||
# wait for all threads to finish
|
||||
for i in range(pool_size):
|
||||
# first network will show gui
|
||||
simulation_threads[i + 1].join()
|
||||
|
||||
neural_networks.sort(key=lambda item: calculate_score(item["score"]))
|
||||
|
||||
return neural_networks[::-1] # return sorted from largest to smallest
|
||||
|
||||
|
||||
def generate_networks(in_count, settings):
|
||||
"""
|
||||
generates brand new network and clones it to acheve desired amount
|
||||
"""
|
||||
# generate networks
|
||||
neural_network = ai.Nnetwork((in_count, no_of_out_nodes, settings[3], settings[4]),
|
||||
mutation_rate=mutation_rate)
|
||||
|
||||
networks = [{
|
||||
"score": (0, 0),
|
||||
"network": neural_network.clone()
|
||||
} for i in range(number_of_networks)]
|
||||
|
||||
print("networks are ready")
|
||||
return networks
|
||||
|
||||
|
||||
def evolve(networks, logs, blob, dimensions, buttons):
|
||||
"""
|
||||
runs a generation and then sorts and evolves the neural networks
|
||||
"""
|
||||
networks = simulate_generation(
|
||||
networks, blob[1], dimensions[0], dimensions[1], dimensions[2])
|
||||
|
||||
# create offsprings to replenish population - roulette selection
|
||||
roulette_wheel_scores = [calculate_score(
|
||||
network["score"]) for network in networks]
|
||||
roulette_wheel_networks = [network["network"] for network in networks]
|
||||
|
||||
print([round(score, 2) for score in roulette_wheel_scores])
|
||||
|
||||
score_sum = sum(roulette_wheel_scores)
|
||||
print(score_sum)
|
||||
average = score_sum / number_of_networks
|
||||
print("average score: " + str(average))
|
||||
print("generation: " + str(blob[0]))
|
||||
logs[0].append(max(roulette_wheel_scores))
|
||||
logs[1].append(average)
|
||||
|
||||
# kill the lower scoring half
|
||||
networks = networks[0:int(number_of_networks / num_of_parts)]
|
||||
|
||||
for _ in range(int(number_of_networks / num_of_parts)):
|
||||
# find random parent
|
||||
score_target = np.random.randint(0, score_sum)
|
||||
for j, score in enumerate([x**4 for x in roulette_wheel_scores]):
|
||||
score_target -= score
|
||||
# generate offsprings
|
||||
if score_target <= 0:
|
||||
for _ in range(num_of_parts - 1):
|
||||
networks.append({
|
||||
"score": (0, 0),
|
||||
"network": roulette_wheel_networks[j].clone()
|
||||
})
|
||||
break
|
||||
|
||||
buttons[0].draw(window)
|
||||
buttons[1].draw(window)
|
||||
buttons[2].draw(window)
|
||||
pygame.display.update()
|
||||
gui.draw_graph(window, graph_position, graph_size, logs, (blob[0], dimensions[0]))
|
||||
|
||||
# save the best ai
|
||||
saves.save_network(networks[0], save_file)
|
||||
return networks
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
main function
|
||||
"""
|
||||
settings = show_menu(window)
|
||||
# (row, col, number_of_blocks, no_of_hidden_layers, no_of_hidden_nodes, rng_seed) = settings
|
||||
block_size = int((600) / settings[0]) # size of block
|
||||
# play window width; 300/10 = 30 width per block
|
||||
# play window height; 600/20 = 20 height per block
|
||||
|
||||
gui.draw_graph(window, graph_position, graph_size, [], (0, block_size))
|
||||
|
||||
# create button instances
|
||||
# button to run one evolution cycle
|
||||
start_img = pygame.image.load(start_btn_image).convert_alpha()
|
||||
start_button = Button(start_btn_position, start_img, start_btn_scale)
|
||||
start_button.draw(window)
|
||||
|
||||
# button to load a neural network from save file
|
||||
load_img = pygame.image.load(load_btn_image).convert_alpha()
|
||||
load_button = Button(load_btn_position, load_img, load_btn_scale)
|
||||
load_button.draw(window)
|
||||
|
||||
# button to run ten generations
|
||||
run_ten_img = pygame.image.load(run_ten_btn_image).convert_alpha()
|
||||
run_ten_button = Button(
|
||||
run_ten_btn_position, run_ten_img, run_ten_btn_scale)
|
||||
run_ten_button.draw(window)
|
||||
|
||||
pygame.display.update()
|
||||
|
||||
max_score_log = []
|
||||
average_score_log = []
|
||||
|
||||
networks = generate_networks((no_of_in_nodes - col) + settings[1], settings)
|
||||
generation = 0
|
||||
|
||||
run = True
|
||||
while run:
|
||||
# run once with gui
|
||||
if start_button.click():
|
||||
networks = evolve(networks, [max_score_log, average_score_log],
|
||||
(generation, settings), (block_size, settings[1] * block_size , settings[0] * block_size), (start_button, load_button, run_ten_button))
|
||||
generation += 1
|
||||
|
||||
# run ten times
|
||||
if run_ten_button.click():
|
||||
for _ in range(10):
|
||||
networks = evolve(networks, [max_score_log, average_score_log],
|
||||
(generation, settings), (block_size, settings[1] * block_size , settings[0] * block_size), (start_button, load_button, run_ten_button))
|
||||
generation += 1
|
||||
|
||||
if load_button.click():
|
||||
networks = [saves.load_network(save_file)] * number_of_networks
|
||||
print("network loaded")
|
||||
generation = 0
|
||||
|
||||
# event handler
|
||||
for event in pygame.event.get():
|
||||
# quit game
|
||||
if event.type == pygame.QUIT:
|
||||
run = False
|
||||
|
||||
pygame.quit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
window = pygame.display.set_mode((s_width, s_height))
|
||||
|
||||
main()
|
||||
@ -0,0 +1,44 @@
|
||||
"""
|
||||
Button module
|
||||
"""
|
||||
|
||||
import pygame
|
||||
|
||||
# button class
|
||||
class Button():
|
||||
"""
|
||||
Button class
|
||||
"""
|
||||
def __init__(self, position, image, scale):
|
||||
width = image.get_width()
|
||||
height = image.get_height()
|
||||
self.image = pygame.transform.scale(
|
||||
image, (int(width * scale), int(height * scale)))
|
||||
self.rect = self.image.get_rect()
|
||||
self.rect.topleft = position
|
||||
self.clicked = False
|
||||
|
||||
def click(self):
|
||||
"""
|
||||
returns true if the button was clicked
|
||||
"""
|
||||
action = False
|
||||
# get mouse position
|
||||
pos = pygame.mouse.get_pos()
|
||||
|
||||
# check mouseover and clicked conditions
|
||||
if self.rect.collidepoint(pos):
|
||||
if pygame.mouse.get_pressed()[0] == 1 and not self.clicked:
|
||||
self.clicked = True
|
||||
action = True
|
||||
|
||||
if pygame.mouse.get_pressed()[0] == 0:
|
||||
self.clicked = False
|
||||
|
||||
return action
|
||||
|
||||
def draw(self, window):
|
||||
"""
|
||||
draw button on screen
|
||||
"""
|
||||
window.blit(self.image, (self.rect.x, self.rect.y))
|
||||
@ -0,0 +1,149 @@
|
||||
"""
|
||||
Stores all important configuration data
|
||||
"""
|
||||
col = 8 # number of columns
|
||||
row = 15 # number of rows
|
||||
|
||||
s_width = 1000 # window width
|
||||
s_height = 750 # window height
|
||||
block_size = int((600) / row) # size of block
|
||||
play_width = col * block_size # play window width; 300/10 = 30 width per block
|
||||
play_height = row * block_size # play window height; 600/20 = 20 height per block
|
||||
top_left_x = 0 # play area to left x
|
||||
top_left_y = s_height - play_height - 50 # play area to left y
|
||||
|
||||
fall_speed = 50 # determines the speed at which blocks fall
|
||||
# how many inputs will the game wait for when running without graphical mode
|
||||
turn_count = 1
|
||||
max_block_count = 13 # maximum amount of blocks allowed to fall to prevent infinite games
|
||||
|
||||
no_of_in_nodes = col + 8 # number of input nodes - don't change
|
||||
no_of_out_nodes = 5 # number of output nodes - don't change
|
||||
no_of_hidden_nodes = 15 # number of hidden nodes in each hidden layer
|
||||
no_of_hidden_layers = 2 # number of hidden layers
|
||||
mutation_rate = 0.4 # starting mutation rate
|
||||
mutation_chance = 0.1 # chance that the mutation will happen
|
||||
min_mutation_rate = 0.1 # munimum mutation rate
|
||||
|
||||
number_of_networks = 10 # how many neural networks will be created, has to be even
|
||||
num_of_parts = 3 # into how many parts will be the old and new networks divided
|
||||
|
||||
# Shapes
|
||||
S = [['....',
|
||||
'....',
|
||||
'..00',
|
||||
'.00.'],
|
||||
['....',
|
||||
'..0.',
|
||||
'..00',
|
||||
'...0']]
|
||||
|
||||
Z = [['....',
|
||||
'....',
|
||||
'.00.',
|
||||
'..00'],
|
||||
['....',
|
||||
'..0.',
|
||||
'.00.',
|
||||
'.0..']]
|
||||
|
||||
I = [['..0.',
|
||||
'..0.',
|
||||
'..0.',
|
||||
'..0.'],
|
||||
['....',
|
||||
'0000',
|
||||
'....',
|
||||
'....']]
|
||||
|
||||
O = [['....',
|
||||
'....',
|
||||
'.00.',
|
||||
'.00.']]
|
||||
|
||||
J = [['....',
|
||||
'.0..',
|
||||
'.000',
|
||||
'....'],
|
||||
['....',
|
||||
'..00',
|
||||
'..0.',
|
||||
'..0.'],
|
||||
['....',
|
||||
'....',
|
||||
'.000',
|
||||
'...0'],
|
||||
['....',
|
||||
'..0.',
|
||||
'..0.',
|
||||
'.00.']]
|
||||
|
||||
L = [['....',
|
||||
'...0',
|
||||
'.000',
|
||||
'....'],
|
||||
['....',
|
||||
'..0.',
|
||||
'..0.',
|
||||
'..00'],
|
||||
['....',
|
||||
'....',
|
||||
'.000',
|
||||
'.0..'],
|
||||
['....',
|
||||
'.00.',
|
||||
'..0.',
|
||||
'..0.']]
|
||||
|
||||
T = [['....',
|
||||
'..0.',
|
||||
'.000',
|
||||
'....'],
|
||||
['....',
|
||||
'..0.',
|
||||
'..00',
|
||||
'..0.'],
|
||||
['....',
|
||||
'....',
|
||||
'.000',
|
||||
'..0.'],
|
||||
['....',
|
||||
'..0.',
|
||||
'.00.',
|
||||
'..0.']]
|
||||
|
||||
num_of_blocks = 4
|
||||
shape_list = [I, Z, L, O, J, S, T]
|
||||
shape_colors = [(0, 0, 255), (255, 0, 0), (0, 255, 255),
|
||||
(255, 255, 0), (255, 165, 0), (0, 255, 0), (128, 0, 128)]
|
||||
border_color = (255, 255, 255) # border color
|
||||
text_color = (255, 255, 255) # text color
|
||||
background_color = (0, 0, 0) # background color
|
||||
|
||||
graph_size = (s_width / 2, 300) # size of the graph
|
||||
graph_position = (s_width / 2, top_left_y + play_height -
|
||||
graph_size[1]) # position of the graph
|
||||
graph_colors = [(255, 165, 0), (0, 0, 255)]
|
||||
|
||||
save_file = "app/saves/ai_save.txt" # path to the ai save file
|
||||
|
||||
caption = 'Tetris machine learning'
|
||||
|
||||
start_btn_image = 'app/gui/start_btn.png' # path to the button image
|
||||
# start button position
|
||||
start_btn_position = (graph_position[0] * 1.05, graph_position[1] - 70)
|
||||
start_btn_scale = 0.5 # start button scale
|
||||
|
||||
load_btn_image = 'app/gui/load_btn.png' # path to the button image
|
||||
# load button position
|
||||
load_btn_position = (graph_position[0] * 1.70, graph_position[1] - 70)
|
||||
load_btn_scale = start_btn_scale # load button scale
|
||||
|
||||
run_ten_btn_image = 'app/gui/run_ten_btn.png' # path to the button image
|
||||
# run ten times button position
|
||||
run_ten_btn_position = (graph_position[0] * 1.38, graph_position[1] - 70)
|
||||
run_ten_btn_scale = start_btn_scale # run ten button scale
|
||||
|
||||
continue_btn_image = 'app/gui/continue_btn.png' # path to the button image
|
||||
continue_btn_position = ((s_width) / 2 - 65, 570) # continue button position
|
||||
continue_btn_scale = start_btn_scale # continue button scale
|
||||
@ -0,0 +1,159 @@
|
||||
"""
|
||||
Handles the application gui and draws the game
|
||||
"""
|
||||
|
||||
import pygame
|
||||
from config import s_width, number_of_networks, num_of_parts, border_color, text_color, background_color, graph_colors, top_left_x, top_left_y, caption
|
||||
|
||||
pygame.font.init()
|
||||
|
||||
|
||||
def draw_text_middle(text, size, color, surface, data):
|
||||
"""
|
||||
Draws text in the middle of the window
|
||||
"""
|
||||
(top, play_width) = data
|
||||
font = pygame.font.SysFont('Arial', size, bold=False, italic=True)
|
||||
label = font.render(text, 1, color)
|
||||
|
||||
surface.blit(label, ((s_width - play_width) // 2 + play_width/2 - (label.get_width()/2),
|
||||
top_left_y + top - (label.get_height()/2)))
|
||||
|
||||
|
||||
def draw_graph(surface, graph_position, graph_size, data, args):
|
||||
"""
|
||||
Draws a performance history graph
|
||||
"""
|
||||
(generation, block_size) = args
|
||||
rectangle = (graph_position[0], graph_position[1],
|
||||
graph_size[0], graph_size[1])
|
||||
surface.subsurface((graph_position[0], graph_position[1],
|
||||
graph_size[0], graph_size[1] + 50)).fill(background_color)
|
||||
# empty graph
|
||||
if len(data) == 0:
|
||||
# draw border
|
||||
pygame.draw.rect(surface, border_color, rectangle, 4)
|
||||
return
|
||||
|
||||
maximum = max(data[0])
|
||||
# draw each dataset as separate line
|
||||
for i, line in enumerate(data):
|
||||
line = [0] + line
|
||||
line = [(graph_position[0] + i * (graph_size[0] / (len(line) - 1)), graph_position[1] +
|
||||
graph_size[1] - (graph_size[1] * point / maximum) * 0.95) for i, point in enumerate(line)]
|
||||
pygame.draw.lines(surface, graph_colors[i], False, line, 2)
|
||||
|
||||
# draw border
|
||||
pygame.draw.rect(surface, border_color, rectangle, 4)
|
||||
|
||||
# print data
|
||||
font = pygame.font.SysFont('Arial', int(block_size / 2))
|
||||
text = "gen: " + str(generation) + " max: " + \
|
||||
str(round(data[0][-1], 2)) + " avg: " + str(round(data[1][-1], 2))
|
||||
label = font.render(text, 1, text_color)
|
||||
surface.blit(label, ((graph_position[0] + graph_size[0] / 2) - (
|
||||
label.get_width() / 2), (graph_position[1] + graph_size[1]) + block_size / 2))
|
||||
|
||||
pygame.display.update(
|
||||
rectangle[0], rectangle[1], rectangle[2], rectangle[3] + 40)
|
||||
|
||||
|
||||
def draw_grid(surface, grid, block_size, play_width, play_height):
|
||||
"""
|
||||
Draws the lines of the grid for the game
|
||||
"""
|
||||
r = g = b = 0
|
||||
grid_color = (r, g, b)
|
||||
|
||||
for i in range(len(grid)):
|
||||
# draw grey horizontal lines
|
||||
pygame.draw.line(surface, grid_color, (top_left_x, top_left_y + i * block_size),
|
||||
(top_left_x + play_width, top_left_y + i * block_size))
|
||||
for j in range(len(grid[0])):
|
||||
# draw grey vertical lines
|
||||
pygame.draw.line(surface, grid_color, (top_left_x + j * block_size, top_left_y),
|
||||
(top_left_x + j * block_size, top_left_y + play_height))
|
||||
|
||||
|
||||
def draw_simulation_progress(surface, progress, block_size, play_width, play_height):
|
||||
"""
|
||||
Prints the simulation progress
|
||||
"""
|
||||
font = pygame.font.SysFont('Arial', block_size)
|
||||
text = str(int(progress / number_of_networks * 100 / (num_of_parts - 1) * num_of_parts)) + " %"
|
||||
label = font.render(text, 1, text_color)
|
||||
|
||||
rectangle = (top_left_x + play_width / 2) - (label.get_width() / 2), top_left_y + \
|
||||
play_height + 10, top_left_x + play_width, label.get_width() * 2
|
||||
pygame.draw.rect(surface, background_color, rectangle)
|
||||
surface.blit(label, ((top_left_x + play_width / 2) -
|
||||
(label.get_width() / 2), top_left_y + play_height + 10))
|
||||
pygame.display.update(rectangle)
|
||||
|
||||
|
||||
def draw_next_shape(piece, surface, block_size, play_width):
|
||||
"""
|
||||
Draws the upcoming piece
|
||||
"""
|
||||
font = pygame.font.SysFont('Arial', int(block_size / 2))
|
||||
label = font.render('Next shape:', 1, text_color)
|
||||
|
||||
start_x = top_left_x + play_width + 10
|
||||
start_y = top_left_y + block_size
|
||||
|
||||
shape_format = piece.shape[piece.rotation % len(piece.shape)]
|
||||
|
||||
for i, line in enumerate(shape_format):
|
||||
row_list = list(line)
|
||||
for j, column in enumerate(row_list):
|
||||
if column == '0':
|
||||
pygame.draw.rect(surface, piece.color, (start_x + j*block_size,
|
||||
start_y + i*block_size, block_size, block_size), 0)
|
||||
|
||||
surface.blit(label, (start_x, start_y - 30))
|
||||
|
||||
|
||||
def draw_window(surface, grid, block_size, dimensions, score=0):
|
||||
"""
|
||||
Draws the content of the window
|
||||
"""
|
||||
(play_width, play_height) = dimensions
|
||||
surface.fill(background_color) # fill the surface with background color
|
||||
|
||||
pygame.font.init() # initialise font
|
||||
font = pygame.font.SysFont('Calibri', block_size * 2, bold=True)
|
||||
# initialise 'Tetris' text with white
|
||||
label = font.render('TETRIS', 1, text_color)
|
||||
|
||||
# put on the center of the window
|
||||
surface.blit(label, ((top_left_x + play_width / 2) -
|
||||
(label.get_width() / 2), 30))
|
||||
|
||||
# current score
|
||||
font = pygame.font.SysFont('Arial', int(block_size / 2))
|
||||
label = font.render('Score: ' + str(score), 1, text_color)
|
||||
|
||||
start_x = top_left_x + play_width + 10
|
||||
start_y = top_left_y + block_size * 5
|
||||
|
||||
surface.blit(label, (start_x, start_y))
|
||||
|
||||
# draw content of the grid
|
||||
for i, row in enumerate(grid):
|
||||
for j, cell in enumerate(row):
|
||||
# pygame.draw.rect()
|
||||
# draw a rectangle shape
|
||||
# rect(Surface, color, Rect, width=0) -> Rect
|
||||
pygame.draw.rect(surface, cell, (top_left_x + j * block_size,
|
||||
top_left_y + i * block_size, block_size, block_size), 0)
|
||||
|
||||
# draw vertical and horizontal grid lines
|
||||
draw_grid(surface, grid, block_size, play_width, play_height)
|
||||
|
||||
# draw rectangular border around play area
|
||||
pygame.draw.rect(surface, border_color, (top_left_x,
|
||||
top_left_y, play_width, play_height), 4)
|
||||
|
||||
|
||||
# initial config
|
||||
pygame.display.set_caption(caption)
|
||||
@ -0,0 +1,72 @@
|
||||
"""
|
||||
Module which add an interactive textbox into pygame
|
||||
"""
|
||||
|
||||
# false positive
|
||||
# pylint: disable=no-member
|
||||
|
||||
import pygame as pg
|
||||
|
||||
COLOR_INACTIVE = pg.Color('lightskyblue3')
|
||||
COLOR_ACTIVE = pg.Color('dodgerblue2')
|
||||
FONT = pg.font.Font(None, 32)
|
||||
|
||||
|
||||
class InputBox:
|
||||
"""
|
||||
Box for data input
|
||||
"""
|
||||
def __init__(self, rect, text=''):
|
||||
self.rect = pg.Rect(rect)
|
||||
self.color = COLOR_INACTIVE
|
||||
self.text = text
|
||||
self.txt_surface = FONT.render(text, True, self.color)
|
||||
self.active = False
|
||||
|
||||
def handle_event(self, event):
|
||||
"""
|
||||
handles the button click event
|
||||
"""
|
||||
if event.type == pg.MOUSEBUTTONDOWN:
|
||||
# If the user clicked on the input_box rect.
|
||||
if self.rect.collidepoint(event.pos):
|
||||
# Toggle the active variable.
|
||||
self.active = not self.active
|
||||
else:
|
||||
self.active = False
|
||||
# Change the current color of the input box.
|
||||
self.color = COLOR_ACTIVE if self.active else COLOR_INACTIVE
|
||||
if event.type == pg.KEYDOWN:
|
||||
if self.active:
|
||||
if event.key == pg.K_RETURN:
|
||||
print(self.text)
|
||||
self.text = ''
|
||||
elif event.key == pg.K_BACKSPACE:
|
||||
self.text = self.text[:-1]
|
||||
else:
|
||||
if str(event.unicode).isnumeric():
|
||||
self.text += event.unicode
|
||||
# Re-render the text.
|
||||
self.txt_surface = FONT.render(self.text, True, self.color)
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Resize the box if the text is too long.
|
||||
"""
|
||||
width = max(200, self.txt_surface.get_width()+10)
|
||||
self.rect.w = width
|
||||
|
||||
def draw(self, screen):
|
||||
"""
|
||||
Draws the button on the screen
|
||||
"""
|
||||
screen.blit(self.txt_surface, (self.rect.x+5, self.rect.y+5))
|
||||
pg.draw.rect(screen, self.color, self.rect, 2)
|
||||
|
||||
def value(self):
|
||||
"""
|
||||
Returns a numerical value of the box
|
||||
"""
|
||||
if self.text == '':
|
||||
return 0
|
||||
return int(self.text)
|
||||
@ -0,0 +1,70 @@
|
||||
"""
|
||||
Starting menu
|
||||
"""
|
||||
import pygame
|
||||
from inputbox import InputBox
|
||||
import gui
|
||||
from button import Button
|
||||
from config import background_color, text_color, s_width, row, col, no_of_hidden_layers, no_of_hidden_nodes, continue_btn_image, continue_btn_position, continue_btn_scale, play_width, num_of_blocks
|
||||
|
||||
# false positive
|
||||
# pylint: disable=no-member
|
||||
|
||||
def show_menu(window):
|
||||
"""
|
||||
displays the starting menu and returns settings
|
||||
"""
|
||||
left_pos = (s_width - 200) / 2
|
||||
box_rows = InputBox((left_pos, 150, 140, 32), str(row))
|
||||
box_columns = InputBox((left_pos, 220, 140, 32), str(col))
|
||||
box_number_of_blocks = InputBox((left_pos, 290, 140, 32), str(num_of_blocks))
|
||||
box_layers = InputBox((left_pos, 360, 140, 32), str(no_of_hidden_layers))
|
||||
box_neurons = InputBox((left_pos, 430, 140, 32), str(no_of_hidden_nodes))
|
||||
box_rng_seed = InputBox((left_pos, 500, 140, 32), str(no_of_hidden_nodes))
|
||||
input_boxes = [box_rows, box_columns, box_number_of_blocks,
|
||||
box_layers, box_neurons, box_rng_seed]
|
||||
|
||||
# continue button
|
||||
continue_img = pygame.image.load(continue_btn_image).convert_alpha()
|
||||
continue_button = Button(
|
||||
continue_btn_position, continue_img, continue_btn_scale)
|
||||
continue_button.draw(window)
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
done = True
|
||||
for box in input_boxes:
|
||||
box.handle_event(event)
|
||||
|
||||
for box in input_boxes:
|
||||
box.update()
|
||||
|
||||
window.fill(background_color)
|
||||
for box in input_boxes:
|
||||
box.draw(window)
|
||||
|
||||
if continue_button.click():
|
||||
done = True
|
||||
|
||||
gui.draw_text_middle("SETTINGS", 30, text_color, window, (0, play_width))
|
||||
gui.draw_text_middle("rows", 20, text_color, window, (40, play_width))
|
||||
gui.draw_text_middle("columns", 20, text_color,
|
||||
window, (110, play_width))
|
||||
gui.draw_text_middle("number of blocks", 20,
|
||||
text_color, window, (180, play_width))
|
||||
gui.draw_text_middle("layers", 20, text_color, window, (250, play_width))
|
||||
gui.draw_text_middle("neurons", 20, text_color,
|
||||
window, (320, play_width))
|
||||
gui.draw_text_middle("rng seed", 20, text_color,
|
||||
window, (390, play_width))
|
||||
continue_button.draw(window)
|
||||
|
||||
pygame.display.update()
|
||||
|
||||
window.fill(background_color)
|
||||
pygame.display.update()
|
||||
|
||||
return [box.value() for box in input_boxes]
|
||||
@ -0,0 +1,21 @@
|
||||
"""
|
||||
Handles the saving and loading of objects
|
||||
"""
|
||||
import pickle
|
||||
|
||||
|
||||
def save_network(obj, filename):
|
||||
"""
|
||||
Store the network in a save file
|
||||
"""
|
||||
with open(filename, 'wb') as outp: # Overwrites any existing file.
|
||||
pickle.dump(obj, outp, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
|
||||
def load_network(filename):
|
||||
"""
|
||||
Retrieve the network from a save file
|
||||
"""
|
||||
with open(filename, 'rb') as inp: # Overwrites any existing file.
|
||||
network = pickle.load(inp)
|
||||
return network
|
||||
@ -0,0 +1,57 @@
|
||||
import pytest
|
||||
from ai import mutate_by, Nnetwork
|
||||
from math import isclose
|
||||
import numpy as np
|
||||
|
||||
# testing mutation
|
||||
@pytest.mark.parametrize(
|
||||
'value, mutation_rate, expected, tol',
|
||||
[
|
||||
(1, 0, 1000, 0),
|
||||
(1, 0.1, 1000, 10),
|
||||
(-10, 1, -10000, 100)
|
||||
])
|
||||
def test_normals(value, mutation_rate, expected, tol):
|
||||
sum = 0
|
||||
for _ in range(1000):
|
||||
sum += mutate_by(value, mutation_rate)
|
||||
assert isclose(sum, expected, abs_tol=tol)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, mutation_rate, expected, tol',
|
||||
[
|
||||
(1, 0, 999.999, 0),
|
||||
(1, 0.1, 1000, 0.01),
|
||||
(-10, 50, -10000, 1)
|
||||
])
|
||||
def test_normals2(value, mutation_rate, expected, tol):
|
||||
sum = 0
|
||||
for _ in range(1000):
|
||||
sum += mutate_by(value, mutation_rate)
|
||||
assert not isclose(sum, expected, abs_tol=tol)
|
||||
|
||||
|
||||
# test cloning (should make exact clone when zero mutations)
|
||||
def test_cloning():
|
||||
network = Nnetwork([1, 1, 1, 1], 0)
|
||||
clone = network.clone()
|
||||
assert network.hidden_layers == clone.hidden_layers \
|
||||
and network.weights_hidden_out == clone.weights_hidden_out \
|
||||
and network.weights_in_hidden == clone.weights_in_hidden
|
||||
|
||||
# should make a diferent clone
|
||||
def test_cloning2():
|
||||
network = Nnetwork([100, 100, 100, 100], 10)
|
||||
clone = network.clone()
|
||||
assert not (np.array(network.hidden_layers) == np.array(clone.hidden_layers)).all()
|
||||
|
||||
|
||||
# test inner logic
|
||||
def test_evaluate():
|
||||
network = Nnetwork([1, 1, 1, 1], 0)
|
||||
assert isclose(network.evaluate([1])[0], 0, rel_tol=1)
|
||||
|
||||
# should only output left, right, down or rotate
|
||||
def test_run():
|
||||
network = Nnetwork([4, 4, 100, 1], 1)
|
||||
assert network.run([1, 6, -2, 0.1]) in [0, 1, 2, 3]
|
||||
@ -0,0 +1,95 @@
|
||||
import pytest
|
||||
from tetris import Piece, game, get_holes, create_grid, valid_space, handle_input, get_highest_list
|
||||
import pygame
|
||||
from math import isclose
|
||||
|
||||
|
||||
# testing tetrominos
|
||||
@pytest.mark.parametrize(
|
||||
'x, y, shape, shapes, expected',
|
||||
[
|
||||
(0, 0, ["0"], [["0"]], [(-2,-4)]),
|
||||
(0, 0, [".0"], [[".0"]], [(-2,-3)]),
|
||||
(0, 0, [".0"], [[".0"]], [(-2,-3)]),
|
||||
(1, 2, [[".","0"]], [[[".","0"]]], [(-1,-1)]),
|
||||
(0, 0, [['....', '.0..', '.000', '....']],
|
||||
[[['....', '.0..', '.000', '....']],["0"]],
|
||||
[(-1, -3), (-1, -2), (0, -2), (1, -2)])
|
||||
])
|
||||
def test_cell_positions(x, y, shape, shapes, expected):
|
||||
piece = Piece(x, y, shape, shapes)
|
||||
assert piece.get_cell_positions() == expected
|
||||
|
||||
|
||||
|
||||
# testing grid
|
||||
@pytest.mark.parametrize(
|
||||
'locked_pos, row, col, expected',
|
||||
{
|
||||
((0, 0, 0), 1, 1, (0, 0, 0))
|
||||
})
|
||||
def test_create_grid(locked_pos, row, col, expected):
|
||||
assert create_grid(locked_pos, row, col) == [[expected]]
|
||||
|
||||
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_grid():
|
||||
return [[(0,0,0), (0,0,0)], [(0,0,0), (255,255,255)]]
|
||||
|
||||
@pytest.fixture
|
||||
def col():
|
||||
return 2
|
||||
|
||||
@pytest.fixture
|
||||
def row():
|
||||
return 2
|
||||
|
||||
# testing positions
|
||||
@pytest.fixture
|
||||
def test_piece():
|
||||
return Piece(0, 0, [[".0",".0"]], [[[".0",".0"]]])
|
||||
|
||||
def test_valid_space(test_piece, test_grid):
|
||||
assert valid_space(test_piece, test_grid, 2, 2) == True
|
||||
|
||||
def test_invalid_space(test_piece, test_grid):
|
||||
test_piece.x += 10
|
||||
test_piece.y += 10
|
||||
assert valid_space(test_piece, test_grid, 2, 2) == False
|
||||
|
||||
|
||||
# testing game input
|
||||
@pytest.mark.parametrize(
|
||||
'action_value, expected_x, expected_y',
|
||||
{
|
||||
(1, -1, -1),
|
||||
(2, 1, 1),
|
||||
(3, 0, 0),
|
||||
(4, 0, 0),
|
||||
})
|
||||
def test_handle_input(action_value, test_piece, test_grid, row, col, expected_x, expected_y):
|
||||
pygame.display.set_mode((100, 100))
|
||||
handle_input(action_value, test_piece, test_grid, row, col)
|
||||
assert test_piece.x == expected_x and test_piece.x == expected_y
|
||||
|
||||
|
||||
# testing eval functions
|
||||
def test_highest(col, test_grid):
|
||||
assert get_highest_list(col, test_grid) == [0, 1]
|
||||
|
||||
def test_holes(col, test_grid):
|
||||
highest = get_highest_list(col, test_grid)
|
||||
assert get_holes(col, highest, test_grid) == [0, -2]
|
||||
|
||||
|
||||
# testing whole game
|
||||
@pytest.fixture
|
||||
def input_function():
|
||||
return lambda _ : 2
|
||||
|
||||
def test_game(input_function):
|
||||
surface = pygame.display.set_mode((100, 100))
|
||||
score = game(surface, input_function, [9, 8, 2, 3, 5, 20], [5, 20, 20])
|
||||
assert isclose(score[0], 1.24, abs_tol=1e-9) and isclose(score[1], 0)
|
||||
@ -0,0 +1,331 @@
|
||||
"""
|
||||
Independent tetris module
|
||||
|
||||
tetriminos:
|
||||
0 - S - green
|
||||
1 - Z - red
|
||||
2 - I - cyan
|
||||
3 - O - yellow
|
||||
4 - J - blue
|
||||
5 - L - orange
|
||||
6 - T - purple
|
||||
"""
|
||||
|
||||
import sys
|
||||
import random
|
||||
from itertools import chain
|
||||
import numpy as np
|
||||
import pygame
|
||||
import gui
|
||||
from config import turn_count, max_block_count, shape_colors, s_width, s_height, shape_list
|
||||
|
||||
# global variables
|
||||
|
||||
# false positive
|
||||
# pylint: disable=no-member
|
||||
|
||||
|
||||
# class to represent each of the pieces
|
||||
class Piece():
|
||||
"""
|
||||
Tetromino object
|
||||
"""
|
||||
|
||||
def __init__(self, x, y, shape, shapes):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.shape = shape
|
||||
self.shapes = shapes
|
||||
# choose color from the shape_color list
|
||||
self.color = shape_colors[shapes.index(shape)]
|
||||
self.rotation = 0 # chooses the rotation according to index
|
||||
|
||||
def get_cell_positions(self):
|
||||
"""col
|
||||
Converts the object type and rotation into an array of cell positions
|
||||
"""
|
||||
positions = []
|
||||
# get the desired rotated shape from piece
|
||||
shape_format = self.shape[self.rotation % len(self.shape)]
|
||||
|
||||
# i gives index; line gives string
|
||||
for i, line in enumerate(shape_format):
|
||||
row_list = list(line) # makes a list of char from string
|
||||
# j gives index of char; column gives char
|
||||
for j, column in enumerate(row_list):
|
||||
if column == '0':
|
||||
positions.append((self.x + j, self.y + i))
|
||||
|
||||
for i, pos in enumerate(positions):
|
||||
# offset according to the input given with dot and zero
|
||||
positions[i] = (pos[0] - 2, pos[1] - 4)
|
||||
|
||||
return positions
|
||||
|
||||
@staticmethod
|
||||
def get_random_shape(shapes):
|
||||
"""
|
||||
Chooses a shape randomly from shapes list
|
||||
"""
|
||||
return Piece(5, 2, random.choice(shapes), shapes)
|
||||
|
||||
|
||||
def create_grid(locked_pos, row, col):
|
||||
"""
|
||||
Initialise the grid
|
||||
"""
|
||||
grid = [[(0, 0, 0) for x in range(col)]
|
||||
for y in range(row)] # grid represented rgb tuples
|
||||
|
||||
# locked_positions dictionary
|
||||
# (x,y):(r,g,b)
|
||||
for y in range(row):
|
||||
for x in range(col):
|
||||
if (x, y) in locked_pos:
|
||||
# get the value color (r,g,b) from the locked_positions dictionary using key (x,y)
|
||||
color = locked_pos[(x, y)]
|
||||
grid[y][x] = color # set grid position to color
|
||||
|
||||
return grid
|
||||
|
||||
|
||||
def valid_space(piece, grid, row, col):
|
||||
"""
|
||||
Checks if current position of piece in grid is valid
|
||||
"""
|
||||
# makes a 2D list of all the possible (x,y)
|
||||
accepted_pos = [[(x, y) for x in range(col) if grid[y]
|
||||
[x] == (0, 0, 0)] for y in range(row)]
|
||||
# removes sub lists and puts (x,y) in one list; easier to search
|
||||
accepted_pos = [x for item in accepted_pos for x in item]
|
||||
|
||||
formatted_shape = piece.get_cell_positions()
|
||||
|
||||
for pos in formatted_shape:
|
||||
if pos not in accepted_pos:
|
||||
if pos[1] >= 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def check_lost(positions):
|
||||
"""
|
||||
Check if the peice does not fit into the board
|
||||
"""
|
||||
for pos in positions:
|
||||
if pos[1] < 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def clear_rows(grid, locked):
|
||||
"""
|
||||
clear a row when it is filled
|
||||
"""
|
||||
|
||||
# need to check if row is clear then shift every other row above one down
|
||||
cleared = 0
|
||||
index = len(grid) - 1
|
||||
while index > 0:
|
||||
grid_row = grid[index]
|
||||
if (0, 0, 0) not in grid_row:
|
||||
for j in range(len(grid_row)):
|
||||
try:
|
||||
# delete every locked element in the row
|
||||
del locked[(j, index + cleared)]
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
cleared += 1 # increase score
|
||||
|
||||
# delete filled bottom row
|
||||
# add another empty row on the top
|
||||
# move down one step
|
||||
for key in sorted(list(locked), key=lambda a: a[1])[::-1]:
|
||||
x, y = key
|
||||
if y < index + cleared:
|
||||
new_key = (x, y + 1) # shift position to down
|
||||
locked[new_key] = locked.pop(key)
|
||||
|
||||
index -= 1 # decrease index
|
||||
|
||||
return cleared
|
||||
|
||||
|
||||
def user_input():
|
||||
"""
|
||||
Handles all in game user input events
|
||||
|
||||
0 - Nothing
|
||||
1 - Left
|
||||
2 - Right
|
||||
3 - Down
|
||||
4 - Rotate
|
||||
"""
|
||||
|
||||
result_action = 0
|
||||
for event in pygame.event.get():
|
||||
if event.key == pygame.K_LEFT:
|
||||
result_action = 1
|
||||
|
||||
elif event.key == pygame.K_RIGHT:
|
||||
result_action = 2
|
||||
|
||||
elif event.key == pygame.K_DOWN:
|
||||
result_action = 3
|
||||
|
||||
elif event.key == pygame.K_UP:
|
||||
result_action = 4
|
||||
|
||||
return result_action
|
||||
|
||||
|
||||
def handle_input(action_value, current_piece, grid, row, col):
|
||||
"""
|
||||
Handles inputed action value
|
||||
|
||||
0 - Nothing
|
||||
1 - Left
|
||||
2 - Right
|
||||
3 - Down
|
||||
4 - Rotate
|
||||
"""
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.display.quit()
|
||||
sys.exit(0)
|
||||
|
||||
if action_value == 1:
|
||||
current_piece.x -= 1 # move x position left
|
||||
if not valid_space(current_piece, grid, row, col):
|
||||
current_piece.x += 1
|
||||
|
||||
elif action_value == 2:
|
||||
current_piece.x += 1 # move x position right
|
||||
if not valid_space(current_piece, grid, row, col):
|
||||
current_piece.x -= 1
|
||||
|
||||
elif action_value == 3:
|
||||
current_piece.y += 1 # move y position down
|
||||
while valid_space(current_piece, grid, row, col):
|
||||
current_piece.y += 1
|
||||
current_piece.y -= 1
|
||||
|
||||
elif action_value == 4:
|
||||
# rotate shape
|
||||
current_piece.rotation = current_piece.rotation + \
|
||||
1 % len(current_piece.shape)
|
||||
if not valid_space(current_piece, grid, row, col):
|
||||
current_piece.rotation = current_piece.rotation - \
|
||||
1 % len(current_piece.shape)
|
||||
|
||||
def get_highest_list(col, grid):
|
||||
"""
|
||||
get the position of the highest cells
|
||||
"""
|
||||
highest_list = [0] * col
|
||||
for y, current_row in enumerate(grid[::-1]):
|
||||
for x, cell in enumerate(current_row):
|
||||
if cell > (0, 0, 0):
|
||||
highest_list[x] = y + 1
|
||||
|
||||
return highest_list
|
||||
|
||||
def get_holes(col, highest, grid):
|
||||
"""
|
||||
get the number of closed holes
|
||||
"""
|
||||
holes = []
|
||||
np_grid = np.array(grid)
|
||||
for x in range(col):
|
||||
holes.append(highest[x] - np.count_nonzero(np_grid[:,x]))
|
||||
return holes
|
||||
|
||||
def game(surface, input_function, settings, dimensions, graphics_mode=False):
|
||||
"""
|
||||
Main game function
|
||||
"""
|
||||
(row, col, number_of_blocks, _, _, rng_seed) = settings
|
||||
(block_size, play_width, play_height) = dimensions
|
||||
|
||||
shapes = shape_list[0:number_of_blocks]
|
||||
|
||||
locked_positions = {}
|
||||
change_piece = False
|
||||
random.seed(rng_seed)
|
||||
current_piece = Piece.get_random_shape(shapes)
|
||||
next_piece = Piece.get_random_shape(shapes)
|
||||
fall_count = 0
|
||||
score = 0
|
||||
height_score = 0
|
||||
block_count = 0
|
||||
last_holes = 0
|
||||
|
||||
while True:
|
||||
|
||||
grid = create_grid(locked_positions, row, col)
|
||||
fall_count += 1
|
||||
|
||||
# wait for given nuber of turns
|
||||
if fall_count > turn_count:
|
||||
fall_count = 0
|
||||
current_piece.y += 1
|
||||
height_score += current_piece.y * 0.01
|
||||
if not valid_space(current_piece, grid, row, col) and current_piece.y > 0:
|
||||
current_piece.y -= 1
|
||||
change_piece = True
|
||||
# give points for placing the block as low as possible
|
||||
|
||||
# gather game data and pass it to the input function
|
||||
# positions of all cells
|
||||
# [highest cell in each column]
|
||||
piece_pos = current_piece.get_cell_positions()
|
||||
|
||||
#game_data = [shapes.index(current_piece.shape),
|
||||
# current_piece.rotation,
|
||||
# current_piece.x,
|
||||
# current_piece.y]
|
||||
game_data = list(chain.from_iterable(piece_pos))
|
||||
highest_list = get_highest_list(col, grid)
|
||||
game_data += highest_list
|
||||
|
||||
# get input
|
||||
input_value = input_function(game_data)
|
||||
# handle input
|
||||
if not change_piece:
|
||||
handle_input(input_value, current_piece, grid, row, col)
|
||||
height_score += 0.005 if input_value == 4 else 0
|
||||
else:
|
||||
holes = sum(get_holes(col, highest_list, grid))
|
||||
height_score -= (holes - last_holes) * 0.01 if holes > last_holes else 0
|
||||
last_holes = holes
|
||||
|
||||
# draw the piece on the grid by giving color in the piece locations
|
||||
for x, y in piece_pos:
|
||||
if y >= 0:
|
||||
grid[y][x] = current_piece.color
|
||||
|
||||
if change_piece: # if the piece is locked
|
||||
for pos in piece_pos:
|
||||
p = (pos[0], pos[1])
|
||||
# add the key and value in the dictionary
|
||||
locked_positions[p] = current_piece.color
|
||||
current_piece = next_piece
|
||||
next_piece = Piece.get_random_shape(shapes)
|
||||
change_piece = False
|
||||
|
||||
# increment score, give exponentialy more points for multiple rows cleared
|
||||
score += (2 ** clear_rows(grid, locked_positions) - 1) * 2
|
||||
#block_count += 1
|
||||
|
||||
if graphics_mode:
|
||||
gui.draw_window(surface, grid, block_size, (play_width, play_height), score)
|
||||
gui.draw_next_shape(next_piece, surface, block_size, play_width)
|
||||
pygame.display.update(0, 0, s_width / 2, s_height)
|
||||
|
||||
# break to prevent infinite games
|
||||
if check_lost(locked_positions) or block_count > max_block_count:
|
||||
break
|
||||
|
||||
return (height_score, score)
|
||||
Loading…
Reference in new issue