Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I was working on my AI pathfinding(which you don't need to understand),
for some reason, my On[3] list was expanding when I did this in the shell:
tempList.append([On[1]-1])
(After the program messed up.) WHY?
The program didn't crash, but that isn't my question.
A screenshot(Ignore the extra prints, I was trying to narrow down the code that was causing it.)
On[1] was my Y coordinates.
The code in question is at # Find Path
(Under the bottom section.)
My code(Over 200 lines long. :/)
# Setup Python ----------------------------------------------- #
import pygame, sys, random, time, webbrowser, os
from datetime import datetime
# Version ---------------------------------------------------- #
Version = '1.0'
# Setup pygame/window ---------------------------------------- #
x = 100
y = 100
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (x,y)
mainClock = pygame.time.Clock()
from pygame.locals import *
pygame.init()
pygame.display.set_caption('Pathfinding '+(Version)+'')
WINDOWWIDTH = 200
WINDOWHEIGHT = 200
screen = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),pygame.NOFRAME)
# Font ------------------------------------------------------- #
basicFont = pygame.font.SysFont(None, 20)
# Images ----------------------------------------------------- #
# Audio ------------------------------------------------------ #
# Colors ----------------------------------------------------- #
WHITE = (255,255,255)
BLACK = (0,0,0)
GRAY3 = (105,105,105)
GRAY = (195,195,195)
GRAY2 = (127,127,127)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
GOLD = (255,215,0)
PURPLE = (115,0,242)
# Variables -------------------------------------------------- #
Map = ['0000000000',
'0300000000',
'0000000000',
'0200000000',
'0000000000',
'0000000000',
'0000000000',
'0000000000',
'0000000000',
'0000000000']
Column = 0
Row = 0
Nodes = {}
for whatevs in Map:
for whatevs2 in Map[Row]:
Nodes[''+(str(Column))+','+(str(Row))+''] = [Column,Row,int(whatevs2)]
if whatevs2 == '3':
On = [Column,Row,[[Column,Row]]]
Column += 1
if Column == 10:
Column = 0
Row += 1
Open = {}
Closed = {}
# Rects ------------------------------------------------------ #
# Defenitions ------------------------------------------------ #
def Distance(Location,End):
if Location != []:
if int(Location[0]) < End[0]:
Dist = End[0] - int(Location[0])
else:
Dist = int(Location[0]) - End[0]
if int(Location[1]) < End[1]:
Dist2 = End[1] - int(Location[1])
else:
Dist2 = int(Location[1]) - End[1]
Dist += Dist2
if Location[2] == 1:
return 100000
elif Location[2] == 2:
return 0
else:
return Dist
else:
return 100000
# FPS -------------------------------------------------------- #
FPS = 80
TrueFPSCount = 0
TrueFPS = 0
fpsOn = False
PrevNow = 0
# Text -------------------------------------------------------- #
def drawText(text, font, color, surface, x, y):
textobj = font.render(text, 1, color)
textrect = textobj.get_rect()
textrect.topleft = (x, y)
screen.blit(textobj, textrect)
# Loop ------------------------------------------------------- #
while True:
# Black Screen ------------------------------------------- #
screen.fill(BLACK)
# Show Nodes --------------------------------------------- #
for Node in Nodes:
Rect = pygame.Rect(Nodes[Node][0]*20,Nodes[Node][1]*20,20,20)
if Nodes[Node][2] == 0:
pygame.draw.rect(screen,WHITE,Rect)
elif Nodes[Node][2] == 1:
pygame.draw.rect(screen,BLUE,Rect)
elif Nodes[Node][2] == 2:
pygame.draw.rect(screen,GREEN,Rect)
End = [Nodes[Node][0],Nodes[Node][1]]
else:
pygame.draw.rect(screen,RED,Rect)
for Node in Closed:
Rect = pygame.Rect(Closed[Node][0]*20,Closed[Node][1]*20,20,20)
pygame.draw.rect(screen,(0,100,200),Rect)
Rect2 = pygame.Rect(On[0]*20,On[1]*20,20,20)
pygame.draw.rect(screen,(0,200,100),Rect2)
if [On[0],On[1]] == End:
print('Completed.')
print(On[2])
input()
time.sleep(3)
pygame.quit()
sys.exit()
# Find Path ---------------------------------------------- #
Top = []
Bottom = []
Right = []
Left = []
Closed[''+(str(On[0]))+','+(str(On[1]))+''] = [On[0],On[1]]
try:
Top.append(Nodes[''+(str(On[0]))+','+(str(On[1]-1))+''][0])
Top.append(Nodes[''+(str(On[0]))+','+(str(On[1]-1))+''][1])
Top.append(Nodes[''+(str(On[0]))+','+(str(On[1]-1))+''][2])
tempList = []
tempList = On[2]
print(On)
tempList.append([On[0],On[1]-1])
print(On)
Top.append(tempList)
for item in Closed:
if Top != []:
if item == ''+(str(Top[0]))+','+(str(Top[1]))+'':
Top = []
except NameError:
pass
except KeyError:
pass
try:
Bottom.append(Nodes[''+(str(On[0]))+','+(str(On[1]+1))+''][0])
Bottom.append(Nodes[''+(str(On[0]))+','+(str(On[1]+1))+''][1])
Bottom.append(Nodes[''+(str(On[0]))+','+(str(On[1]+1))+''][2])
tempList = []
tempList = On[2]
print('?')
print(On)
tempList.append([On[0],On[1]+1])
print(On)
print()
Bottom.append(tempList)
print('On')
print(On)
for item in Closed:
if Bottom != []:
if item == ''+(str(Bottom[0]))+','+(str(Bottom[1]))+'':
Bottom = []
except NameError:
pass
except KeyError:
pass
try:
Right.append(Nodes[''+(str(On[0]+1))+','+(str(On[1]))+''][0])
Right.append(Nodes[''+(str(On[0]+1))+','+(str(On[1]))+''][1])
Right.append(Nodes[''+(str(On[0]+1))+','+(str(On[1]))+''][2])
tempList = []
tempList = On[2]
tempList.append([On[0]+1,On[1]])
Right.append(tempList)
for item in Closed:
if Right != []:
if item == ''+(str(Right[0]))+','+(str(Right[1]))+'':
Right = []
except NameError:
pass
except KeyError:
pass
try:
Left.append(Nodes[''+(str(On[0]-1))+','+(str(On[1]))+''][0])
Left.append(Nodes[''+(str(On[0]-1))+','+(str(On[1]))+''][1])
Left.append(Nodes[''+(str(On[0]-1))+','+(str(On[1]))+''][2])
tempList = []
tempList = On[2]
tempList.append([On[0]-1,On[1]])
Left.append(tempList)
for item in Closed:
if Left != []:
if item == ''+(str(Left[0]))+','+(str(Left[1]))+'':
Left = []
except NameError:
pass
except KeyError:
pass
if Top != []:
Open[''+(str(Top[0]))+','+(str(Top[1]))+''] = [Distance(Top,End),Top[0],Top[1],Top[3]]
if Bottom != []:
print(':D')
print(On)
Open[''+(str(Bottom[0]))+','+(str(Bottom[1]))+''] = [Distance(Bottom,End),Bottom[0],Bottom[1],Bottom[3]]
if Right != []:
Open[''+(str(Right[0]))+','+(str(Right[1]))+''] = [Distance(Right,End),Right[0],Right[1],Right[3]]
if Left != []:
Open[''+(str(Left[0]))+','+(str(Left[1]))+''] = [Distance(Left,End),Left[0],Left[1],Left[3]]
Lowest = [0,0]
LowestNum = 100000
try:
del Open[''+(str(On[0]))+','+(str(On[1]))+'']
except KeyError:
pass
for Place in Open:
if Open[Place][0] < LowestNum:
LowestNum = Open[Place][0]
Lowest = [Open[Place][1],Open[Place][2],Open[Place][3]]
On = Lowest
# FPS ---------------------------------------------------- #
NewSec = False
TrueFPSCount += 1
now = datetime.now()
now = now.second
if PrevNow != now:
PrevNow = now
NewSec = True
TrueFPS = TrueFPSCount
TrueFPSCount = 0
TrueFPS = str(TrueFPS)
# Buttons ------------------------------------------------ #
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
if event.key == ord('x'):
if fpsOn == True:
fpsOn = False
elif fpsOn == False:
fpsOn = True
# Update ------------------------------------------------- #
if fpsOn == True:
drawText('FPS:'+(TrueFPS)+'', basicFont, WHITE, screen, 500,12)
pygame.display.update()
mainClock.tick(10)
I don't use classes or sprites. :P
Thanks for taking a look!
Because your On[-1] and tempList are the same object. And this is how it works with same objects:
>>> x=[1,2,3]
>>> y=x
>>> y.append(10)
>>> x
[1, 2, 3, 10]
>>> y
[1, 2, 3, 10]
>>>
Related
for me its not possible to train an AI with Neat-Python in my own game enviroment :/
I downloaded a FlappyBird example from internet with Neat-Python implementation, there it works.
I created a GIT Repo with a File, that starts Training the AI my Game:
https://bitbucket.org/salvaracer/testpy/src/master/
*The Problem*
The AI doesnt get besser fitness scores. I do get different fitness scores, better and worse, but after many generation it did not learn that is should move to the targets! so the fitness/AOI is not realy evolving.
I tried a lot thinks:
Inputs like (playerX, playerY, targetX, targetY, distanceToTarget) at the beginning
Now I have inputs: normalized(distToTopBorder, distToLeftBorder, distToRightBorder, distToBottomBorder, distanceToTarget, playerDirection).
I used 4 outputs at beginning, movement with [WASD] (up/left/right/down)
now I use 2 outputs ("turn left/right), movement "Turn Left" or "Turn Right" while the Player is always moving.
Now the game runs 1x while evaluating all genomes's fitness in this one game
At beginning ich started 1 game for 1 player.
Population always min 50 and generations min 200, up to 2000 generations, I never got good results. Sometimes, some player randomly perfor ell, but FITNESS does not increase with time. I also tried different fitness calculations. Always no evolving AI :/
Code entry file is:
import random
from Square import Square
import neat
import os
from game_singleplayer import Game
import global_vars
GENERATION = 0
MAX_FITNESS = 0
BEST_GENOME = 0
def eval_genomes(genomes, config):
global GENERATION, MAX_FITNESS, BEST_GENOME
GENERATION += 1
i = 0
for genome_id, genome in genomes:
i+=1
game = Game()
GENOMES_FITNESSES = game.train_ai([genome], config, GENERATION, len(genomes), MAX_FITNESS, genome_id)
if genome.fitness is None:
genome.fitness = float('-inf') #fixes errors on early termination
for genfit in GENOMES_FITNESSES:
if genfit[0] == genome_id:
genome.fitness = genfit[1]
# print("Gen : {} Genome # : {} Fitness : {} Max Fitness : {}".format(GENERATION,i,genome.fitness, MAX_FITNESS))
if (genome.fitness):
if genome.fitness >= MAX_FITNESS:
MAX_FITNESS = genome.fitness
BEST_GENOME = genome
def eval_genomes_all(genomes, config):
print(len(genomes))
global GENERATION, MAX_FITNESS, BEST_GENOME
GENERATION += 1
game = Game()
GENOMES_FITNESSES = game.train_ai_all(genomes, config, GENERATION, len(genomes), MAX_FITNESS)
for genfit in GENOMES_FITNESSES:
for genome_id, genome in genomes:
if genfit[0] == genome_id:
genome.fitness = genfit[1]
# print("Gen : {} Genome # : {} Fitness : {} Max Fitness : {}".format(GENERATION,i,genome.fitness, MAX_FITNESS))
if (genome.fitness):
if genome.fitness >= MAX_FITNESS:
MAX_FITNESS = genome.fitness
BEST_GENOME = genome
def run_neat(config):
p = neat.Checkpointer.restore_checkpoint('neat-checkpoint-2718')
# p = neat.Population(config)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
p.add_reporter(neat.Checkpointer(100))
# winner = p.run(eval_genomes, 100) # Generations
winner = p.run(eval_genomes_all, 2000) # Generations
if __name__ == '__main__':
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, 'config.txt')
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
neat.DefaultSpeciesSet, neat.DefaultStagnation,
config_path)
run_neat(config)
# game = Game()
# game.test_ai(config)
# game.play()
In Game-Class i have:
def train_ai(self, genomes, config, gen, populationInt, maxfit, genome_id):
self.squares, self.targets = [], []
self.squares.append(Square(agent = 2, playerIndex = genome_id, targetsNum = 1))
# self.targets.append(Target())
GENOMES_FITNESSES = self.play_game([genomes[0]], config, 0, gen, populationInt, maxfit)
return GENOMES_FITNESSES[0]
def train_ai_all(self, genomes, config, gen, populationInt, maxfit):
self.squares, self.targets = [], []
for genome_id, genome in genomes:
self.squares.append(Square(agent = 2, playerIndex = genome_id, targetsNum = 3))
GENOMES_FITNESSES = self.play_game(genomes, config, 0, gen, populationInt, maxfit)
return GENOMES_FITNESSES
def play_game(self, genomes = False, config = False, FPS = 30, GENERATION = 0, populationInt = 0, MAX_FITNESS = 0, GENOME_INDEX = 0, genome_id = 0):
GENOMES_FITNESSES = []
pygame.init()
screen = pygame.display.set_mode((global_vars.SCREEN_WIDTH, global_vars.SCREEN_HEIGHT))
if FPS > 0:
clock = pygame.time.Clock()
# Hauptspielschleife
running = True
self.tickIndex = 0
while running:
self.tickIndex += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# init
screen.fill((0, 0, 0))
all_sprites = pygame.sprite.Group()
overIn = global_vars.PLAY_MAX_TICKS - self.tickIndex
for target in self.targets:
target.drawObject(screen, all_sprites)
for square in self.squares:
square.tickIndex += 1
if overIn > global_vars.PLAYER_MAX_TICKS - self.tickIndex:
overIn = global_vars.PLAYER_MAX_TICKS - self.tickIndex
for genome_id, genome in genomes:
if genome_id == square.playerIndex:
if square.agent == 2:
net = neat.nn.FeedForwardNetwork.create(genome, config)
# output = net.activate((square.rect.x, square.rect.y, square.Cx, square.Cy, square.nearestChildDistance/10, square.direction))
x_array = np.array([square.distTop, square.distLeft, square.distBottom, square.distRight, square.nearestChildDistance, square.direction])
normalized_arr = preprocessing.normalize([x_array])
# print(normalized_arr)
output = net.activate(normalized_arr[0])
# probabilities = global_vars.softmax(output)
# decision = np.argmax(probabilities)
action1 = 1 if output[0] >= 0.5 else 0
action2 = 1 if output[1] >= 0.5 else 0
actions = [action1, action2]
else:
# manual player and computer no need actions here
actions = []
# move and draw
square.move(self, actions)
square.handle_collision(self.targets)
fitness = square.score*100 + (global_vars.SCREEN_WIDTH - square.nearestChildDistance)/10 + (self.tickIndex/100.0)
lastFitness = [genome_id, fitness]
square.drawObject(screen, all_sprites, fitness)
all_sprites.draw(screen)
sx = square.rect.x + square.size/2
sy = square.rect.y + square.size/2
sP = (sx, sy)
s0 = (sx, 0)
s1 = (0, sy)
s2 = (sx, global_vars.SCREEN_HEIGHT)
s3 = (global_vars.SCREEN_WIDTH, sy)
for targetC in square.targets:
pygame.draw.line(screen, (180, 180, 180), sP, (square.Cx + targetC.size/2, square.Cy + targetC.size/2))
if square.rect.y > 0: pass
else:
self.squares.remove(square)
GENOMES_FITNESSES.append(lastFitness)
continue
if square.rect.y < global_vars.SCREEN_HEIGHT - square.rect.height: pass
else:
self.squares.remove(square)
GENOMES_FITNESSES.append(lastFitness)
continue
if square.rect.x > 0: pass
else:
self.squares.remove(square)
GENOMES_FITNESSES.append(lastFitness)
continue
if square.rect.x < global_vars.SCREEN_WIDTH - square.rect.width: pass
else:
self.squares.remove(square)
GENOMES_FITNESSES.append(lastFitness)
continue
if square.tickIndex >= global_vars.PLAYER_MAX_TICKS:
try:
self.squares.remove(square)
except ValueError:
pass
GENOMES_FITNESSES.append(lastFitness)
continue
# Additional Text
font = pygame.font.Font(None, 24)
infotxt = font.render("Game left: {}".format(int(global_vars.PLAY_MAX_TICKS - self.tickIndex)/10), True, (255, 255, 255))
if FPS == 0:
infotxt = font.render("GEN: {} P: {}/{} Max Fit: {} Over in: {}".format(GENERATION, len(self.squares), populationInt, int(MAX_FITNESS), int(overIn)), True, (255, 255, 255))
text_rect = infotxt.get_rect()
text_rect.topleft = (10, 10)
screen.blit(infotxt, text_rect)
pygame.display.flip()
if self.tickIndex > global_vars.PLAY_MAX_TICKS or len(self.squares) == 0:
GENOMES_FITNESSES.append(lastFitness)
return GENOMES_FITNESSES
if FPS > 0:
clock.tick(FPS)
# end of play_game()
and mostly default CONFIG
[NEAT]
fitness_criterion = max
fitness_threshold = 2000
pop_size = 25
reset_on_extinction = False
[DefaultGenome]
# node activation options
# activation_default = random
activation_default = sigmoid
activation_mutate_rate = 0.0
activation_options = abs clamped cube exp gauss hat identity inv log relu sigmoid sin softplus square tanh
# node aggregation options
aggregation_default = sum
aggregation_mutate_rate = 0.1
aggregation_options = sum
# node bias options
bias_init_mean = 0.0
bias_init_stdev = 1.0
bias_max_value = 30.0
bias_min_value = -30.0
bias_mutate_power = 0.5
bias_mutate_rate = 0.7
bias_replace_rate = 0.1
The point is, none of my tries and configurations did work very well, i want to know what I'm doing wrong :)
I tried a math formula from analytic geometry but I didn't get the desired results.
As you can see in the code, the user draw two lines and a green circle(point of intersection) appear.
I have tried to fix the problem for hours but I failed. Sometimes the point doesn't appear or appear but in the wrong position.
Source of the formula I used in the code
Docs of pygame the library I used
#!/usr/bin/env python
from pygame import *
from time import sleep
init();
win = display.set_mode((500,500));
lines = []
cords = []
preview = False
xi = -100
yi = -100
def drawlines(flines):
for fcords in flines:
draw.line(win,(0,0,0),fcords[0],fcords[1],4)
while True:
win.fill((255,255,255))
for ev in event.get():
if ev.type == QUIT:
quit();
exit();
elif ev.type == MOUSEBUTTONDOWN:
if ev.button == 1:
preview = True
a = mouse.get_pos()
cords.append(mouse.get_pos())
elif ev.type == MOUSEBUTTONUP:
if ev.button == 1:
cords.append(mouse.get_pos())
lines.append((cords))
cords = []
preview = False
######################################## THIS BROKEN PART #################################
if len(lines) == 2:
#line 1
points_line1 = lines[0]
point1_line1 = points_line1[0]
point2_line1 = points_line1[1]
line1_vector = (point2_line1[0]-point1_line1[0],point2_line1[1]-point1_line1[1])
a_line1 = line1_vector[1]
b_line1 = -line1_vector[0]
c_line1 = -((a_line1*point1_line1[0]) + (b_line1*point1_line1[1]))
#line2
points_line2 = lines[1]
point1_line2 = points_line2[0]
point2_line2 = points_line2[1]
line2_vector = (point2_line2[0]-point1_line2[0],point2_line2[1]-point1_line2[1])
a_line2 = line2_vector[1]
b_line2 = -line2_vector[0]
c_line2 = -((a_line2*point1_line2[0]) + (b_line2*point1_line2[1]))
if (a_line2 != 0 and b_line2 != 0):
if (a_line1 / a_line2) != ( b_line1/b_line2):
#intersection between line1 and line2
yi = ((((a_line1*c_line2) / a_line2) + c_line1) / -b_line1)
xi = (((-b_line1*yi)-c_line1) / a_line1)
###########################################################################################
elif preview:
draw.line(win,(0,0,0),a,mouse.get_pos(),4)
drawlines(lines)
draw.circle(win,(0,200,0),(int(xi),int(yi)),10)
display.flip()
It's hard to dig through your code. Especially c_line1 = -((a_line1*point1_line1[0]) + (b_line1*point1_line1[1])) seems to be odd, because it calculates the Dot product of vector and a point.
Therefore I don't know exactly what's wrong with your code, but I know how to intersect lines. See Problem with calculating line intersections and Collision and Intersection - Line and line.
Write functions that can add, subtract, scale vectors and rotate vectors by 90°. Write a function which calculates the Dot product:
def add(a, b):
return a[0]+b[0], a[1]+b[1]
def sub(a, b):
return a[0]-b[0], a[1]-b[1]
def rot90(v):
return -v[1], v[0]
def mul(v, s):
return v[0]*s, v[1]*s
def dot(a, b):
return a[0]*b[0] + a[1]*b[1]
Calculate the vector from the beginning of the lines to the end of the lines and the normal vectors to the lines. The normal vector is obtained by rotating the line vector by 90°:
#line 1
point1_line1, point2_line1 = lines[0]
line1_vector = sub(point2_line1, point1_line1)
line1_norml = rot90(line1_vector)
#line2
point1_line2, point2_line2 = lines[1]
line2_vector = sub(point2_line2, point1_line2)
line2_norml = rot90(line2_vector)
Calculate the intersection of the line segments. Make sure the lines are not parallel and that the intersection point is on the line segments:
# vector from start point of line 2 to start point of line 1
l2p1_l1p1 = sub(point1_line1, point1_line2)
# intersection
d = dot(line2_vector, line1_norml)
if d != 0: # prallel lines
t = dot(l2p1_l1p1, line1_norml) / d
u = dot(l2p1_l1p1, line2_norml) / d
if 0 <= t <= 1 and 0 <= u <= 1: # intersection on line segments
xi, yi = add(point1_line2, mul(line2_vector, t))
Minimal example:
from pygame import *
init()
win = display.set_mode((500,500))
lines = []
cords = []
preview = False
xi, yi = -100, -100
def drawlines(flines):
for fcords in flines:
draw.line(win,(0,0,0),fcords[0],fcords[1],4)
def add(a, b):
return a[0]+b[0], a[1]+b[1]
def sub(a, b):
return a[0]-b[0], a[1]-b[1]
def rot90(v):
return -v[1], v[0]
def mul(v, s):
return v[0]*s, v[1]*s
def dot(a, b):
return a[0]*b[0] + a[1]*b[1]
run = True
while run:
for ev in event.get():
if ev.type == QUIT:
run = False
elif ev.type == MOUSEBUTTONDOWN:
if ev.button == 1:
preview = True
a = ev.pos
cords.append(a)
elif ev.type == MOUSEBUTTONUP:
if ev.button == 1:
cords.append(ev.pos)
lines.append((cords))
cords = []
preview = False
intersections = []
for i, line1 in enumerate(lines):
for line2 in lines[i+1:]:
#line 1
point1_line1, point2_line1 = line1
line1_vector = sub(point2_line1, point1_line1)
line1_norml = rot90(line1_vector)
#line2
point1_line2, point2_line2 = line2
line2_vector = sub(point2_line2, point1_line2)
line2_norml = rot90(line2_vector)
# vector from start point of line 2 to start point of line 1
l2p1_l1p1 = sub(point1_line1, point1_line2)
# intersection
d = dot(line2_vector, line1_norml)
if d != 0: # prallel lines
t = dot(l2p1_l1p1, line1_norml) / d
u = dot(l2p1_l1p1, line2_norml) / d
if 0 <= t <= 1 and 0 <= u <= 1: # intersection on line segments
xi, yi = add(point1_line2, mul(line2_vector, t))
intersections.append((xi, yi))
win.fill((255,255,255))
if len(cords) % 2 == 1:
draw.line(win,(0,0,0), a, mouse.get_pos(), 4)
drawlines(lines)
for p in intersections:
draw.circle(win, (0,200,0), (round(p[0]), round(p[1])), 10)
display.flip()
quit()
exit()
I have been learning Pygame for 3 days. Now I want to save and load the high score and display it.
This has been a very difficult task to achieve in other engines I've used - saving and loading, that is - but with sheer determination, I have managed to do it on one of my main engines.
Any help will do.
The code:
import pygame, sys, random
def draw_floor():
screen.blit(floor_surface,(floor_x_pos,900))
screen.blit(floor_surface,(floor_x_pos + 576,900))
def create_pipe():
random_pipe_pos = random.choice(pipe_height)
bottom_pipe = pipe_surface.get_rect(midtop = (584,random_pipe_pos))
top_pipe = pipe_surface.get_rect(midbottom = (584,random_pipe_pos -300))
return bottom_pipe,top_pipe
def move_pipes(pipes):
for pipe in pipes:
pipe.centerx -= 5
visible_pipes = [pipe for pipe in pipes if pipe.right > -50]
return visible_pipes
def draw_pipes(pipes):
for pipe in pipes:
if pipe.bottom >= 1024:
screen.blit(pipe_surface,pipe)
else:
flip_pipe = pygame.transform.flip(pipe_surface,False,True)
screen.blit(flip_pipe,pipe)
def check_collision(pipes):
global can_score
for pipe in pipes:
if bird_rect.colliderect(pipe):
hit_sound.play()
can_score = True
return False
if bird_rect.top <= -100 or bird_rect.bottom >= 900:
can_score = True
hit_sound.play()
return False
return True
def rotate_bird(bird):
new_bird = pygame.transform.rotozoom(bird,-bird_movement * 3,1)
return new_bird
def bird_animation():
new_bird = bird_frames[bird_index]
new_bird_rect = new_bird.get_rect(center = (100,bird_rect.centery))
return new_bird,new_bird_rect
def score_display(game_state):
if game_state == 'main_game':
score_surface = game_font.render(str(int(score)),True,(255,255,255))
score_rect = score_surface.get_rect(center = (288,100))
screen.blit(score_surface,score_rect)
if game_state == 'game_over':
score_surface = game_font.render(f'Score: {int(score)}',True,(255,255,255))
score_rect = score_surface.get_rect(center = (288,100))
screen.blit(score_surface,score_rect)
high_score_surface = game_font.render(f'High score: {int(high_score)}',True,(255,255,255))
high_score_rect = high_score_surface.get_rect(center = (288,850))
screen.blit(high_score_surface,high_score_rect)
def update_score(score,high_score):
if score > high_score:
high_score = score
return high_score
def pipe_score_check():
global score, can_score
if pipe_list:
for pipe in pipe_list:
if 95 < pipe.centerx < 105 and can_score:
score += 1
point_sound.play()
can_score = False
if pipe.centerx < 0:
can_score = True
# pygame.mixer.pre_init(frequency = 44100, size = 16, channels = 1, buffer = 512)
pygame.init()
screen = pygame.display.set_mode((576,1024))
clock = pygame.time.Clock()
game_font = pygame.font.Font('04B_19.ttf',40)
# Variables
gravity = 0.25
bird_movement = 0
game_active = True
score = 0
high_score = 0
can_score = True
# Loading images
bg_surface = pygame.image.load('assets/background-day.png').convert()
bg_surface = pygame.transform.scale2x(bg_surface)
floor_surface = pygame.image.load('assets/base.png').convert()
floor_surface = pygame.transform.scale2x(floor_surface)
floor_x_pos = 0
bird_downflap = pygame.transform.scale2x(pygame.image.load('assets/yellowbird-downflap.png').convert_alpha())
bird_midflap = pygame.transform.scale2x(pygame.image.load('assets/yellowbird-midflap.png').convert_alpha())
bird_upflap = pygame.transform.scale2x(pygame.image.load('assets/yellowbird-upflap.png').convert_alpha())
bird_frames = [bird_downflap,bird_midflap,bird_upflap]
bird_index = 0
bird_surface = bird_frames[bird_index]
bird_rect = bird_surface.get_rect(center = (100,512))
BIRDFLAP = pygame.USEREVENT + 1
pygame.time.set_timer(BIRDFLAP,200)
# bird_surface = pygame.image.load('assets/yellowbird-midflap.png').convert_alpha()
# bird_surface = pygame.transform.scale2x(bird_surface)
# bird_rect = bird_surface.get_rect(center = (100,512))
pipe_surface = pygame.image.load('assets/pipe-green.png').convert()
pipe_surface = pygame.transform.scale2x(pipe_surface)
pipe_list = []
SPAWNPIPE = pygame.USEREVENT
pygame.time.set_timer(SPAWNPIPE,1500)
pipe_height = [400,600,800]
game_over_surface = pygame.transform.scale2x(pygame.image.load('assets/message.png').convert_alpha())
game_over_rect = game_over_surface.get_rect(center = (288,512))
flap_sound = pygame.mixer.Sound('sound/sfx_wing.wav')
hit_sound = pygame.mixer.Sound('sound/sfx_hit.wav')
point_sound = pygame.mixer.Sound('sound/sfx_point.wav')
countdown_sound_score = 100
# Event loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and game_active:
bird_movement = 0
bird_movement -= 12
flap_sound.play()
if event.key == pygame.K_SPACE and game_active == False:
game_active = True
pipe_list.clear()
bird_rect.center = (100,512)
bird_movement = 0
score = 0
if event.type == SPAWNPIPE:
pipe_list.extend(create_pipe())
if event.type == BIRDFLAP:
if bird_index < 2:
bird_index += 1
else:
bird_index = 0
bird_surface,bird_rect = bird_animation()
# Pasting background image
screen.blit(bg_surface,(0,0))
if game_active:
# Bird movement and pasting bird
bird_movement += gravity
rotated_bird = rotate_bird(bird_surface)
bird_rect.centery += bird_movement
screen.blit(rotated_bird,bird_rect)
game_active = check_collision(pipe_list)
# Pipes
pipe_list = move_pipes(pipe_list)
draw_pipes(pipe_list)
# Score
pipe_score_check()
score_display('main_game')
else:
screen.blit(game_over_surface,game_over_rect)
high_score = update_score(score,high_score)
score_display('game_over')
# Floor movement and pasting floor image
floor_x_pos -= 1
draw_floor()
if floor_x_pos <=-576:
floor_x_pos = 0
pygame.display.update()
clock.tick(120)
This will help me greatly.
Thanks,
Josh
The easiest way would be to have a file with your score. You will write the score to it before closing the game, or whenever you want to save the score. and read the score when you open the game.
Here is an example of the read and write functions to achive this:
def write_score(score):
with open("highscore.txt", 'w') as file:
file.write(str(score))
def read_score():
with open("highscore.txt", 'r') as file:
return int(file.read())
(Haven't tested the code but I'm pretty sure that this will work, if not, I can fix it)
I just currently finished making the game 'snake' as a practice to learn how to program, as I am new to programming for about 3 months.
Although the game is completed and runs the way I intended, I want to try to simplify my code and reduce the amount of lines as much as possible, and possibly make the script tidier as the current majority of my codes are cluster in the while loop.
Until now I haven't touched upon class objects, and I want everything in the while loop to go into individual classes that get called out from the while loop to reduce the amount of lines in it.
off-topic: by reading through the script, how else can I improve it to be run more efficiently, including simplifying some code as I may have over-complicated it?
I looked up how class object is used from w3school and other programming tutorials, but I still don't fully understand it as it only shows examples in using print. I did play around and experimented with class object examples and attempted to call them without using print, but I lack the knowledge of how to use them properly.
from graphics import *
from threading import Timer
import keyboard, random, time
# configurations
width = 400
gridHeight = width
height = 470
timer = False
game = True
score = 0
bonus = 0
x = 70
y = 30
radius = 10
length = radius * 2
playerLength = 3
poisonLength = playerLength
i = 0
k = 0
pointRadius = 5
points = False
cherryPoints = False
key = "Right"
countDown = 0
# set coordinations
cX = 90
cY = 30
coordX = [10]
coordY = [10]
while coordX[len(coordX)-1] != width-10:
cX+=20
coordX.append(cX)
while coordY[len(coordY)-1] != 390:
cY+=20
coordY.append(cY)
randomX = random.choice(coordX)
randomY = random.choice(coordY)
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
# window set up
win = GraphWin("SNAKE", width, height, autoflush = False)
win.setBackground(color_rgb(15,15,15))
# grid
lineX = 20
while lineX < width:
gridX = Line(Point(lineX,0),Point(lineX,gridHeight))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineX += 20
lineY = 20
while lineY <= gridHeight:
gridX = Line(Point(0,lineY),Point(width,lineY))
gridX.setOutline(color_rgb(25,25,25))
gridX.draw(win)
lineY += 20
# snake banner
UI = Rectangle(Point(0,400),Point(width,height))
UI.setFill(color_rgb(102,51,0))
UI.setOutline(color_rgb(102,51,0))
UI.draw(win)
snakeTitle = Text(Point(width/2,420),"SNAKE")
snakeTitle.setTextColor("green")
snakeTitle.setSize(20)
snakeTitle.draw(win)
scoreTitle = Text(Point(320,424),"SCORE")
scoreTitle.setTextColor("white")
scoreTitle.setSize(10)
scoreTitle.draw(win)
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# make player
player = {}
player[0] = Rectangle(Point(x-20-radius,y-radius), Point(x-20+radius, y+radius))
player[1] = Rectangle(Point(x-40-radius,y-radius), Point(x-40+radius, y+radius))
player[2] = Rectangle(Point(x-60-radius,y-radius), Point(x-60+radius, y+radius))
# make poison
poison = {}
def main():
global timer, scoreUI, score, bonus, playerLength, poisonLength, x, y, points, cherryPoints, randomX, randomY, cherryRandomX, cherryRandomY, poisonRandomX, poisonRandomY, key, countDown, k, game
while(game==True):
# score update
scoreUI.undraw()
scoreUI = Text(Point(320,435),score)
scoreUI.setTextColor("white")
scoreUI.setSize(10)
scoreUI.draw(win)
# generating new body blocks
if len(player) < playerLength:
i+=1
player[i] = player[i-1].clone()
# body following player
player[0].undraw()
for i in range(1,len(player)):
player[len(player)-i].undraw()
player[len(player)-i] = player[len(player)-i-1].clone()
player[len(player)-i].draw(win)
# update player's head coordinate
player[0] = Rectangle(Point(x-radius,y-radius), Point(x+radius,y+radius))
player[0].setFill("green")
player[0].setWidth(2)
player[0].draw(win)
# player movement
if keyboard.is_pressed("Up") and key != "Down":
key = "Up"
elif keyboard.is_pressed("Left") and key != "Right":
key = "Left"
elif keyboard.is_pressed("Down") and key != "Up":
key = "Down"
elif keyboard.is_pressed("Right") and key != "Left":
key = "Right"
if key == "Up":
y -= length
elif key == "Left":
x -= length
elif key == "Down":
y += length
elif key == "Right":
x += length
# point
if points == False: # generates new point when eaten
point = Rectangle(Point(randomX-pointRadius,randomY-pointRadius),Point(randomX+pointRadius,randomY+pointRadius))
point.setFill("white")
point.setWidth(2)
point.draw(win)
points = True
if player[0].getCenter().getX() == point.getCenter().getX() and player[0].getCenter().getY() == point.getCenter().getY(): # when player eats the point
point.undraw()
playerLength += 1
poisonLength += 1
score += 200+bonus
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(player)):
if (point.getCenter().getX() == player[i].getCenter().getX() and point.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoints == True and cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and cherry
randomX = random.choice(coordX)
randomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if point shares the same coordinate to other array of poisons
if point.getCenter().getX() == poison[i].getCenter().getX() and point.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
points = False
# cherry
if countDown == 150:
countDown = 0
if cherryPoints == False: # generates new cherry from countdown
cherryPoint = Rectangle(Point(cherryRandomX-pointRadius,cherryRandomY-pointRadius),Point(cherryRandomX+pointRadius,cherryRandomY+pointRadius))
cherryPoint.setFill(color_rgb(213,0,50))
cherryPoint.setWidth(2)
cherryPoint.draw(win)
cherryPoints = True
if cherryPoints == True:
for i in range(2, 6): # cherry blinks between countdown 40 to 100
if countDown == 20*i:
cherryPoint.undraw()
elif countDown == 10+(20*i):
cherryPoint.draw(win)
if countDown >= 100: # when countdown becomes 100, remove cherry and reset count down
cherryPoints = False
countDown = 0
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
if cherryPoints==True and player[0].getCenter().getX() == cherryPoint.getCenter().getX() and player[0].getCenter().getY() == cherryPoint.getCenter().getY(): # when player eats the cherry
cherryPoint.undraw()
score += 500
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(player)):
if (cherryPoint.getCenter().getX() == player[i].getCenter().getX() and cherryPoint.getCenter().getY() == player[i].getCenter().getY()) or (cherryPoint.getCenter().getX() == point.getCenter().getX() and cherryPoint.getCenter().getY() == point.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
for i in range(len(poison)): # regenerate x and y coordinate if cherry shares the same coordinate to other array of poisons
if cherryPoint.getCenter().getX() == poison[i].getCenter().getX() and cherryPoint.getCenter().getY() == poison[i].getCenter().getY():
cherryRandomX = random.choice(coordX)
cherryRandomY = random.choice(coordY)
cherryPoints = False
# poison
if poisonLength % 5 == 0: # generates a poison block each time the player size reaches the multiple of 5
poison[k] = Rectangle(Point(poisonRandomX-pointRadius,poisonRandomY-pointRadius),Point(poisonRandomX+pointRadius,poisonRandomY+pointRadius))
poison[k].setFill("green")
poison[k].setWidth(2)
poison[k].draw(win)
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(player)):
if (poison[k].getCenter().getX() == player[i].getCenter().getX() and poison[k].getCenter().getY() == player[i].getCenter().getY()) or (poison[k].getCenter().getX() == point.getCenter().getX() and poison[k].getCenter().getY() == point.getCenter().getY()) or (cherryPoints==True and poison[k].getCenter().getX() == cherryPoint.getCenter().getX() and poison[k].getCenter().getY() == cherryPoint.getCenter().getY()): # regenerate x and y coordinate if they share the same coordinate as player and point and cherry
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
for i in range(len(poison)):
if poison[k].getCenter().getX() == poison[i].getCenter().getX() and poison[k].getCenter().getY() == poison[i].getCenter().getY(): # regenerate x and y coordinate if new poison shares the same coordinate to other array of poisons
poisonRandomX = random.choice(coordX)
poisonRandomY = random.choice(coordY)
bonus+=50
k+=1
poisonLength+=1
# game over requirements
for i in range(len(poison)): # if player touches poison
if player[0].getCenter().getX() == poison[i].getCenter().getX() and player[0].getCenter().getY() == poison[i].getCenter().getY():
game = False
for i in range(2, len(player)): # if player touches its own body or reach out of window
if (player[0].getCenter().getX() == player[i].getCenter().getX() and player[0].getCenter().getY() == player[i].getCenter().getY()) or x < 0 or x > width or y < 0 or y > gridHeight:
game = False
# FPS
update(10)
countDown += 1
# GAME OVER
gameOver = Text(Point(width/2,200), "GAME OVER")
gameOver.setTextColor("red")
gameOver.setSize(30)
gameOver.draw(win)
update()
time.sleep(2)
win.close()
main()
Ideally the result should replace each code in the while loop with individual classes outside of the function to reduce the amount of lines in the main() function and make the script easier to read.
Classes are essentially just bundles of code that contain various attributes and methods.
A Snake class might have a list of coordinates for each section of the body (the first is the head).
class Snake:
def __init__(self, x, y):
self.positions = [(x, y)]
def get_head(self):
return self.positions[0]
def move_forward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] + 1)
def move_backward(self):
self.positions.pop()
self.positions.insert(0, self.get_head()[1] - 1)
...
And so on. Classes, at this level, let you think of objects as concrete entities, distinct from each other but easily manipulated.
I'm looking to get some help with a match-3 game I'm working on in pygame. I've loaded the images and sounds as follows and the 'gems' will be the classical elements air, earth, fire and water. How can I play the appropriate water sound file, for example, when 3 or more water sprites are matched? Don't need help with any of the game code just to create an association between the image and audio files and how to play it 'while matchedElements != []'. Thank you.
# Directions
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
# Space to the sides of grid
XMARGIN = int((WIDTH - ELEMENTSIZE * GRIDWIDTH) / 2)
YMARGIN = int((HEIGHT - ELEMENTSIZE * GRIDHEIGHT) / 2)
EMPTY_SPACE = -1
ROWABOVEBOARD = 'row above board'
# Colours
AIR = pygame.Color(145, 129, 129)
FIRE = pygame.Color(255, 123, 0)
WATER = pygame.Color(93, 118, 245)
EARTH = pygame.Color(22, 136, 0)
ELECTRIC = pygame.Color(22, 204, 0)
SMOKE = pygame.Color(222, 222, 222)
ICE = pygame.Color(234, 231, 255)
METAL = pygame.Color(105, 105, 105)
BLOOD = pygame.Color(222, 7, 7)
# FPS controller
fpsController = pygame.time.Clock()
def main():
global FPSCLOCK, BOARDRECTS, ELEMENTIMAGES, SOUNDS, PLAYSURF, BASICFONT
# Basic set up
pygame.init()
FPSCLOCK = pygame.time.Clock()
PLAYSURF = pygame.display.set_mode((WIDTH, HEIGHT))
BASICFONT = pygame.font.Font('freesansbold.ttf', 36)
# Load images
ELEMENTIMAGES = []
for i in range(1, NUMELEMENTS+1):
elementImage = pygame.image.load('element%s.jpg' % i)
if elementImage.get_size() != (ELEMENTSIZE, ELEMENTSIZE):
elementImage = pygame.transform.smoothscale(elementImage, (ELEMENTSIZE, ELEMENTSIZE))
ELEMENTIMAGES.append(elementImage)
# Load sounds
SOUNDS = {}
SOUNDS['bad swap'] = pygame.mixer.Sound('badswap.wav')
SOUNDS['match'] = []
for i in range(NUMMATCHSOUNDS):
SOUNDS['match'].append(pygame.mixer.Sound('elementsound%s.wav' % i))
# Rect objects for board space conversions
BOARDRECTS = []
for x in range(GRIDWIDTH):
BOARDRECTS.append([])
for y in range(GRIDHEIGHT):
r = pygame.Rect((XMARGIN + (x * ELEMENTSIZE),
YMARGIN + (y * ELEMENTSIZE),
ELEMENTSIZE, ELEMENTSIZE))
BOARDRECTS[x].append(r)
while True:
runGame()
def runGame():
# Board initialisation
gameBoard = getBlankBoard()
score = 0
fillBoardAndAnimate(gameBoard, [], score) # Drop initial elements
# Initialise new game variables
firstSelectedElement = None
lastMouseDownX = None
lastMouseDownY = None
gameIsOver = False
lastScoreDeduction = time.time()
clickContinueTextSurf = None
# Main game loop
while True:
clickedSpace = None
for event in pygame.event.get(): # Event handling
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == KEYUP and event.key == K_BACKSPACE:
return # new game
elif event.type == MOUSEBUTTONUP:
if gameIsOver:
return # click to start new game
if event.pos == (lastMouseDownX, lastMouseDownY):
clickedSpace = checkForElementClick(event.pos)
else:
firstSelectedElement = checkForElementClick((lastMouseDownX, lastMouseDownY))
clickedSpace = checkForElementClick(event.pos)
if not firstSelectedElement or not clickedSpace:
firstSelectedElement = None
clickedSpace = None
elif event.type == MOUSEBUTTONDOWN:
lastMouseDownX, lastMouseDownY = event.pos
if clickedSpace and not firstSelectedElement:
firstSelectedElement = clickedSpace
elif clickedSpace and firstSelectedElement:
firstSwappingElement, secondSwappingElement = getSwappingElements(gameBoard, firstSelectedElement, clickedSpace)
if firstSwappingElement == None and secondSwappingElement == None:
# If both are None, elements are not adjacent
firstSelectedElement = None
continue
# Swap animation
boardCopy = getBoardCopyMinusElements(gameBoard, (firstSwappingElement, secondSwappingElement))
animateMovingElements(boardCopy, [firstSwappingElement, secondSwappingElement], [], score)
# Swap elements in the board
gameBoard[firstSwappingElement['x']][firstSwappingElement['y']] = secondSwappingElement['imageNum']
gameBoard[secondSwappingElement['x']][secondSwappingElement['y']] = firstSwappingElement['imageNum']
# See if this is a match
matchedElements = findMatchingElements(gameBoard)
if matchedElements == []:
# No match - swap back
SOUNDS['bad swap'].play()
animateMovingElements(boardCopy, [firstSwappingElement, secondSwappingElement], [], score)
gameBoard[firstSwappingElement['x']][firstSwappingElement['y']] = firstSwappingElement['imageNum']
gameBoard[secondSwappingElement['x']][secondSwappingElement['y']] = secondSwappingElement['imageNum']
else:
# A match
scoreAdd = 0
while matchedElements != []:
points = []
for elementSet in matchedElements:
scoreAdd += (10 + (len(elementSet) - 3) * 10)
for element in elementSet:
gameBoard[element[0]][element[1]] = EMPTY_SPACE
points.append({'points': scoreAdd,
'x': element[0] * ELEMENTSIZE + XMARGIN,
'y': element[1] * ELEMENTSIZE + YMARGIN})
score += scoreAdd
# Drop new elements
fillBoardAndAnimate(gameBoard, points, score)
# Check for new matches
matchedElements = findMatchingElements(gameBoard)
firstSelectedElement = None
if not canMakeMove(gameBoard):
gameIsOver = True
# Draw the board
PLAYSURF.fill(BGCOLOUR)
drawBoard(gameBoard)
if firstSelectedElement != None:
highlightSpace(firstSelectedElement['x'], firstSelectedElement['y'])
if gameIsOver:
if clickContinueTextSurf == None:
clickContinueTextSurf = BASICFONT.render('Final Score: %s (Click to continue)' % (score), 1, GAMEOVERCOLOUR, GAMEOVERBGCOLOUR)
clickContinueTextRect = clickContinueTextSurf.get_rect()
clickContinueTextRect.center = int(WIDTH / 2), int(HEIGHT / 2)
PLAYSURF.blit(clickContinueTextSurf, clickContinueTextRect)
elif score > 0 and time.time() - lastScoreDeduction > DEDUCTSPEED:
# score drops over time
score -= 1
lastScoreDeduction = time.time()
drawScore(score)
pygame.display.update()
FPSCLOCK.tick(FPS)
def getSwappingElements(board, firstXY, secondXY):
# If the elements at the (X, Y) coordinates of the two elements are adjacent,
# then their 'direction' keys are set to the appropriate direction
# value to be swapped with each other.
# Otherwise, (None, None) is returned.
firstElement = {'imageNum': board[firstXY['x']][firstXY['y']],
'x': firstXY['x'],
'y': firstXY['y']}
secondElement = {'imageNum': board[secondXY['x']][secondXY['y']],
'x': secondXY['x'],
'y': secondXY['y']}
highlightedElement = None
if firstElement['x'] == secondElement['x'] + 1 and firstElement['y'] == secondElement['y']:
firstElement['direction'] = LEFT
secondElement['direction'] = RIGHT
elif firstElement['x'] == secondElement['x'] - 1 and firstElement['y'] == secondElement['y']:
firstElement['direction'] = RIGHT
secondElement['direction'] = LEFT
elif firstElement['y'] == secondElement['y'] + 1 and firstElement['x'] == secondElement['x']:
firstElement['direction'] = UP
secondElement['direction'] = DOWN
elif firstElement['y'] == secondElement['y'] - 1 and firstElement['x'] == secondElement['x']:
firstElement['direction'] = DOWN
secondElement['direction'] = UP
else:
# These elements are not adjacent and can't be swapped.
return None, None
return firstElement, secondElement
def getBlankBoard():
# Create and return a blank board data structure.
board = []
for x in range(GRIDWIDTH):
board.append([EMPTY_SPACE] * GRIDHEIGHT)
return board
def canMakeMove(board):
# Return True if the board is in a state where a matching
# move can be made on it. Otherwise return False.
# The patterns in oneOffPatterns represent elements that are configured
# in a way where it only takes one move to make a triplet.
oneOffPatterns = (((0,1), (1,0), (2,0)),
((0,1), (1,1), (2,0)),
((0,0), (1,1), (2,0)),
((0,1), (1,0), (2,1)),
((0,0), (1,0), (2,1)),
((0,0), (1,1), (2,1)),
((0,0), (0,2), (0,3)),
((0,0), (0,1), (0,3)))
# The x and y variables iterate over each space on the board.
# If we use + to represent the currently iterated space on the
# board, then this pattern: ((0,1), (1,0), (2,0))refers to identical
# elements being set up like this:
#
# +A
# B
# C
#
# That is, element A is offset from the + by (0,1), element B is offset
# by (1,0), and element C is offset by (2,0). In this case, element A can
# be swapped to the left to form a vertical three-in-a-row triplet.
#
# There are eight possible ways for the elements to be one move
# away from forming a triple, hence oneOffPattern has 8 patterns.
for x in range(GRIDWIDTH):
for y in range(GRIDHEIGHT):
for pat in oneOffPatterns:
# check each possible pattern of "match in next move" to
# see if a possible move can be made.
if (getElementAt(board, x+pat[0][0], y+pat[0][1]) == \
getElementAt(board, x+pat[1][0], y+pat[1][1]) == \
getElementAt(board, x+pat[2][0], y+pat[2][1]) != None) or \
(getElementAt(board, x+pat[0][1], y+pat[0][0]) == \
getElementAt(board, x+pat[1][1], y+pat[1][0]) == \
getElementAt(board, x+pat[2][1], y+pat[2][0]) != None):
return True # return True the first time you find a pattern
return False
def drawMovingElement(element, progress):
# Draw an element sliding in the direction that its 'direction' key
# indicates. The progress parameter is a number from 0 (just
# starting) to 100 (slide complete).
movex = 0
movey = 0
progress *= 0.01
if element['direction'] == UP:
movey = -int(progress * ELEMENTSIZE)
elif element['direction'] == DOWN:
movey = int(progress * ELEMENTSIZE)
elif element['direction'] == RIGHT:
movex = int(progress * ELEMENTSIZE)
elif element['direction'] == LEFT:
movex = -int(progress * ELEMENTSIZE)
basex = element['x']
basey = element['y']
if basey == ROWABOVEBOARD:
basey = -1
pixelx = XMARGIN + (basex * ELEMENTSIZE)
pixely = YMARGIN + (basey * ELEMENTSIZE)
r = pygame.Rect( (pixelx + movex, pixely + movey, ELEMENTSIZE, ELEMENTSIZE) )
PLAYSURF.blit(ELEMENTIMAGES[element['imageNum']], r)
def pullDownAllElements(board):
# pulls down elements on the board to the bottom to fill in any gaps
for x in range(GRIDWIDTH):
elementsInColumn = []
for y in range(GRIDHEIGHT):
if board[x][y] != EMPTY_SPACE:
elementsInColumn.append(board[x][y])
board[x] = ([EMPTY_SPACE] * (GRIDHEIGHT - len(elementsInColumn))) + elementsInColumn
def getElementAt(board, x, y):
if x < 0 or y < 0 or x >= GRIDWIDTH or y >= GRIDHEIGHT:
return None
else:
return board[x][y]
def getDropSlots(board):
# Creates a "drop slot" for each column and fills the slot with a
# number of elements that that column is lacking. This function assumes
# that the elements have been gravity dropped already.
boardCopy = copy.deepcopy(board)
pullDownAllElements(boardCopy)
dropSlots = []
for i in range(GRIDWIDTH):
dropSlots.append([])
# count the number of empty spaces in each column on the board
for x in range(GRIDWIDTH):
for y in range(GRIDHEIGHT-1, -1, -1): # start from bottom, going up
if boardCopy[x][y] == EMPTY_SPACE:
possibleElements = list(range(len(ELEMENTIMAGES)))
for offsetX, offsetY in ((0, -1), (1, 0), (0, 1), (-1, 0)):
# Narrow down the possible elements we should put in the
# blank space so we don't end up putting an two of
# the same elements next to each other when they drop.
neighborElement = getElementAt(boardCopy, x + offsetX, y + offsetY)
if neighborElement != None and neighborElement in possibleElements:
possibleElements.remove(neighborElement)
newElement = random.choice(possibleElements)
boardCopy[x][y] = newElement
dropSlots[x].append(newElement)
return dropSlots
This is just a partial answer to demonstrate what I suggested in the comments. I'm not sure how your gameBoard actually looks like, so you have to adjust the code as needed.
I use a board which is just filled with strings in this example (you could also use constants WATER = 1, FIRE = 2, etc. or an enum). The sounds are in a dictionary with the elements as the keys. If you have a match, figure out which element it is, then use it to get the associated sound out of the dict and play it.
ELEMENT_SOUNDS = {
'water': WATER_SOUND,
'fire': FIRE_SOUND,
}
board = [
['water', 'fire', 'water'],
['fire', 'water', 'fire'],
['water', 'water', 'water'],
]
if match:
# Figure out the kind of the matching element 'water' in this case.
element_kind = 'water'
ELEMENT_SOUNDS[element_kind].play()