Related
I'm trying to make a tic tac toe game for 2 players - user and computer using minimax algorythm. My problem is that the code has an error and it doesn't work properly, but I'm not sure why. More explanation about code you can see in comments for code.
I have a TypeError: cannot unpack non-iterable Non-Type object for line 258 in main row, col = comp.evaluation(board)
Here is the code and comments:
file ttt1 with variables assignment:
import random
WIDTH = 600
HEIGHT = 600
ROWS = 3 #rows of the grid
COLS = 3 #cols of the grid
SQUARE_SIZE = WIDTH // COLS #size of one square of the grid - can be height devided by rows
LINE_WIDTH = 15 #width of the lines of the grid
#circle
CIRCLE_WIDTH = 20
RADIUS = SQUARE_SIZE // 3
#cross
CROSS_WIDTH = 20
OFFSET = 50
#define colors
BACKCOLOR = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
LINE_COLOR = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
CIRCLE = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
CROSS = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
file with classes and etc:
import sys #for quitting the application
import copy
import random
import pygame
import numpy as np
from ttt1 import * #importing things from ttt1 file
#pygamecode and set up
pygame.init() #initializig pygame module
screen = pygame.display.set_mode((WIDTH, HEIGHT)) #setting up the screen - width and height
pygame.display.set_caption("Tic Tac Toe game with minimax")
screen.fill(BACKCOLOR) #filling in the screen with a background colors
class Board:
def __init__(self): #2 dimensional array of 0-s
self.squares = np.zeros((ROWS, COLS)) #parameters as tuple with 0-s, making empty squares
#self.mark_squares(1, 2, 3) #for testing
#print(self.squares) #return 0-s in terminal
self.empty_square = self.squares #list of squares
self.marked_square = 0 #state of a square
def final_condition(self, show=False):
#returning 0 if there is no win till now(not a draw condition), if player 1 wins - then returns 1; if player 2 wins returns 2
#vertical winning situation
for col in range(COLS):
if self.squares[0][col] == self.squares[1][col] == self.squares[2][col] != 0:
if show:
color = CIRCLE if self.squares[0][col] == 2 else CROSS
start_pos = (col * SQUARE_SIZE + SQUARE_SIZE // 2, 20)
final_pos = (col * SQUARE_SIZE + SQUARE_SIZE // 2, HEIGHT - 20)
pygame.draw.line(screen, color, start_pos, final_pos, LINE_WIDTH)
return self.squares[0][col] #return any of column from previous if stat
#horizontal winning situation
for row in range(ROWS):
if self.squares[row][0] == self.squares[row][1] == self.squares[row][2] != 0:
if show:
color = CIRCLE if self.squares[row][0] == 2 else CROSS
start_pos = (20, row * SQUARE_SIZE + SQUARE_SIZE // 2)
final_pos = (WIDTH - 20, row * SQUARE_SIZE + SQUARE_SIZE // 2)
pygame.draw.line(screen, color, start_pos, final_pos, LINE_WIDTH)
return self.squares[row][0]
#going down diagonal winning situation
if self.squares[0][0] == self.squares[1][1] == self.squares[2][2] != 0:
if show:
color = CIRCLE if self.squares[1][1] == 2 else CROSS
start_pos = (20, 20)
final_pos = (WIDTH - 20, HEIGHT - 20)
pygame.draw.line(screen, color, start_pos, final_pos, CROSS_WIDTH)
return self.squares[1][1] #11 is a common square between 2 diagonals so it's returning this one
#going up diagonal winning situation
if self.squares[2][0] == self.squares[1][1] == self.squares[0][2] != 0:
if show:
color = CIRCLE if self.squares[1][1] == 2 else CROSS
start_pos = (20, HEIGHT - 20)
final_pos = (WIDTH - 20, 20)
pygame.draw.line(screen, color, start_pos, final_pos, CROSS_WIDTH)
return self.squares[1][1]
return 0 #no winning yet
def mark_squares(self, row, col, player): #for 2 players, one will be marked and zeros in tuple will be replaced in a square that was marked by a player with a player
self.squares[row][col] = player
#ach time square is marked it's gonna be increased by one
self.marked_square += 1
def empty_square(self, row, col): #checking if the square is empy or not
return self.squares[row][col] == 0 #if return True than it's empty, if False than bot empty
def return_empty(self):
#square that is needed to be deleted from the empty squares
empty = []
for row in range(ROWS):
for col in range(COLS): #2 dimentional array(matrix)
if self.empty_square[row, col]:
empty.append((row, col))
return empty
def isfull(self):
return self.marked_square == 9 #max amount of squares when the board is full
def isempty(self):
return self.marked_square == 0 #return if the square is marked
class MinMax:
def __init__(self, level=1, player=2):
self.level = level #level of game
self.player = player #there are 2 players
def random_choice(self, board): #random choice function
empty = board.return_empty()
index = random.randrange(0, len(empty))
return empty[index] #row and col
def minmax(self, board, max):
#checking terminal case
case = board.final_condition() #case
#player 1 wins
if case == 1:
return 1, None #evaluation, move
#player 2 wins
if case == 2:
return -1, None
#draw
elif board.isfull():
return 0, None
#coding the algorythm
if max: #if player is maximizing
max_eval = -100 #minimal eva;uation that player gets from a specific board
best_move = None
empty_square = board.return_empty()
#loop each square inside of the list of empty squares
for(row, col) in empty_square:
temp_board = copy.deepcopy(board) #copying the board for testing on other boards
temp_board.mark_squares(row, col, 1)#marking the square to a copy
eval = self.minmax(temp_board, False)[0] #it's true because of changing the player, first position is an evaluation
if eval > max_eval:
max_eval = eval #save eval into max eval
best_move = (row, col)
return max_eval, best_move
elif not max: #if player is minimizing
min_eval = 100 #minimal eva;uation that player gets from a specific board
best_move = None
empty_square = board.return_empty()
#loop each square inside of the list of empty squares
for(row, col) in empty_square:
temp_board = copy.deepcopy(board) #copying the board for testing on other boards - a copy of the object is copied into another object
temp_board.mark_squares(row, col, self.player)#marking the square to a copy
eval = self.minmax(temp_board, True)[0] #it's true because of changing the player, first position is an evaluation
if eval < min_eval:
min_eval = eval
best_move = (row, col)
return min_eval, best_move #returning the min eval with respect to best move
def evaluation(self, main_board):
if self.level == 0:
#random choice
eval = 'random'
move = self.random_choice(main_board)
else:
#minmax alg choice
eval, move = self.minmax(main_board, False)
print(f'Minimax is chosen for marking the square in position {move} with an evaluation {eval}')
return move #move is the row and the col
#drowing lines for game - lines of the grid
class TicTac:
def __init__(self): #init method for the new game objects
self.board = Board() #creating a new board class
self.comp = MinMax()
self.player = 2 #who is the next player to mark in the squares #player 1 is crosses and player 2 is circles
self.gamemode = 'computer' #by human, or by computer (ai)
self.run = True
self.lines() #colling the method for showing the lines
def make_move(self, row, col):
self.board.mark_squares(row, col, self.player) #the last parameter changes from 1 to tictac.player
#print(board.squares) #testing, return 0 and 1 in console whether it's empty or not
self.draw_figure(row, col) #graphic displaying of the information
self.another_player()
#print(board.squares) #checking the another player works or not, result in console
def reset(self):
self.__init__() #restarting all attributes to default values
def lines(self): #defining the creating the grid function
#background color after reseting
screen.fill(BACKCOLOR)
#vertical lines
pygame.draw.line(screen, LINE_COLOR,(SQUARE_SIZE, 0), (SQUARE_SIZE, HEIGHT), LINE_WIDTH) #first line
pygame.draw.line(screen, LINE_COLOR,(WIDTH - SQUARE_SIZE, 0), (WIDTH - SQUARE_SIZE, HEIGHT), LINE_WIDTH) #y axis is 0, x axis is first; second line
#horizontal lines
pygame.draw.line(screen, LINE_COLOR,(0,SQUARE_SIZE), (WIDTH, SQUARE_SIZE), LINE_WIDTH) #first line
pygame.draw.line(screen, LINE_COLOR,(0, HEIGHT - SQUARE_SIZE), (WIDTH, HEIGHT - SQUARE_SIZE), LINE_WIDTH) #second line
def draw_figure(self, row, col):
if self.player == 1:
#drawing the cross
start_down_line = (col * SQUARE_SIZE + OFFSET, row * SQUARE_SIZE + OFFSET) #adjusting the down going line of the cross
end_down_line = (col * SQUARE_SIZE + SQUARE_SIZE - OFFSET, row * SQUARE_SIZE + SQUARE_SIZE - OFFSET)
pygame.draw.line(screen, CROSS, start_down_line, end_down_line, CROSS_WIDTH) #drawing the cross
#going up line
start_up_line = (col * SQUARE_SIZE + OFFSET, row * SQUARE_SIZE + SQUARE_SIZE - OFFSET)
end_up_line = (col * SQUARE_SIZE + SQUARE_SIZE - OFFSET, row * SQUARE_SIZE + OFFSET)
pygame.draw.line(screen, CROSS, start_up_line, end_up_line, CROSS_WIDTH) #drawing the line
elif self.player == 2:
#drawing the circle
center = (col * SQUARE_SIZE + SQUARE_SIZE // 2, row * SQUARE_SIZE + SQUARE_SIZE // 2) #center position
pygame.draw.circle(screen, CIRCLE, center, RADIUS, CIRCLE_WIDTH)
def another_player(self): #chenging the player that is marking the square to the next one
self.player = self.player % 2 + 1 #first operation will return the remainder ((model)%2) if the player is 1, than it adds 1 and there is o a remainder so the player is changing
def change_gamemode(self):
#self.gamemode = 'computer' if self.gamemode == 'user' else 'user'
if self.gamemode == 'user':
self.gamemode = 'computer'
else:
self.gamemode = 'user'
def isover(self):
return self.board.final_condition(show=True) != 0 or self.board.isfull()
def main(): #main function where the code will be executing
#game object
tictac = TicTac() #object and the class
board = tictac.board
comp = tictac.comp
#mainloop
while True:
#pygame events
for event in pygame.event.get():
if event.type == pygame.QUIT: #event is any actin happening in the game(pressing the key etc)
pygame.quit()
sys.exit() #for exiting the game
if event.type == pygame.KEYDOWN: #keydown event
#g-gamemode
if event.key == pygame.K_g:
tictac.change_gamemode()
# r = restart
if event.key == pygame.K_r:
tictac.reset()
board = tictac.board #board and ai will be reseted again and start from the initializing condition
comp = tictac.comp
# 0 -random computer alg
if event.key == pygame.K_0:
comp.level = 0
# 1 - random
if event.key == pygame.K_1:
comp.level = 1
if event.type == pygame.MOUSEBUTTONDOWN: #position of coursor in the pixels
pos = event.pos #position of pixels
row = pos[1]//SQUARE_SIZE #represents y axis of the board
col = pos[0]//SQUARE_SIZE #position 0 of x axis
#print(row, col) #testing
if board.empty_square[row,col] and tictac.run:
tictac.make_move(row, col)
if tictac.isover():
tictac.run = False
#board.mark_squares(row, col, 1) #in a position row col player number 1
#print(tictac.board.squares) #testing the board, returns information to console
if tictac.gamemode == 'computer' and tictac.player == comp.player and tictac.run:
#update the screen
pygame.display.update()
#computer methods
row, col = comp.evaluation(board)
#board.mark_squares(row, col, comp.player)
tictac.make_move(row, col)
if tictac.isover():
tictac.run = False
pygame.display.update() #updating the screen
#minimax alg =
""" terminal case and the base case - terminal case is when the game is over
3 situations: 1) player 1 wins, 2) player 2 wins, 3) draw //terminal cases
if __name__ == "__main__":
main()
In my pygame-code, I have a drone that is supposed to follow a flight path.
I used pygame.draw.lines to draw lines between specified points. Now, I have a flight path with 10 points where after each point the path angle changes (a bit like a zigzag). The player can move the drone by pressing the keys.
My goal is to print a warning once the drone deviates from the path, e.g. by +/-30. I have been racking my brain for two days but can't come up with a condition to detect a deviation. I just can't figure out how to approach this.
I can determine the drone's x-coordinate at any time but how do I determine the offset from the path? I have attached an image to visualize my problem.
Edit:
As I am a beginner my code is a mess but when copy-pasting it, I guess only the lines 35-91 are interesting. Thank you for any kind of advice in advance!!
import pygame
import pygame.gfxdraw
import random
import sys
import math
pygame.init()
# Define some colors
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
red_transp = (255,0,0, 150)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
X = 0
Y = 250
#Display
display_width, display_height = 1200, 700
h_width, h_height = display_width/2, display_height/2
gameDisplay = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Game Display')
#Drone Sprite Image Load Function
droneImg_interim = pygame.image.load('drone.png')
droneImg = pygame.transform.scale(droneImg_interim, [50,50])
drone_width, drone_height = droneImg.get_rect().size
#Create 11 Waypoints with the same coordinates
p1=[X, Y]
p2=[X, Y]
p3=[X, Y]
p4=[X, Y]
p5=[X, Y]
p6=[X, Y]
p7=[X, Y]
p8=[X, Y]
p9=[X, Y]
p10=[X, Y]
p11=[X, Y]
pointlist = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11]
x_min=drone_width
x_max=100
#Setting new x-coordinate for each point
for i in pointlist:
i[0] = random.randrange(x_min, x_max)
x_min+=250
x_max+=250
#Setting new y-coordinate for each point
for i in range(len(pointlist)):
if i == 0:
pointlist[i][1] = random.randrange(200, 400)
else:
prev = pointlist[i-1][1]
pointlist[i][1] = random.randrange(200, prev+100)
#Plotting pointlist on gameDisplay and connecting dots
def flightpath(pointlist):
pygame.draw.lines(gameDisplay, (255, 0, 0), False, pointlist, 2)
def margin(x):
for i in range(len(pointlist)-1):
p1_x = pointlist[i][0]
p2_x = pointlist[i+1][0]
p1_y = pointlist[i][1]
p2_y = pointlist[i+1][1]
distance_x = p2_x - p1_x
distance = math.sqrt((p2_x-p1_x)**2+(p2_y-p1_y)**2)
halfwaypoint_x = math.sqrt((p2_x - p1_x)**2)/2 + p1_x
halfwaypoint_y = math.sqrt((p2_y - p1_y)**2)/2 + p1_y
if p2_y < p1_y:
angle_rad = math.acos(distance_x/distance)
elif p2_y > p1_y:
angle_rad = 0 - math.acos(distance_x/distance)
angle_deg = math.degrees(angle_rad)
rect_width = distance
rect_height = 60
"""
This part of the code is meant for displaying the margins (the rectangles) around the flight path on the display.
marginSize = (rect_width, rect_height)
surface = pygame.Surface(marginSize, pygame.SRCALPHA)
surface.fill((255,0,0,25))
rotated_surface = pygame.transform.rotate(surface, angle_deg)
#new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((pointlist[i][0], pointlist[i][1]))).center)
new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((halfwaypoint_x, halfwaypoint_y))).center)
#gameDisplay.blit(rotated_surface, new_rect)
"""
#Placing drone on the screen
def drone(x,y):
rect = droneImg.get_rect ()
rect.center=(x, y)
gameDisplay.blit(droneImg,rect)
def displayMSG(value,ttext,posx,posy):
myFont = pygame.font.SysFont("Verdana", 12)
Label = myFont.render(ttext, 1, black)
Value = myFont.render(str(value), 1, black)
gameDisplay.blit(Label, (posx, posy))
gameDisplay.blit(Value, (posx + 100, posy))
#Main Loop Object
def game_loop():
global X, Y, FThrustX, FThrustY, FDragY, Time
FThrustY = 0
gameExit = False
while not gameExit:
#Event Checker (Keyboard, Mouse, etc.)
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_ESCAPE:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed() #checking pressed keys
if keys[pygame.K_LEFT]:
X -= 1
if keys[pygame.K_RIGHT]:
X +=1
if keys[pygame.K_DOWN]:
Y += 1
if keys[pygame.K_UP]:
Y -=1
#Display Background Fill
gameDisplay.fill(white)
#Plot flightpath
flightpath(pointlist)
#YS: Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
displayMSG(current_pos_x,'x:',20,665)
displayMSG(current_pos_y,'y:',20,680)
#Plot margin
margin(5)
#Move Drone Object
drone(X,Y)
#Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
#No exceeding of display edge
if X > display_width - drone_width: X = display_width - drone_width
if Y > display_height - drone_height: Y = display_height - drone_height
if X < drone_width: X = drone_width
if Y < drone_height: Y = drone_height
pygame.display.update()
#MAIN
game_loop()
pygame.quit()
sys.exit()
One approach is to find the minimum distance between the center of the drone and the line.
Write the function that calculates the minimum distance from a point to a line segment. To do this, use pygame.math.Vector2 and the Dot product:
def distance_point_linesegment(pt, l1, l2):
LV = pygame.math.Vector2(l2[0] - l1[0], l2[1] - l1[1])
PV = pygame.math.Vector2(pt[0] - l1[0], pt[1]- l1[1])
dotLP = LV.dot(PV)
if dotLP < 0:
return PV.length()
if dotLP > LV.length_squared():
return pygame.math.Vector2(pt[0] - l2[0], pt[1]- l2[1]).length()
NV = pygame.math.Vector2(l1[1] - l2[1], l2[0] - l1[0])
return abs(NV.normalize().dot(PV))
Find the line segment with the shortest distance in a loop:
def minimum_distance(pt, pointlist):
min_dist = -1
for i in range(len(pointlist)-1):
dist = distance_point_linesegment(pt, pointlist[i], pointlist[i+1])
if i == 0 or dist < min_dist:
min_dist = dist
return min_dist
Create an alert when the distance exceeds a certain threshold:
def game_loop():
# [...]
while not gameExit:
# [...]
dist_to_path = minimum_distance((X, Y), pointlist)
if dist_to_path > 25:
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
Another possible solution is to use pygame.Rect.clipline and detect the collision of the line segments and the rectangle surrounding the drone:
def intersect_rect(rect, pointlist):
for i in range(len(pointlist)-1):
if rect.clipline(pointlist[i], pointlist[i+1]):
return True
return False
def game_loop():
# [...]
while not gameExit:
# [...]
if not intersect_rect(droneImg.get_rect(center = (X, Y)), pointlist):
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
The interesting part of the question is of course finding the nearest point on the desired path to the actual position; distance is easy. The hard part of that is in turn identifying the nearest element (line segment) of the path; projecting onto it is also straightforward.
If the path is simple enough (in particular, if it doesn’t branch and it’s impossible/disallowed to skip sections at a self-intersection), you can finesse that part by just maintaining that current element in a variable and updating it to the previous or next element when the projection onto one of them is closer than the projection onto the current one. This is a typical algorithm used by racing games to determine the instantaneous order of racers.
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.)
So my question is simple: How do I make a sprite that my mouse can't pass through? I've been experimenting, and I found an unreliable way to do it that is also super glitchy. If anyone knows how I might go about this, please help.
Here is the code that I am currently using:
import pygame
import pyautogui
import sys
import time
pygame.init()
game_display = pygame.display.set_mode((800,600))
pygame.mouse.set_visible(True)
pygame.event.set_grab(True)
exit = False
class Wall(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((30, 100))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
self.rect.center = (200, 200)
def collision(self):
loc = pygame.mouse.get_pos()
yy = loc[1]
xx = loc[0]
if yy >= self.rect.top and yy <= self.rect.bottom and xx >= self.rect.left and xx <= self.rect.right:
if xx >= 200:
pyautogui.move(216 - xx, 0)
if xx <= 200:
pyautogui.move(-xx + 184, 0)
w = Wall()
all_sprites = pygame.sprite.Group()
all_sprites.add(w)
print(w.rect.top)
print(w.rect.bottom)
while (not exit):
mouse_move = (0,0)
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
exit = True
w.collision()
clock = pygame.time.Clock()
game_display.fill((0, 0, 0))
clock.tick(30)
all_sprites.update()
all_sprites.draw(game_display)
pygame.display.flip()
pygame.quit()
note: please ignore my extra import statements, I am going to use them for later.
To do what you want you have to check if the line form the previous mouse position to the new mouse position intersects the rectangle. Write a function IntersectLineRec which checks for the intersection and use it and returns a list of intersection points, sorted by the distance.
The function returns a list of tules whith points and distances:
e.g.
[((215.0, 177.0), 12.0), ((185.0, 177.0), 42.0)]
prev_loc = pygame.mouse.get_pos()
class Wall(pygame.sprite.Sprite):
# [...]
def collision(self):
global prev_loc
loc = pygame.mouse.get_pos()
intersect = IntersectLineRec(prev_loc, loc, self.rect)
prev_loc = loc
if intersect:
ip = [*intersect[0][0]]
for i in range(2):
tp = self.rect.center[i] if ip[i] == loc[i] else loc[i]
ip[i] += -3 if ip[i] < tp else 3
pyautogui.move(ip[0]-loc[0], ip[1]-loc[1])
prev_loc = loc = ip
The function IntersectLineRec has to check if one of the 4 outer lines between the 4 corners of the rectangle inter sects the line between the mouse positions:
def IntersectLineRec(p1, p2, rect):
iL = [
IntersectLineLine(p1, p2, rect.bottomleft, rect.bottomright),
IntersectLineLine(p1, p2, rect.bottomright, rect.topright),
IntersectLineLine(p1, p2, rect.topright, rect.topleft),
IntersectLineLine(p1, p2, rect.topleft, rect.bottomleft) ]
iDist = [(i[1], pygame.math.Vector2(i[1][0] - p1[0], i[1][1] - p1[1]).length()) for i in iL if i[0]]
iDist.sort(key=lambda t: t[1])
return iDist
IntersectLineRec checks if to endless lines, which are defined by to points are intersecting. Then it checks if the intersection point is in the rectangles which are defined by the each of the lines (the line is the diagonal of the rectangle):
def IntersectLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
isect, xPt = IntersectEndlessLineLine(l1_p1, l1_p2, l2_p1, l2_p2)
isect = isect and PtInRect(xPt, l1_p1, l1_p2) and PtInRect(xPt, l2_p1, l2_p2)
return isect, xPt
To check if a point is in an axis aligned rectangle has to check if both coordinates of the point are in the range of the coordinates of the rectangle:
def InRange(coord, range_s, range_e):
if range_s < range_e:
return coord >= range_s and coord <= range_e
return coord >= range_e and coord <= range_s
def PtInRect(pt, lp1, lp2):
return InRange(pt[0], lp1[0], lp2[0]) and InRange(pt[1], lp1[1], lp2[1])
The intersection of to endless lines can be calculated like this:
def IntersectEndlessLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
# calculate the line vectors and test if both lengths are > 0
P = pygame.math.Vector2(*l1_p1)
Q = pygame.math.Vector2(*l2_p1)
line1 = pygame.math.Vector2(*l1_p2) - P
line2 = pygame.math.Vector2(*l2_p2) - Q
if line1.length() == 0 or line2.length() == 0:
return (False, (0, 0))
# check if the lines are not parallel
R, S = (line1.normalize(), line2.normalize())
dot_R_nvS = R.dot(pygame.math.Vector2(S[1], -S[0]))
if abs(dot_R_nvS) < 0.001:
return (False, (0, 0))
# calculate the intersection point of the lines
# t = dot(Q-P, (S.y, -S.x)) / dot(R, (S.y, -S.x))
# X = P + R * t
ptVec = Q-P
t = ptVec.dot(pygame.math.Vector2(S[1], -S[0])) / dot_R_nvS
xPt = P + R * t
return (True, (xPt[0], xPt[1]))
See the animation:
As per topic, below is my code:
import pygame #provides Pygame framework.
import sys #provides sys.exit function
from pygame import *
from pygame.locals import * # Import constants used by PyGame
# game classes
class OAndX:
def __init__(self):
# Initialize Pygame
pygame.init()
# Create the clock to manage the game loop
self.clock = time.Clock()
display.set_caption("Noughts and Crosses")
# Create a windows with a resolution of 640 x 480
self.displaySize=(640,480)
self.screen=display.set_mode(self.displaySize)
# will either be 0 or X
self.player="0"
# The background class
class Background:
def __init__(self,displaySize):
self.image=Surface(displaySize)
# Draw a title
# Create the font we'll use
self.font=font.Font(None,(displaySize[0]/12))
self.text = self.font.render("Noughts and crosses",True,(Color("White")))
# Work out where to place the text
self.textRect = self.text.get_rect()
self.textRect.centerx=displaySize[0] / 2
# Add a little margin
self.textRect.top = displaySize[1] * 0.02
# Blit the text to the background image
self.image.blit(self.text, self.textRect)
def draw(self, display):
display.blit(self.image, (0, 0))
# A class for an individual grid square
class GridSquare(sprite.Sprite):
def __init__(self, position, gridSize):
# Initialise the sprite base class
super(GridSquare, self).__init__()
# We want to know which row and column we are in
self.position = position
# State can be “X”, “O” or “”
self.state = ""
self.permanentState = False
self.newState = ""
# Work out the position and size of the square
width = gridSize[0] / 3
height = gridSize[1] / 3
# Get the x and y co ordinate of the top left corner
x = (position[0] * width) - width
y = (position[1] * height) - height
# Create the image, the rect and then position the rect
self.image = Surface((width,height))
self.image.fill(Color("white"))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
# The rect we have is white, which is the parent rect
# We will draw another rect in the middle so that we have
# a white border but a blue center
self.childImage = Surface(((self.rect.w * 0.9), (self.rect.h * 0.9)))
self.childImage.fill(Color("blue"))
self.childRect = self.childImage.get_rect()
self.childRect.center = ((width /2), (height / 2))
self.image.blit(self.childImage,self.childRect)
# Create the font we’ll use to display O and X
self.font = font.Font(None, (self.childRect.w))
class Grid:
def __init__(self, displaySize):
self.image=Surface(displaySize)
# Make a number of grid squares
gridSize = (displaySize[0] * 0.75,displaySize[1] * 0.75)
# Work out the co-ordinate of the top left corner of the grid so that it can be centered on the screen
self.position = ((displaySize[0] /2) - (gridSize[0] / 2),(displaySize[1] / 2) - (gridSize[1] / 2))
# An empty array to hold our grid squares in
self.squares = []
for row in range(1,4):
# Loop to make 3 rows
for column in range(1,4):
# Loop to make 3 columns
squarePosition = (column,row)
self.squares.append(GridSquare(squarePosition, gridSize))
# Get the squares in a sprite group
self.sprites = sprite.Group()
for square in self.squares:
self.sprites.add(square)
def draw(self, display):
self.sprites.update()
self.sprites.draw(self.image)
display.blit(self.image, self.position)
def update(self):
# Need to update if we need to set a new state
if (self.state != self.newState):
# Refill the childImage blue
self.childImage.fill(Color("blue"))
text = self.font.render(self.newState, True, (Color("white")))
textRect = text.get_rect()
textRect.center = ((self.childRect.w / 2),(self.childRect.h / 2))
# We need to blit twice because the new child image needs to be blitted to the parent image
self.childImage.blit(text,textRect)
self.image.blit(self.childImage, self.childRect)
# Reset the newState variable
self.state = self.newState
self.newState = ""
def setState(self, newState,permanent=False):
if not self.permanentState:
self.newState = newState
if permanent:
self.permanentState = True
def reset(self):
# Create an instance of our background and grid class
self.background =Background(self.displaySize)
self.grid = Grid(self.displaySize)
def getSquareState(self, column, row):
# Get the square with the requested position
for square in self.squares:
if square.position == (column,row):
return square.state
def full(self):
# Finds out if the grid is full
count = 0
for square in self.squares:
if square.permanentState ==True:
count += 1
if count == 9:
return True
else:
return False
def getWinner(self):
players = ["X", "O"]
for player in players:
# check horizontal spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState((column + 1),row)
square3 = self.grid.getSquareState((column + 2), row)
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# check vertical spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState(column, (row + 1))
square3 = self.grid.getSquareState(column, (row + 2))
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# check forwards diagonal spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState((column + 1), (row - 1))
square3 = self.grid.getSquareState((column + 2), (row - 2))
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# check backwards diagonal spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState((column + 1), (row + 1))
square3 = self.grid.getSquareState((column + 2), (row + 2))
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# Check if grid is full if someone hasn’t won already
if self.grid.full():
return "draw"
def winMessage(self, winner):
# Display message then reset the game to its initial state
# Blank out the screen
self.screen.fill(Color("Black"))
# Create the font we’ll use
textFont = font.Font(None, (self.displaySize[0] / 6))
textString = ""
if winner == "draw":
textString = "It was a draw!"
else:
textString = winner + " Wins!"
# Create the text surface
text = textFont.render(textString,True,(Color("white")))
textRect = text.get_rect()
textRect.centerx = self.displaySize[0] / 2
textRect.centery = self.displaySize[1] / 2
# Blit changes and update the display before we sleep
self.screen.blit(text, textRect)
display.update()
# time.wait comes from pygame libs
time.wait(2000)
# Set game to its initial state
self.reset()
def run(self):
while True:
# Our Game loop,Handle events
self.handleEvents()
# Draw our background and grid
self.background.draw(self. screen)
self.grid.draw(self.screen)
# Update our display
display.update()
# Limit the game to 10 fps
self.clock.tick(10)
def handleEvents(self):
# We need to know if the mouse has been clicked later on
mouseClicked = False
# Handle events, starting with quit
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONUP:
mouseClicked = True
# Get the co ordinate of the mouse mousex, mousey = mouse.get_pos() ,These are relative to the top left of the screen,and we need to make them relative to the top left of the grid
mousex -= self.grid.position[0]
mousey -= self.grid.position[1]
# Find which rect the mouse is in
for square in self.grid.squares:
if square.rect.collidepoint((mousex, mousey)):
if mouseClicked:
square.setState(self.player, True)
# Change to next player
if self.player == "O":
self.player = "X"
else:
self.player = "O"
# Check for a winner
winner = self.getWinner()
if winner:
self.winMessage(winner)
else:
square.setState(self.player)
else:
# Set it to blank, only if
permanentState == False
square.setState("")
if __name__ == "__main__":
game = OAndX()
game.run()
I'm new to python language but I need to understand why caused to error "AttributeError: 'OAndX' object has no attribute 'run'" when I using python3 interpreter to run it.Appreciate thankful to anyone could help.
You should actually place your run method under you class OAndX method as follows:
class OAndX:
def __init__(self):
# Initialize Pygame
pygame.init()
# Create the clock to manage the game loop
self.clock = time.Clock()
display.set_caption("Noughts and Crosses")
# Create a windows with a resolution of 640 x 480
self.displaySize=(640,480)
self.screen=display.set_mode(self.displaySize)
# will either be 0 or X
self.player="0"
def run(self):
while True:
# Our Game loop,Handle events
self.handleEvents()
# Draw our background and grid
self.background.draw(self. screen)
self.grid.draw(self.screen)
# Update our display
display.update()
# Limit the game to 10 fps
self.clock.tick(10)