How would I fade from one colour into another in pygame? I want to slowly change the colour of a circle from green to blue to purple to pink to red to orange to yellow to green. How would I do that? At the moment, I'm using
def colour():
switcher = {
0: 0x2FD596,
1: 0x2FC3D5,
2: 0x2F6BD5,
3: 0x432FD5,
4: 0x702FD5,
5: 0xBC2FD5,
6: 0xD52F91,
7: 0xD52F43,
8: 0xD57F2F,
9: 0xD5D52F,
10: 0x64D52F,
11: 0x2FD557,
}
return switcher.get(round((datetime.datetime.now() - starting_time).total_seconds()%11))
but that has really big steps in between the colours and looks clunky.
The key is to simply calculate how much you have to change each channel (a,r,g and b) each step. Pygame's Color class is quite handy, since it allows iteration over each channel and it's flexible in it's input, so you could just change e.g. 'blue' to 0x2FD596 in the below example and it will still run.
Here's the simple, running example:
import pygame
import itertools
pygame.init()
screen = pygame.display.set_mode((800, 600))
colors = itertools.cycle(['green', 'blue', 'purple', 'pink', 'red', 'orange'])
clock = pygame.time.Clock()
base_color = next(colors)
next_color = next(colors)
current_color = base_color
FPS = 60
change_every_x_seconds = 3.
number_of_steps = change_every_x_seconds * FPS
step = 1
font = pygame.font.SysFont('Arial', 50)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
text = font.render('fading {a} to {b}'.format(a=base_color, b=next_color), True, pygame.color.Color('black'))
step += 1
if step < number_of_steps:
# (y-x)/number_of_steps calculates the amount of change per step required to
# fade one channel of the old color to the new color
# We multiply it with the current step counter
current_color = [x + (((y-x)/number_of_steps)*step) for x, y in zip(pygame.color.Color(base_color), pygame.color.Color(next_color))]
else:
step = 1
base_color = next_color
next_color = next(colors)
screen.fill(pygame.color.Color('white'))
pygame.draw.circle(screen, current_color, screen.get_rect().center, 100)
screen.blit(text, (230, 100))
pygame.display.update()
clock.tick(FPS)
If you don't want to be dependent on the framerate but rather use a time based approach, you could change the code to:
...
change_every_x_milliseconds = 3000.
step = 0
running = True
while running:
...
if step < change_every_x_milliseconds:
current_color = [x + (((y-x)/change_every_x_milliseconds)*step) for x, y in zip(pygame.color.Color(base_color), pygame.color.Color(next_color))]
else:
...
...
pygame.display.update()
step += clock.tick(60)
You could go between all the values from one colour to the next by converting it into an int, increasing the number, and converting it back to hex. Then you just loop until you reach the next value with something like so:
value1 = 0xff00ff
value2 = 0xffffff
increment = 1 # amount to decrease or increase the hex value by
while value1 != value2:
if value1 > value2:
if int(value1)-increment < int(value2): # failsafe if the increment is greater than 1 and it skips being the value
value1 = value2
else:
value1 = hex(int(value1)-increment)
else:
if int(value1)+increment > int(value2):
value1 = value2
else:
value1 = hex(int(value1)+increment)
code_to_change_colour(value1)
See the edit by Prune for a much more elegant implementation of this. Note that code_to_change_colour(value1) should be changed to however you change the colour in your program. The increment will let you change how many colours are skipped. Obviously this code would need to be edited into a manner it can be used easily: e.g a function like def fade(value1, value2).
Edit from #Prune -- because code doesn't work well in comments.
Note that most of what you've written is "merely" loop control. You have known start and stop values and a fixed increment. This suggests a for loop rather than a while. Consider this:
value1 = int(0xff00ff)
value2 = int(0xffffff)
increment = 1 if value1 < value2 else -1
for current in range(value1, value2, increment):
code_to_change_colour(hex(value1))
value1 = value2
If you'd prefer just calculating the colors (without using any surfaces), you can do this:
First, you need to determine how long you want the dissolve to take. You also need to store the original and final colors. Last, calculate the blend. I would create a class for this:
import pygame
import time
class color_blend:
def __init__(self, start_color, end_color, duration=1000):
self.start_color = pygame.Color(start_color.r, start_color.g, start_color.b)
self.current_color = pygame.Color(start_color.r, start_color.g, start_color.b)
self.end_color = end_color
self.duration = float(duration)
self.start_time = color_blend.millis()
# Return current time in ms
#staticmethod
def millis():
return (int)(round(time.time() * 1000))
# Blend any 2 colors
# 0 <= amount <= 1 (0 is all initial_color, 1 is all final_color)
#staticmethod
def blend_colors(initial_color, final_color, amount):
# Calc how much to add or subtract from start color
r_diff = (final_color.r - initial_color.r) * amount
g_diff = (final_color.g - initial_color.g) * amount
b_diff = (final_color.b - initial_color.b) * amount
# Create and return new color
return pygame.Color((int)(round(initial_color.r + r_diff)),
(int)(round(initial_color.g + g_diff)),
(int)(round(initial_color.b + b_diff)))
def get_next_color(self):
# Elapsed time in ms
elapsed_ms = color_blend.millis() - self.start_time
# Calculate percentage done (0 <= pcnt_done <= 1)
pcnt_done = min(1.0, elapsed_ms / self.duration)
# Store new color
self.current_color = color_blend.blend_colors(self.start_color, self.end_color, pcnt_done)
return self.current_color
def is_finished(self):
return self.current_color == self.end_color
# Blend red to green in .3 seconds
c = color_blend(pygame.Color(255, 0, 0), pygame.Color(0, 255, 0), 300)
while not c.is_finished():
print(c.get_next_color())
You can easily modify this to do non-linear blending. For example, in blend_colors: amount = math.sin(amount * math.pi)
(I'm no Pygame expert - there may already be a built-in function for this.)
Set your foreground surface to the old color, over a background of the new one. Use set_alpha() to perform the fade. Once you're entirely on the new color, make that surface the foreground and make a new background of your third color. Repeat as desired.
This question and other references to "fade" and set_alpha() should allow you to finish the job.
Is that enough to get you moving?
I hesitated to post an answer because I came up with almost the same answer as Sloth's, but I'd just like to mention linear interpolation (short lerp, also called mix in OpenGL/GLSL). It's usually used to blend between two colors, but unfortunately pygame's Color class doesn't have a lerp method, so you have to define your own lerp function and use a list comprehension to interpolate the RGBA values.
Here's the lerp function from Wikipedia ported to Python (t is the weight and has to be between 0 and 1):
def lerp(v0, v1, t):
return (1 - t) * v0 + t * v1
Now you can lerp the RGBA values of two colors with a list comprehension.
color = [lerp(v0, v1, t) for v0, v1 in zip(color1, color2)]
E.g.:
>>> [lerp(v0, v1, .5) for v0, v1 in zip((0, 0, 0), (255, 255, 255))]
[127.5, 127.5, 127.5]
>>> [lerp(v0, v1, .25) for v0, v1 in zip((0, 0, 0), (255, 255, 255))]
[63.75, 63.75, 63.75]
If you don't need the alpha channel, you can also use pygame's Vector3 class which has a lerp method for your colors, then you'd just have to write: color = color1.lerp(color2, t).
import itertools
import pygame as pg
from pygame.math import Vector3
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
color_cycle = itertools.cycle([
Vector3(255, 0, 0),
Vector3(0, 255, 0),
Vector3(255, 255, 0),
Vector3(0, 0, 255),
])
color1 = next(color_cycle)
color2 = next(color_cycle)
color = color1
start_time = pg.time.get_ticks()
time_interval = 2000 # milliseconds
t = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
now = pg.time.get_ticks()
t = (now - start_time) / time_interval
if t > 1:
t = 0
start_time = now
color1, color2 = color2, next(color_cycle)
color = color1.lerp(color2, t) # Lerp the two colors.
screen.fill(color)
pg.display.flip()
clock.tick(60)
I had a similar problem not to long ago, I went the crazy route of learning a bit of Calculus to do it
R = X, G = Y, B = Z
then wrote a program to calculate the difference between R0 and R1 (X0 and X1) etc.
#Nick Poling
#Fade Test V2
import random
#import mpmath
#import sympy
import time
import pygame
import shelve
import sys
from pygame.locals import *
#Always Initialize
pygame.init()
#sympy contains function line_3d?
#top level?
#formated string?
#Change Name Displayed in Corner
MY_FONT = 'freesansbold.ttf'
FONTSIZE_1 = 20
pygame.display.set_caption('Fade Test V2')
GAME_NAME = pygame.font.Font(MY_FONT, FONTSIZE_1)
#Boards
GAME_SCREEN_WIDTH = 900
GAME_SCREEN_HEIGHT = 600
Main_Game_Loop = 1
def get_close_out():
#Save_Game = get_Save_Game()
pygame.quit()
sys.exit()
def get_Save_Game():
#Save
global Main_Game_Loop
global GAME_SCREEN_COLOR
global GAME_SCREEN_WIDTH
global GAME_SCREEN_HEIGHT
saveGameShelfFile = shelve.open('Fade Test V1')
saveGameShelfFile['GAME_SCREEN_WIDTH'] = GAME_SCREEN_WIDTH
saveGameShelfFile['GAME_SCREEN_HEIGHT'] = GAME_SCREEN_HEIGHT
def get_Load_Game():
global Main_Game_Loop
global GAME_SCREEN_COLOR
global GAME_SCREEN_WIDTH
global GAME_SCREEN_HEIGHT
try:
#Load
saveGameShelfFile = shelve.open('Fade Test V1')
GAME_SCREEN_WIDTH = saveGameShelfFile['GAME_SCREEN_WIDTH']
GAME_SCREEN_HEIGHT = saveGameShelfFile['GAME_SCREEN_HEIGHT']
except:
#Save
#Save_Game = get_Save_Game()
#Load
saveGameShelfFile = shelve.open('Fade Test V1')
GAME_SCREEN_WIDTH = saveGameShelfFile['GAME_SCREEN_WIDTH']
GAME_SCREEN_HEIGHT = saveGameShelfFile['GAME_SCREEN_HEIGHT']
GAME_SCREEN = pygame.display.set_mode((GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT),pygame.RESIZABLE)
#By putting the GAME_SCREEN here you can make the resize doable with the press of a button
#Does need to be un "#" to work when press "L"
def get_Colors():
#Colors
global BLACK
global WHITE
BLACK = [0, 0, 0]
WHITE = [255,255,255]
def get_Main_Game():
global Main_Game_Loop
global GAME_SCREEN_COLOR
global GAME_SCREEN_WIDTH
global GAME_SCREEN_HEIGHT
global BLACK
global WHITE
global Point_1
global Point_2
global Vector_1
global Vector_2
Colors = get_Colors()
GAME_SCREEN_COLOR = BLACK
#Load_Game = get_Load_Game()
GAME_SCREEN = pygame.display.set_mode((GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT),pygame.RESIZABLE)
while Main_Game_Loop == 1:
GAME_SCREEN.fill(GAME_SCREEN_COLOR)
Equation_Of_Lines_in_3D_Space = get_Equation_Of_Lines_in_3D_Space()
for t in range(0,255):
XYZ_1 = [Point_1[0] + (Vector_1[0] * t), Point_1[1] + (Vector_1[1] * t), Point_1[2] + (Vector_1[2] * t)]
XYZ_2 = [Point_2[0] + (Vector_2[0] * t), Point_2[1] + (Vector_2[1] * t), Point_2[2] + (Vector_2[2] * t)]
GAME_SCREEN_COLOR = XYZ_1
GAME_SCREEN.fill(GAME_SCREEN_COLOR)
ticks = pygame.time.delay(5)
pygame.display.update()
for event in pygame.event.get():
if event.type == QUIT:
close_out = get_close_out()
elif event.type == pygame.VIDEORESIZE:
# There's some code to add back window content here.
surface = pygame.display.set_mode((event.w, event.h),pygame.RESIZABLE)
GAME_SCREEN_HEIGHT = event.h
GAME_SCREEN_WIDTH = event.w
pygame.display.update()
def get_Equation_Of_Lines_in_3D_Space():
global Point_1
global Point_2
global BLACK
global WHITE
global Vector_1
global Vector_2
global LCM_X1
global LCM_X2
global LCM_Y1
global LCM_Y2
global LCM_Z1
global LCM_Z2
Point_1 = BLACK
Point_2 = WHITE
Vector_1 = []
Vector_2 = []
LCM_X1 = []
LCM_X2 = []
LCM_Y1 = []
LCM_Y2 = []
LCM_Z1 = []
LCM_Z2 = []
for i in range(0,3):
#
Delta_XYZ_1 = Point_2[i] - Point_1[i]
Vector_1.append(Delta_XYZ_1)
Delta_XYZ_2 = Point_1[i] - Point_2[i]
Vector_2.append(Delta_XYZ_2)
factors = get_factors()
def get_factors():
global num_1
global num_2
global Vector_1
global Vector_2
global LCM_XYZ_1
global LCM_XYZ_2
for i in range(1,7):
if i == 1:
num_1 = Vector_1[0]
num_2 = 1
elif i == 2:
num_1 = Vector_2[0]
num_2 = 2
elif i == 3:
num_1 = Vector_1[1]
num_2 = 3
elif i == 4:
num_1 = Vector_2[1]
num_2 = 4
elif i == 5:
num_1 = Vector_1[2]
num_2 = 5
elif i == 6:
num_1 = Vector_2[2]
num_2 = 6
get_largest_and_lowest_common_factors(num_1)
get_LCM_XYZ()
Vector_1[0] = Vector_1[0] / LCM_XYZ_1[0]
Vector_1[1] = Vector_1[1] / LCM_XYZ_1[0]
Vector_1[2] = Vector_1[2] / LCM_XYZ_1[0]
#
Vector_2[0] = Vector_2[0] / LCM_XYZ_2[0]
Vector_2[1] = Vector_2[1] / LCM_XYZ_2[0]
Vector_2[2] = Vector_2[2] / LCM_XYZ_2[0]
def get_largest_and_lowest_common_factors(x):
global num_1
global num_2
global Vector_1
global Vector_2
global LCM_X1
global LCM_X2
global LCM_Y1
global LCM_Y2
global LCM_Z1
global LCM_Z2
#This function takes a number and puts its factor into a list
for i in range(1, x + 1) or range(-x, x - 1, -1):
try:
if x % i == 0:
if num_1 == Vector_1[0] and num_2 == 1:
LCM_X1.append(i)
elif num_1 == Vector_1[1] and num_2 == 3:
LCM_Y1.append(i)
elif num_1 == Vector_1[2] and num_2 == 5:
LCM_Z1.append(i)
elif num_1 == Vector_2[0] and num_2 == 2:
LCM_X2.append(i)
elif num_1 == Vector_2[1] and num_2 == 4:
LCM_Y2.append(i)
elif num_1 == Vector_2[2] and num_2 == 6:
LCM_Z2.append(i)
except ZeroDivisionError:
return 0
def get_LCM_XYZ():
global LCM_X1
global LCM_Y1
global LCM_Z1
global LCM_X2
global LCM_Y2
global LCM_Z2
global LCM_XYZ_1
global LCM_XYZ_2
#If 1 is 0
check_1 = 0
check_2 = 0
check_3 = 0
for i in range(0,3):
if i == 0:
if LCM_X1 == [] and LCM_X2 == []:
check_1 = 1
elif i == 1:
if LCM_Y1 == [] and LCM_Y2 == []:
check_2 = 2
elif i == 2:
if LCM_Z1 == [] and LCM_Z2 == []:
check_3 = 3
F_check = check_1 + check_2 + check_3
if F_check == 1:
LCM_X1.extend(LCM_Y1)
LCM_X2.extend(LCM_Y2)
elif F_check == 2:
LCM_Y1.extend(LCM_X1)
LCM_Y2.extend(LCM_X2)
elif F_check == 3:
if check_2 == 0:
LCM_Z1.extend(LCM_Y1)
LCM_Z2.extend(LCM_Y2)
elif check_2 != 0:
LCM_X1.extend(LCM_Z1)
LCM_X2.extend(LCM_Z2)
LCM_Y1.extend(LCM_Z1)
LCM_Y2.extend(LCM_Z2)
elif F_check == 4:
LCM_X1.extend(LCM_Y1)
LCM_X2.extend(LCM_Y2)
LCM_Z1.extend(LCM_Y1)
LCM_Z2.extend(LCM_Y2)
elif F_check == 5:
LCM_Y1.extend(LCM_X1)
LCM_Y2.extend(LCM_X2)
LCM_Z1.extend(LCM_X1)
LCM_Z2.extend(LCM_X2)
elif F_check == 6:
LCM_X1.append(1)
LCM_X2.append(1)
LCM_Y1.append(1)
LCM_Y2.append(1)
LCM_Z1.append(1)
LCM_Z2.append(1)
LCM_X1 = set(LCM_X1)
LCM_Y1 = set(LCM_Y1)
LCM_Z1 = set(LCM_Z1)
LCM_X2 = set(LCM_X2)
LCM_Y2 = set(LCM_Y2)
LCM_Z2 = set(LCM_Z2)
LCM_XYZ_1 = list(set.intersection(LCM_X1,LCM_Y1,LCM_Z1))
LCM_XYZ_2 = list(set.intersection(LCM_X2,LCM_Y2,LCM_Z2))
LCM_XYZ_1.sort(reverse = True)
LCM_XYZ_2.sort(reverse = True)
Main_Game = get_Main_Game()
Pygame provides the pygame.Color object. The object can construct a color from various arguments (e.g. RGBA color channels, hexadecimal numbers, strings, ...).
It also offers the handy method lerp, that can interpolate 2 colors:
Returns a Color which is a linear interpolation between self and the given Color in RGBA space
Use the pygame.Color object and the lerp method to interpolate a color form a list of colors:
def lerp_color(colors, value):
fract, index = math.modf(value)
color1 = pygame.Color(colors[int(index) % len(colors)])
color2 = pygame.Color(colors[int(index + 1) % len(colors)])
return color1.lerp(color2, fract)
The interpolation value must change over time. Use pygame.time.get_ticks to get the current time in milliseconds:
colors = ["green", "blue", "purple", "pink", "red", "orange", "yellow"]
value = (pygame.time.get_ticks() - start_time) / 1000
current_color = lerp_color(colors, value)
See also Color - Lerp
Minimal example:
import pygame, math
def lerp_color(colors, value):
fract, index = math.modf(value)
color1 = pygame.Color(colors[int(index) % len(colors)])
color2 = pygame.Color(colors[int(index + 1) % len(colors)])
return color1.lerp(color2, fract)
pygame.init()
window = pygame.display.set_mode((400, 300))
clock = pygame.time.Clock()
start_time = pygame.time.get_ticks()
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
start_time = pygame.time.get_ticks()
colors = ["green", "blue", "purple", "pink", "red", "orange", "yellow"]
value = (pygame.time.get_ticks() - start_time) / 1000
current_color = lerp_color(colors, value)
window.fill((255, 255, 255))
pygame.draw.circle(window, current_color, window.get_rect().center, 100)
pygame.display.flip()
pygame.quit()
exit()
Related
I am trying to do code a simple game in python using tkinter where a block jumps over obstacles, however I got stuck on the jumping part. Every time I call the jump function it jumps slower and slower, and I don't know the reason. Ty in advance.
import time
import tkinter
import random
bg = "white"
f = 2
k=0
t = 0.01
groundLevel = 550
root = tkinter.Tk()
root.geometry("1000x600")
canvas = tkinter.Canvas(root,width = 1000,height = 1000,bg = bg)
canvas.pack(fill= tkinter.BOTH, expand= True)
posX = 50
posY= 530
startButton = tkinter.Button(canvas,text=" Start ")
def startPlayer(xx,yy):
canvas.create_rectangle(xx-20,yy-22,xx+20,yy+18,fill = "orange")
return(xx,yy)
def move(x,y,x2,y2,direction,fill,outline):
global f
#direction 0 = up
#direction 1 = down
#direction 2 = left
#direction 3 = right
if direction == 0:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y-f,x2,y2-f,fill=fill,outline=outline)
if direction == 1:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y+f,x2,y2+f,fill=fill,outline=outline)
if direction == 2:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y,x2,y2,fill=fill,outline=outline)
if direction == 3:
canvas.create_rectangle(x,y,x2,y2,fill="cyan",outline="cyan")
canvas.create_rectangle(x,y,x2,y2,fill=fill,outline=outline)
def playerJump():
global groundLevel, f, k,posX,posY,t
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 0, fill = "orange",outline = "black")
posY -= 2
canvas.update()
if (posY) == 480:
k = 1
time.sleep(t)
k = 0
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 1, fill = "orange",outline = "black")
posY += 2
canvas.update()
if (posY) == 530:
k = 1
time.sleep(t)
k = 0
def start():
canvas.create_rectangle(0,0,1000,600,fill="cyan")
canvas.create_line(0,550,1000,550,width = 3)
startButton.destroy()
startPlayer(50,530)
startGameButton = tkinter.Button(canvas, text ="Go!",command = playerJump)
startGameButton.place(x = 35, y=400)
return(startGameButton)
def resetButton():
global startGameButton
startGameButton.destroy()
startGameButton = tkinter.Button(canvas, text ="Go!",command = playerJump)
startGameButton.place(x = 35, y=400)
startImage = tkinter.PhotoImage(file="C:/Users/marti/OneDrive/Desktop/Wheel finder/startSign.png")
canvas.create_rectangle(0,0,1000,1000,fill="green")
startButton.config(image = startImage,command = start)
startButton.place(x = 130, y= 25)
canvas.create_rectangle(300,400,700,500,fill="#113B08",outline = "black",width = 3)
canvas.create_text(500,450,text = "By: --------", font = "Arial 30",fill ="white")
I shrinking the sleep time every time it runs so its faster, but that is only a temporary solution and it didn't even work.
Problem with your code is you are always adding new items into your canvas. When you jump you update orange rectangle and repaint its old place. However they stack top of each other and handling too many elements makes slower your program.
We create player and return it to main function.
def startPlayer(xx,yy):
player=canvas.create_rectangle(xx-20,yy-22,xx+20,yy+18,fill = "orange")
return player
This is new start function. Check that we get player and send to playerjump function.
def start():
canvas.create_rectangle(0,0,1000,600,fill="cyan")
canvas.create_line(0,550,1000,550,width = 3)
startButton.destroy()
player = startPlayer(50,530)
startGameButton = tkinter.Button(canvas, text ="Go!",command = lambda :playerJump(player))
startGameButton.place(x = 35, y=400)
return(startGameButton)
And this is playerjump function.We get player and sent to move function.
def playerJump(player):
global groundLevel, f, k,posX,posY,t
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 0, fill = "orange",outline = "black",player=player)
posY -= 2
canvas.update()
if (posY) == 480:
k = 1
time.sleep(t)
k = 0
while k != 1:
move(posX-20,posY-22,posX+20,posY+18,direction = 1, fill = "orange",outline = "black",player=player)
posY += 2
canvas.update()
if (posY) == 530:
k = 1
time.sleep(t)
k = 0
Except move lines, I didn't change anything in this function.
Okay now let's check key part.
def move(x,y,x2,y2,direction,fill,outline,player):
global f
#direction 0 = up
#direction 1 = down
#direction 2 = left
#direction 3 = right
if direction == 0:
canvas.coords(player,x,y-f,x2,y2-f)
if direction == 1:
canvas.coords(player,x,y+f,x2,y2+f)
if direction == 2:
canvas.coords(player,x,y,x2,y2)
if direction == 3:
canvas.coords(player,x,y,x2,y2)
look that instead of creating new rectangles,we updated existing one. which is much more stable
Also you forgot adding root.mainloop in your code snippet.
I'm making a game with pygame and pymunk as a physics engine. I'm trying to kill a bullet whenever it hits a player or goes past its lifetime.
When I tried to space.remove(self.shape) and the second bullet hits the player, it gives me an "AssertionError: shape not in space, already removed. I simply changed it to teleport the bullets away, and then learned of the real error.
When I have more than one bullet in the space and a bullet hits the enemy player, all the current bullets teleport away, which means that when I tried to remove one bullet, it called the remove on all the bullets and thats why I had the initial error.
However the problem still remains that one bullet is being treated as every bullet.
Why is something that should be a non-static variable being called as a static variable?
I even tried to use deepcopy to see if that fixed it, but to no avail
This is my chunk of code, apologies since I don't know what is needed to understand it.
The key parts are most likely the Bullet class, the shoot() function in the Player class, and the drawBulletCollision() function
# PyGame template.
# Import modules.
import sys, random, math, time, copy
from typing import List
import pygame
from pygame.locals import *
from pygame import mixer
import pymunk
import pymunk.pygame_util
from pymunk.shapes import Segment
from pymunk.vec2d import Vec2d
pygame.mixer.pre_init(44110, -16, 2, 512)
mixer.init()
# Set up the window.
width, height = 1440, 640
screen = pygame.display.set_mode((width, height))
bg = pygame.image.load("space.png")
def draw_bg():
screen.blit(bg, (0, 0))
#load sounds
#death_fx = pygame.mixer.Sound("")
#death_fx.set_volume(0.25)
shoot_fx = mixer.Sound("shot.wav")
shoot_fx.set_volume(0.25)
#mixer.music.load("video.mp3")
#mixer.music.play()
#time.sleep(2)
#mixer.music.stop()
#gun_mode_fx = pygame.mixer.Sound("")
#gun_mode_fx.set_volume(0.25)
#thrust_mode_fx = pygame.mixer.Sound("")
#thrust_mode_fx.set_volume(0.25)
collision_fx = mixer.Sound("thump.wav")
collision_fx.set_volume(0.25)
ship_group = pygame.sprite.Group()
space = pymunk.Space()
space.gravity = 0, 0
space.damping = 0.6
draw_options = pymunk.pygame_util.DrawOptions(screen)
bulletList = []
playerList = []
environmentList = []
arbiterList = []
b0 = space.static_body
segmentBot = pymunk.Segment(b0, (0,height), (width, height), 4)
segmentTop = pymunk.Segment(b0, (0,0), (width, 0), 4)
segmentLef = pymunk.Segment(b0, (width,0), (width, height), 4)
segmentRit = pymunk.Segment(b0, (0,0), (0, height), 4)
walls = [segmentBot,segmentLef,segmentRit,segmentTop]
for i in walls:
i.elasticity = 1
i.friction = 0.5
i.color = (255,255,255,255)
environmentList.append(i)
class Player(object):
radius = 30
def __init__(self, position, space, color):
self.body = pymunk.Body(mass=5,moment=10)
self.mode = 0 # 0 is gun, 1 is thrust, ? 2 is shield
self.body.position = position
self.shape = pymunk.Circle(self.body, radius = self.radius)
#self.image
#self.shape.friction = 0.9
self.shape.elasticity= 0.2
space.add(self.body,self.shape)
self.angleGun = 0
self.angleThrust = 0
self.health = 100
self.speed = 500
self.gearAngle = 0
self.turningSpeed = 5
self.shape.body.damping = 1000
self.cooldown = 0
self.fireRate = 30
self.shape.collision_type = 1
self.shape.color = color
playerList.append(self)
def force(self,force):
self.shape.body.apply_force_at_local_point(force,(0,0))
def rocketForce(self):
radians = self.angleThrust * math.pi/180
self.shape.body.apply_force_at_local_point((-self.speed * math.cos(radians),-self.speed * math.sin(radians)),(0,0))
def draw(self):
gear = pygame.image.load("gear.png")
gearBox = gear.get_rect(center=self.shape.body.position)
gearRotated = pygame.transform.rotate(gear, self.gearAngle)
#gearRotated.rect.center=self.shape.body.position
x,y = self.shape.body.position
radianGun = self.angleGun * math.pi/180
radianThrust = self.angleThrust * math.pi/180
radiyus = 30 *(100-self.health)/100
screen.blit(gearRotated,gearBox)
self.gearAngle += 1
if radiyus == 30:
radiyus = 32
pygame.draw.circle(screen,self.shape.color,self.shape.body.position,radiyus,0)
pygame.draw.circle(screen,(0,0,0),self.shape.body.position,radiyus,0)
pygame.draw.line(
screen,(0,255,0),
(self.radius * math.cos(radianGun) * 1.5 + x,self.radius * math.sin(radianGun) * 1.5 + y),
(x,y), 5
)
pygame.draw.line(
screen,(200,200,0),
(self.radius * math.cos(radianThrust) * 1.5 + x,self.radius * math.sin(radianThrust) * 1.5 + y),
(x,y), 5
)
#more
def targetAngleGun(self,tAngle):
tempTAngle = tAngle - self.angleGun
tempTAngle = tempTAngle % 360
if(tempTAngle < 180 and not tempTAngle == 0):
self.angleGun -= self.turningSpeed
elif(tempTAngle >= 180 and not tempTAngle == 0):
self.angleGun += self.turningSpeed
self.angleGun = self.angleGun % 360
#print(tAngle, "target Angle")
#print(self.angleGun, "selfangleGun")
#print(tempTAngle, "tempTAngle")
def targetAngleThrust(self,tAngle):
tempTAngle = tAngle - self.angleThrust
tempTAngle = tempTAngle % 360
if(tempTAngle < 180 and not tempTAngle == 0):
self.angleThrust -= self.turningSpeed
elif(tempTAngle >= 180 and not tempTAngle == 0):
self.angleThrust += self.turningSpeed
self.angleThrust = self.angleThrust % 360
#print(tAngle, "target Angle")
#print(self.angleThrust, "selfangleGun")
#print(tempTAngle, "tempTAngle")
def targetAngle(self,tAngle):
if(self.mode == 0):
self.targetAngleGun(tAngle)
elif(self.mode == 1):
self.targetAngleThrust(tAngle)
def shoot(self):
if(self.cooldown == self.fireRate):
x,y = self.shape.body.position
radianGun = self.angleGun * math.pi/180
spawnSpot = (self.radius * math.cos(radianGun) * 1.5 + x,self.radius * math.sin(radianGun)*1.5+y)
self.shape.body.apply_impulse_at_local_point((-20 * math.cos(radianGun),-20 * math.sin(radianGun)),(0,0))
print(spawnSpot)
bT = Bullet(spawnSpot, 5, 50,self.shape.color)
b = copy.deepcopy(bT)
bulletList.append(b)
space.add(b.shape,b.shape.body)
b.getShot(self.angleGun)
self.cooldown = 0
print('pew')
shoot_fx.play()
# HEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEREEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
def tick(self):
self.draw()
if(self.cooldown < self.fireRate):
self.cooldown += 1
#for o in playerList:
# c = self.shape.shapes_collide(o.shape)
# if(len(c.points)>0):
# self.damage(c.points[0].distance/10)
for o in bulletList:
c = self.shape.shapes_collide(o.shape)
#print(c)
for o in walls:
c = self.shape.shapes_collide(o)
if(len(c.points)>0):
self.damage(c.points[0].distance * 3)
def damage(self, damage):
self.health -= abs(damage)
if self.health < 0:
self.health = 0
#maybe make it part of the player class
def drawWallCollision(arbiter, space, data):
for c in arbiter.contact_point_set.points:
r = max(3, abs(c.distance * 5))
r = int(r)
p = tuple(map(int, c.point_a))
pygame.draw.circle(data["surface"], pygame.Color("red"), p, r, 0)
print('magnitude', math.sqrt(arbiter.total_impulse[0]**2 + arbiter.total_impulse[1]**2))
#print('position', p)
#print(data)
print("its all arbitrary")
s1, s2 = arbiter.shapes
collision_fx.play()
def drawBulletCollision(arbiter, space, data):
s1, s2 = arbiter.shapes
for c in arbiter.contact_point_set.points:
magnitude = math.sqrt(arbiter.total_impulse[0]**2 + arbiter.total_impulse[1]**2)
for p in playerList:
avr = ((c.point_a[0] + c.point_b[0])/2, (c.point_a[1] + c.point_b[1])/2)
distance = (math.sqrt((avr[0] - p.shape.body.position[0]) **2 + (avr[1] - p.shape.body.position[1]) **2 ))
if(distance < Bullet.explosionRadius + Player.radius):
if not(s1.color == s2.color):
p.damage(magnitude)
for b in bulletList:
avr = ((c.point_a[0] + c.point_b[0])/2, (c.point_a[1] + c.point_b[1])/2)
distance = (math.sqrt((avr[0] - p.shape.body.position[0]) **2 + (avr[1] - p.shape.body.position[1]) **2 ))
if(distance < Bullet.explosionRadius + Player.radius):
if not(s1.color == s2.color):
b.damage(magnitude)
pygame.draw.circle(data["surface"], pygame.Color("red"), tuple(map(int, c.point_a)), 10, 0)
print('magnitude', magnitude)
#print('position', p)
#print(data)
print("its all arbitrary")
def drawArbitraryCollision(arbiter, space, data):
collision_fx.play()
class Ship(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("gear.png")
self.rect = self.image.get_rect()
self.rect.center = [x, y]
def rotate(self):
self.image = pygame.transform.rotate(self.image,1)
class Bullet(object):
damage = 2
explosionRadius = 5
def __init__(self, position, size, speed,color):
pts = [(-size, -size), (size, -size), (size, size), (-size, size)]
self.body = copy.deepcopy(pymunk.Body(mass=0.1,moment=1))
self.shape = copy.deepcopy(pymunk.Poly(self.body, pts))
self.shape.body.position = position
self.shape.friction = 0.5
self.shape.elasticity = 1
self.shape.color = color
self.speed = speed
self.size = size
self.shape.collision_type = 2
#space.add(self.body,self.shape)
#bulletList.append(self)
self.lifetime = 0
def getShot(self,angle):
radians = angle * math.pi/180
self.shape.body.apply_impulse_at_local_point((self.speed * math.cos(radians),self.speed * math.sin(radians)),(0,0))
def tick(self):
self.lifetime += 1
if(self.lifetime > 300):
self.shape.body.position = (10000,30)
def damage(self, damage):
self.lifetime = 300
#VELOCITY OF BULLET STARTS WITH VELOCITY OF PLAYER
#MAKE VOLUME OF SOUND DEPEND ON THE IMPULSE FOR THE IMPACTS
#error on purpose so you notice this
#INSTANCES NOT WORKING????
def runPyGame():
# Initialise PyGame.
pygame.init()
# Set up the clock. This will tick every frame and thus maintain a relatively constant framerate. Hopefully.
fps = 60.0
fpsClock = pygame.time.Clock()
running = True
font = pygame.font.SysFont("Arial", 16)
p1 = Player((240,240),space,(132, 66, 245,255))
p2 = Player((1200,400),space,(47, 247, 184,255))
space.add(segmentBot,segmentTop,segmentLef,segmentRit)
# Main game loop.
ch = space.add_collision_handler(1, 0)
ch.data["surface"] = screen
ch.post_solve = drawWallCollision
ch = space.add_collision_handler(1, 2)
ch.data["surface"] = screen
ch.post_solve = drawBulletCollision
ch = space.add_collision_handler(0, 2)
ch.data["surface"] = screen
ch.post_solve = drawArbitraryCollision
dt = 1/fps # dt is the time since last frame.
while True: # Loop forever!
keys = pygame.key.get_pressed()
for event in pygame.event.get():
# We need to handle these events. Initially the only one you'll want to care
# about is the QUIT event, because if you don't handle it, your game will crash
# whenever someone tries to exit.
if event.type == QUIT:
pygame.quit() # Opposite of pygame.init
sys.exit() # Not including this line crashes the script on Windows.
if event.type == KEYDOWN:
if event.key == pygame.K_s:
p1.mode = -(p1.mode - 0.5) + 0.5
print(p1.mode)
if (event.key == pygame.K_k and p1.mode == 0):
p1.shoot()
if event.key == pygame.K_KP_5:
p2.mode = -(p2.mode - 0.5) + 0.5
print(p2.mode)
if (event.key == pygame.K_m and p2.mode == 0):
p2.shoot()
#b = Bullet((200,200),51,51)
if(keys[K_w]):
p1.targetAngle(90)
if(keys[K_q]):
p1.targetAngle(45)
if(keys[K_a]):
p1.targetAngle(0)
if(keys[K_z]):
p1.targetAngle(315)
if(keys[K_x]):
p1.targetAngle(270)
if(keys[K_c]):
p1.targetAngle(225)
if(keys[K_d]):
p1.targetAngle(180)
if(keys[K_e]):
p1.targetAngle(135)
if(keys[K_k] and p1.mode == 1):
p1.rocketForce()
if(keys[K_KP_8]):
p2.targetAngle(90)
if(keys[K_KP_7]):
p2.targetAngle(45)
if(keys[K_KP_4]):
p2.targetAngle(0)
if(keys[K_KP_1]):
p2.targetAngle(315)
if(keys[K_KP_2]):
p2.targetAngle(270)
if(keys[K_KP_3]):
p2.targetAngle(225)
if(keys[K_KP_6]):
p2.targetAngle(180)
if(keys[K_KP_9]):
p2.targetAngle(135)
if(keys[K_m] and p2.mode == 1):
p2.rocketForce()
# Handle other events as you wish.
screen.fill((250, 250, 250)) # Fill the screen with black.
# Redraw screen here.
### Draw stuff
draw_bg()
space.debug_draw(draw_options)
for i in playerList:
i.tick()
screen.blit(
font.render("P1 Health: " + str(p1.health), True, pygame.Color("white")),
(50, 10),
)
screen.blit(
font.render("P2 Health: " + str(p2.health), True, pygame.Color("white")),
(50, 30),
)
for i in bulletList:
i.tick()
ship_group.draw(screen)
# Flip the display so that the things we drew actually show up.
pygame.display.update()
dt = fpsClock.tick(fps)
space.step(0.01)
pygame.display.update()
runPyGame()
I cant point to the exact error since the code is quite long and depends on files I dont have. But here is a general advice for troubleshooting:
Try to give a name to each shape when you create them, and then print it out. Also print out the name of each shape that you add or remove from the space. This should show which shape you are actually removing and will probably make it easy to understand whats wrong.
For example:
...
self.shape = pymunk.Circle(self.body, radius = self.radius)
self.shape.name = "circle 1"
print("Created", self.shape.name)
...
print("Adding", self.shape.name)
space.add(self.body,self.shape)
...
(Note that you need to reset the name of shapes you copy, since otherwise the copy will have the same name.)
The code should display 1 or 2 circles depending on what you ask, and the program asks the user for the frequency of both circles.
I'm pretty sure the code works right, but the problem is that when the circles are displayed, 1 of the circles keeps flashing, but I don't know how to adjust it.
I have tried by playing around with it, by moving the update after or before, by updating it 2 times, after or before, anyway i'm stuck and I don't know how I should do it
import pygame
import math
import time
clock = pygame.time.Clock()
pygame.init()
pygame.display.set_caption("circles")
screen = pygame.display.set_mode([1000,700])
width_2 = int(screen.get_width() / 2)
width_3 = int(screen.get_width() / 3)
height_center = int(screen.get_height() / 2 )
black = (0,0,0)
keep_going = True
onecircle = False
twocircles = False
white = (255,255,255)
blue = (0,0,255)
red = (255,0,0)
freq = 0
circle1spot = (0,0)
circle2spot = (0,0)
freq2 = 0
pointradius = 3
num_circles = 0
num_circles2 = 0
radius = 0
radius2 = 0
centerradius = 20
howmanycircles = int(input("How many circles? \n"))
if howmanycircles == 1:
onecircle = True
elif howmanycircles == 2:
twocircles = True
else:
print("Answer not correct, 1 circle selected by default")
onecircle = True
if howmanycircles == 1:
freqinput = int(input("Frequency 1 circle, MIN [1], MAX [148]: \n"))
freq = 150 - freqinput
elif howmanycircles == 2:
freqinput = int(input("Frequency 1 circle, MIN [1], MAX [148]: \n"))
freq = 150 - freqinput
freqinput2 = int(input("Frequency 2 circle, MIN [1], MAX [148]: \n"))
freq2 = 150 - freqinput2
def circle1(radius, centerradius):
radius = radius + 1
num_circles = math.ceil(radius / freq)
#screen.fill(white)
radiusMax = num_circles * freq
pace = freq / radiusMax
for y in range(num_circles, 1, -1):
radiusY = int(((pace * (num_circles - y)) + pace) * radiusMax) + (radius % freq)
pygame.draw.circle(screen, black, circle1spot, centerradius, 1 )
pygame.draw.circle(screen, black, circle1spot, radiusY, 1)
#pygame.display.update()
return radius
def circle2(raggio2, centerradius):
radius2 = radius2 + 1
num_circles2 = math.ceil(radius2 / freq2)
#screen.fill(white)
radiusMax = num_circles2 * freq2
pace = freq2 / radiusMax
for y in range(num_circles2, 1, -1):
radiusY = int(((pace * (num_circles2 - y)) + pace) * radiusMax) + (radius2 % freq2)
pygame.draw.circle(screen, red, circle2spot, centerradius, 1 )
pygame.draw.circle(screen, red, circle2spot, radiusY, 1)
#pygame.display.update()
return radius2
while keep_going:
for event in pygame.event.get():
if event.type == pygame.QUIT:
keep_going = False
if event.type == pygame.MOUSEBUTTONDOWN:
if pygame.mouse.get_pressed()[0]:
#mousedownleft = True
circle1spot = pygame.mouse.get_pos()
print(circle1spot)
if pygame.mouse.get_pressed()[2]:
#mousedownright = True
circle2spot = pygame.mouse.get_pos()
pygame.draw.circle(screen, blue, (width_3,height_center), pointradius, 3 )
pygame.draw.circle(screen, blue, ((width_3*2),height_center), pointradius, 3 )
pygame.draw.circle(screen, blue, ((width_2),height_center), pointradius, 3 )
if onecircle == True:
radius = circle1(radius,centerradius)
pygame.display.update()
elif twocircles == True:
radius = circle1(radius,centerradius) #this is the critical zone
pygame.display.update() #this is the critical zone
radius2 = circle2(radius2, centerradius) #this is the critical zone
pygame.display.update() #this is the critical zone
screen.fill(white) #this is the critical zone
pygame.quit()
I'm looking for a possible solution to make it work correctly and to get it refreshed correctly
It is sufficient to do one single pygame.display.update() at the end of the main loop.
clear the display
do all the drawing
update the display
while keep_going:
# [...]
# 1. clear the display
screen.fill(white)
# 2. do all the drawing
pygame.draw.circle(screen, blue, (width_3,height_center), pointradius, 3 )
pygame.draw.circle(screen, blue, ((width_3*2),height_center), pointradius, 3 )
pygame.draw.circle(screen, blue, ((width_2),height_center), pointradius, 3 )
if onecircle == True:
radius = circle1(radius,centerradius)
elif twocircles == True:
radius = circle1(radius,centerradius)
radius2 = circle2(radius2, centerradius)
# 3. update the display
pygame.display.update()
I am having trouble sorting out a loop in my program which does not behave a I would expect. This program lets you play "Connect Four". I included the full (runnable code) and the excerpt that troubles me at the end.
import numpy as np
import random
import pygame
import time
BOARD_SIZE = 6
BOARD_BOX_NUM = BOARD_SIZE ** 2
GIVEN_IDS = 0
# ------------------------------- setting up pygame --------------------------------------------
pygame.init()
display_height = 600
display_width = 600
game_display = pygame.display.set_mode((display_width, display_height))
clock = pygame.time.Clock()
# ------------------------------- colours ----------------------------------------------------
white = (255, 255, 255)
black = (0, 0, 0)
blue = (0,0,255)
light_blue = (30, 144, 255)
red = (200, 0, 0)
light_red = (255, 0, 0)
yellow = (200, 200, 0)
light_yellow = (255, 255, 0)
green = (34, 177, 76)
light_green = (0, 255, 0)
# ------------------------------- methods for the game algorythm ------------------------------
def rolling_window(a, size):
# This method is required for the algorythm that broadcasts the board for a win
shape = a.shape[:-1] + (a.shape[-1] - size + 1, size)
strides = a.strides + (a. strides[-1],)
return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
class Chip:
def __init__(self, colour):
if colour == "Blue":
self.colour = colour
if colour == "Red":
self.colour = colour
global GIVEN_IDS
self.iD = GIVEN_IDS
GIVEN_IDS += 1
def get_iD(self):
return self.iD
class Game:
def __init__(self, player_colour="Blue", comp_colour="Red"):
self.board = np.empty(BOARD_BOX_NUM, dtype=Chip)
self.board = self.board.reshape(BOARD_SIZE, BOARD_SIZE)
self.player_colour = player_colour
self.comp_colour = comp_colour
def get_comp_colour(self):
return self.comp_colour
def get_player_colour(self):
return self.player_colour
def give_str_board(self):
"""Returns a copy of the board array and replaces the Chip objects with the str with the colour of the chips."""
this_board = np.copy(self.board)
for x, y in np.ndindex(this_board.shape):
if this_board[x, y] is not None:
if this_board[x, y].colour == "Blue":
this_board[x, y] = "Blue"
else:
this_board[x, y] = "Red"
return this_board
def print_board_in_console(self):
"""This function holds the board which is a 8x8 matrix."""
this_board = self.give_str_board()
print(this_board)
print("-"*40)
def insert_chip(self, chip, col):
"""Method for making a new entry to the board. For the player and enemy.
The column has to be parametrised in the pythonic way. i.e. cols from 0-7."""
# slices the entries of the column into a new array
col_entries = self.board[:, col:col+1]
# checks for all unoccupied pos in this column(entries are None)
# double array of indexes with the form (array([row_i, ...]), array([column_i, ...]))
# checking the condition with "is" is here not possible, because "is" operator cannot be overloaded
none_indexes = np.where(col_entries == None)
# check whether the column cannot contain an extra chip and function has to be aborted
if len(none_indexes[0]) == 0:
print("This column is full. Chose again.")
return False
# the pos where the chip will fall is the one with the highest index
self.board[len(none_indexes[0]) - 1, col] = chip
return True
def get_chip(self, x, y):
"""This function can return the information about a chip in a pos of the board."""
chip = self.board[x, y]
return chip
def is_won(self):
"""This function can be used to check the board on whether the game has been decided.
The function differentiates between player and enemy and returns..."""
winning_chip = None
# get a copy of the board which only contains str and None
this_board = self.give_str_board()
flipped_board = np.fliplr(this_board)
# in order to check the entire board for 4 Chips in a formation individual rows and cols are examined
# use for loops to isolate rows and cols
for this_ax in range(0, 2):
for index in range(0, BOARD_SIZE):
# the stack will contain the row[index] when this_ax = 0 and the col[index] when this_ax = 1
stack = this_board.take(index, axis=this_ax)
# this will be the patterns, that are searched for
winning_formations = [['Blue', 'Blue', 'Blue', 'Blue'], ['Red', 'Red', 'Red', 'Red']]
for i in winning_formations:
bool_array = rolling_window(stack, 4) == i
if [True, True, True, True] in bool_array.tolist():
# the stack_index is the index of the first chip in the 4xChip formation in the row/col
stack_index_tuple, = np.where(np.all(bool_array == [True, True, True, True], axis=1))
stack_index = stack_index_tuple[0]
loop_index = index
# this_ax = 0 means loop_index is row and stack_index is col
if this_ax == 0:
winning_chip = self.get_chip(loop_index, stack_index)
break
else:
winning_chip = self.get_chip(stack_index, loop_index)
break
# This next part of the algorythm checks whether diagonal patterns of the array
# contain a winning formation
# if this bit is problematic: change the 0 in range()!!!
for index in range(0, BOARD_SIZE - 2):
for board in [this_board, flipped_board]:
diag_elements = board.diagonal(index)
for i in winning_formations:
bool_array = rolling_window(diag_elements, 4) == i
if [True, True, True, True] in bool_array.tolist():
# the stack_index is the index of the first chip in the 4xChip formation in the row/col
diag_el_index_tuple, = np.where(
np.all(bool_array == [True, True, True, True], axis=1))
diag_index = diag_el_index_tuple[0]
loop_index = index
# this_ax = 0 means loop_index is row and stack_index is col
if board == this_board:
winning_chip = self.get_chip(loop_index, diag_index)
break
else:
winning_chip = self.get_chip(diag_index, loop_index)
break
if winning_chip is not None:
return winning_chip.colour
return None
def get_comp_move(self):
"""This method generates the computer's move (the column) based on INTELLIGENCE!!!!
Returns the column of the move"""
c_colour = self.get_comp_colour()
p_colour = self.get_player_colour()
# check, if the comp can win in the next move
for i in range(0, BOARD_SIZE):
board = self.give_str_board()
chip = Chip(c_colour)
self.insert_chip(chip, i)
if self.is_won() == c_colour:
return i
# check, if the player can win in the next move and block that position
for i in range(0, BOARD_SIZE):
board = self.give_str_board()
chip = Chip(p_colour)
self.insert_chip(chip, i)
if self.is_won() == p_colour:
return i
# accumulate preferable positions for the next move
good_spots = []
board = self.give_str_board()
for axis in range(0, 2):
for index in range(0, BOARD_SIZE):
# the stack will contain the row[index] when this_ax = 0 and the col[index] when this_ax = 1
stack = board.take(index, axis=axis)
# this will be the patterns, that are searched for
for i in [c_colour, c_colour]:
bool_array = rolling_window(stack, 2) == i
if [True, True] in bool_array.tolist():
# the stack_index is the index of the first chip in the 4xChip formation in the row/col
stack_index_tuple, = np.where(np.all(bool_array == [True, True], axis=1))
stack_index = stack_index_tuple[0]
# this_ax = 0 means loop_index is row and stack_index is col
if axis == 0:
# "- 1" because this if-statement is called when broadcasting a row. i.e. column before
good_spots.append(stack_index - 1)
else:
good_spots.append(index)
# The pick is the x-coo of the first of a series of two chips (column) or the x before (row).
print(good_spots)
pick = random.randint(0, len(good_spots))
return pick
# make a move, "better than nothing"
flag = True
while flag is True:
rnd = random.randint(0, BOARD_SIZE)
if self.board[rnd, 0] is None:
return rnd
# ------------------------------- this part will take care of the visualisation in pygame ------------------------
# buttons = []
#
# def button(self, text, x, y, radius, inactive_colour, active_colour, action=None, size=" "):
# cur = pygame.mouse.get_pos()
# if x + radius > cur[0] > x - radius and y + radius > cur[1] > y - radius:
# pygame.draw.circle(game_display, active_colour, (x, y), radius)
#
# ix = None
# iy = None
# for event in pygame.event.get():
# if event.type == pygame.MOUSEBUTTONDOWN:
# ix, iy = event.pos
#
# if ix is not None and iy is not None:
# if x + radius > ix > x - radius and y + radius > iy > y - radius and action is not None:
# if action == "quit":
# pygame.quit()
# quit()
#
# if action == "move":
# col = int(x / 80 - 90)
# self.insert_chip(Chip(self.get_player_colour()), col)
# else:
# pygame.draw.circle(game_display, inactive_colour, (x, y), radius)
def draw_board(self):
board = self.give_str_board()
pygame.draw.rect(game_display, green, (60, 60, 80*BOARD_SIZE, 80*BOARD_SIZE))
for y in range(0, BOARD_SIZE):
for x in range(0, BOARD_SIZE):
dx = 90 + x * 80
dy = 90 + y * 80
if board[x, y] is None:
pygame.draw.circle(game_display, white, (dy, dx), 30)
elif board[x, y] == "Blue":
pygame.draw.circle(game_display, blue, (dy, dx), 30)
elif board[x, y] == "Red":
pygame.draw.circle(game_display, red, (dy, dx), 30)
# draw the selector square
pygame.draw.rect(game_display, yellow, (self.selector_pos * 80 + 80, 60, 20, 20))
selector_pos = 0
def move_selector(self, dir):
selector_pos = self.selector_pos
if dir == 'left' and selector_pos >= 0:
new_x = selector_pos * 80 - 80
pygame.draw.rect(game_display, yellow, (new_x, 60, 20, 20))
self.selector_pos -= 1
if dir == 'right' and selector_pos <= BOARD_SIZE - 1:
new_x = selector_pos * 80 + 80
pygame.draw.rect(game_display, yellow, (new_x, 60, 20, 20))
self.selector_pos += 1
def intro(self):
return
def game_loop(self, comp_goes_first=False):
game_over = False
while game_over is not True:
# here comes the computer's move
if comp_goes_first is True:
col = self.get_comp_move()
chip = Chip(self.get_comp_colour())
self.insert_chip(chip, col)
# This will be the player move
move_over = False
while move_over is not True:
for event in pygame.event.get():
print(event)
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.move_selector('right')
pygame.draw.rect(game_display, yellow, (50, 50, 50, 50))
if event.key == pygame.K_LEFT:
self.move_selector('left')
if event.key == pygame.K_RETURN:
# the selector position indicates the column which the player has chosen.
col = self.selector_pos
chip = Chip(self.get_player_colour())
move_over = self.insert_chip(chip, col)
game_display.fill(white)
self.draw_board()
pygame.display.update()
comp_goes_first = True
clock.tick(15)
game = Game()
game.game_loop()
Now to the part that particularly leaves me in doubt.
As the following loop runs, I can make the Player's move in the game as expected with the use of arrow keys and return, however then the program goes crazy. It will do multiple moves for the computer and sometimes the player as well. This is a picture of the GUI after the loop has run once (player's move and then the computer's move and now it would be the players move again).
At this point I am wondering whether I got something fundamentally wrong with the while loop construction here and I cannot figure it out. What is the issue?
game_over = False
while game_over is not True:
# here comes the computer's move
if comp_goes_first is True:
col = self.get_comp_move()
chip = Chip(self.get_comp_colour())
self.insert_chip(chip, col)
# This will be the player move
move_over = False
while move_over is not True:
for event in pygame.event.get():
print(event)
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.move_selector('right')
if event.key == pygame.K_LEFT:
self.move_selector('left')
if event.key == pygame.K_RETURN:
# the selector position indicates the column which the player has chosen.
col = self.selector_pos
chip = Chip(self.get_player_colour())
move_over = self.insert_chip(chip, col)
game_display.fill(white)
self.draw_board()
pygame.display.update()
comp_goes_first = True
clock.tick(15)
Your problem looks to me to be in your AI logic. You're making a copy of the board for the AI to experiment on, but then you're not using it, and instead inserting chips into the real board.
# check, if the comp can win in the next move
for i in range(0, BOARD_SIZE):
# Never used.
board = self.give_str_board()
chip = Chip(c_colour)
# inserting onto the real board.
self.insert_chip(chip, i)
print("insert check", board, self)
if self.is_won() == c_colour:
return i
Hello I'm new to python and I'm trying to make a simple counter as a delay to blit some text in pygame so it does not show up instantly.
What I want to do is make text appear in a box when the player levels up.
def TEXT(A, B, C):
global ONE, TWO, THREE, FOUR
counter = 0
text = True
if text:
if counter == 10:
ONE = A
if counter == 20:
TWO = B
elif counter == 30:
THREE = C
elif counter == 40:
ONE = None
TWO = None
THREE = None
text = False
counter += 1
The code as a whole seems to be working fine but I just can't get the counter to work.
Here's the full program as reference:
from pygame import *
from userInterface import Title, Dead
WIN_WIDTH = 640
WIN_HEIGHT = 400
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 32
FLAGS = 0
init()
screen = display.set_mode(DISPLAY, FLAGS, DEPTH)
saveState = False
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (30, 30, 30)
FONT = font.SysFont("Courier New", 15)
heroHP = 1000
hero={'name' : 'Hero',
'height':4,
'lvl': 1,
'xp' : 0,
'reward' : 0,
'lvlNext':25,
'stats': {'str' : 12, # strength
'dex' : 4, # dexterity
'int' : 15, # intelligence
'hp' : heroHP, # health
'atk' : [250,350]}} # range of attack values
boss1={'name' : 'Imp',
'xp' : 0,
'lvlNext':25,
'reward' : 25,
'stats': {'hp' :400,
'atk' : [300,350]}}
ONE = None
TWO = None
THREE = None
FOUR = None
def TEXT(A, B, C):
global ONE, TWO, THREE, FOUR
counter = 0
text = True
if text:
if counter == 0:
ONE = A
if counter == 10:
TWO = B
elif counter == 20:
THREE = C
elif counter == 30:
ONE = None
TWO = None
THREE = None
text = False
counter += 1
def level(char): # level up system
#nStr, nDex, nInt=0,0,0
while char['xp'] >= char['lvlNext']:
char['lvl']+=1
char['xp']=char['xp'] - char['lvlNext']
char['lvlNext'] = round(char['lvlNext']*1.5)
nStr=0.5*char['stats']['str']+1
nDex=0.5*char['stats']['dex']+1
nInt=0.5*char['stats']['int']+1
print(f'{char["name"]} levelled up to level {char["lvl"]}!') # current level
A = (f'{char["name"]} levelled up to level {char["lvl"]}!') # current level
print(f'( INT {round((char["stats"]["int"] + nInt))} - STR {round(char["stats"]["str"] + nStr)} - DEX {round(char["stats"]["dex"] + nDex)} )') # print new stats
B = (f'( INT {round((char["stats"]["int"] + nInt))} - STR {round(char["stats"]["str"] + nStr)} - DEX {round(char["stats"]["dex"] + nDex)} )') # print new statsm
char['stats']['str'] += nStr
char['stats']['dex'] += nDex
char['stats']['int'] += nInt
TEXT(A,B,None)
from random import randint
def takeDmg(attacker, defender): # damage alorithm
dmg = randint(attacker['stats']['atk'][0], attacker['stats']['atk'][1])
defender['stats']['hp'] = defender['stats']['hp'] - dmg
print(f'{defender["name"]} takes {dmg} damage!')
#TEXT(f'{defender["name"]} takes {dmg} damage!')
if defender['stats']['hp'] <= 0:
print(f'{defender["name"]} has been slain...')
#TEXT(f'{defender["name"]} has been slain...')
attacker['xp'] += defender['reward']
level(attacker)
if defender==hero:
#Dead()
pass
else:
hero['stats']['hp']=heroHP
#Title()
pass
def Battle(player, enemy):
global ONE, TWO, THREE, FOUR
mouse.set_visible(1)
clock = time.Clock()
YES = Rect(100, 100, 50, 50)
NO = Rect(500, 100, 50, 50)
Text = Rect(70, 300, 500, 75)
#while ((enemy['stats']['hp']) > 0):
while True:
for e in event.get():
if e.type == QUIT:
exit("Quit") # if X is pressed, exit program
elif e.type == KEYDOWN:
if e.key == K_ESCAPE:
exit()
elif e.type == MOUSEBUTTONDOWN:
# 1 is the left mouse button, 2 is middle, 3 is right.
if e.button == 1:
# `event.pos` is the mouse position.
if YES.collidepoint(e.pos):
takeDmg(player, enemy)
print(f'{enemy["name"]} takes the opportunity to attack!')
#TEXT(f'{enemy["name"]} takes the opportunity to attack!')
takeDmg(enemy, player)
elif NO.collidepoint(e.pos):
print(f'{enemy["name"]} takes the opportunity to attack!')
#TEXT(f'{enemy["name"]} takes the opportunity to attack!')
takeDmg(enemy, player)
screen.fill(WHITE)
draw.rect(screen, BLACK, YES)
draw.rect(screen, BLACK, NO)
draw.rect(screen, GRAY, Text)
YES_surf = FONT.render(("YES"), True, WHITE)
NO_surf = FONT.render(("NO"), True, WHITE)
Text1_surf = FONT.render(ONE, True, WHITE)
Text2_surf = FONT.render(TWO, True, WHITE)
Text3_surf = FONT.render(THREE, True, WHITE)
Text4_surf = FONT.render(FOUR, True, WHITE)
screen.blit(YES_surf, YES)
screen.blit(NO_surf, NO)
screen.blit(Text1_surf, (80, 305))
screen.blit(Text2_surf, (80, 320))
screen.blit(Text3_surf, (80, 335))
screen.blit(Text4_surf, (80, 350))
display.update()
clock.tick(60)
Battle(hero, boss1)
If all you want to do is make counter a global variable that only gets set to 0 at the start of the program instead of a local variable that gets reset to 0 on each call, you do that the exact same way you handled ONE and the other globals in the same function:
counter =0
def TEXT(A, B, C):
global ONE, TWO, THREE, FOUR
global counter
text = True
if text:
if counter == 10:
# etc.
counter += 1
The only changes are moving that counter = 0 from the function body to the top level, and adding global counter at the start of the function body.