I'm running this python code and having a problem with the accel function. The rotate method works fine when left and right are pressed however when up is pressed nothing happens. I've stepped through the code in a debugger and the my_ship.accel line is executed but it doesn't go to method body, it just continues as if that line isn't there. Idk what's wrong please help. Also my_ship is the name of a Ship object and it is defined properly lower in my code.
import simplegui
WIDTH = 800
HEIGHT = 600
class ImageInfo:
def __init__(self, center, size, radius = 0, lifespan = None, animated = False):
self.center = center
self.size = size
self.radius = radius
if lifespan:
self.lifespan = lifespan
else:
self.lifespan = float('inf')
self.animated = animated
def get_center(self):
return self.center
def get_size(self):
return self.size
def get_radius(self):
return self.radius
def get_lifespan(self):
return self.lifespan
def get_animated(self):
return self.animated
def change_center(self, new_center):
self.center = new_center
# ship image
ship_info = ImageInfo([45, 45], [90, 90], 35)
ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png")
class Ship:
def __init__(self, pos, vel, angle, image, info):
self.pos = [pos[0],pos[1]]
self.vel = [vel[0],vel[1]]
self.thrust = False
self.angle = angle
self.angle_vel = 0
self.image = image
self.image_center = info.get_center()
self.image_size = info.get_size()
self.radius = info.get_radius()
self.info = info
self.accel = 10
self.angle_accel = .1
def draw(self,canvas):
if not self.thrust:
self.info.change_center(ship_center)
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle)
else:
self.info.change_center(thrust_ship_center)
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle)
def update(self):
self.pos[0] += self.vel[0]
self.pos[1] += self.vel[1]
self.angle += self.angle_vel
def accel(self):
self.thrust = True
self.vel[0] += self.accel
self.vel[1] += self.accel
def rotate(self, direction):
if direction == "left":
self.angle_vel -= self.angle_accel
elif direction == "right":
self.angle_vel += self.angle_accel
else:
print "error"
def keydown_handler(key):
if key == simplegui.KEY_MAP['left']:
my_ship.rotate("left")
elif key == simplegui.KEY_MAP['right']:
my_ship.rotate("right")
elif key == simplegui.KEY_MAP['up']:
my_ship.accel
elif key == simplegui.KEY_MAP['space']:
self.angle_vel += self.angle_accel
def keyup_handler(key):
if key == simplegui.KEY_MAP['left']:
my_ship.rotate("right")
elif key == simplegui.KEY_MAP['right']:
my_ship.rotate("left")
my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], 1, ship_image, ship_info)
This:
my_ship.accel
Doesn't call the method my_ship.accel, any more than 2 calls the number 2. To call something in Python, you need parentheses. So:
my_ship.accel()
(If you're wondering why Python does it this way when other languages, like Ruby, don't… well, this means that you can use the method object my_ship.accel as a value—store it to call later, pass it to map, etc.)
But you've got another problem on top of that.
You define a method accel on Ship objects. But you also assign an integer value 10 to self.accel on Ship objects. There's no way self.accel can mean two different things at once, both the method and the number. So, which one "wins"? In this case, the self.accel = 10 happens at the time you constructed your Ship, which is later, so it wins.
So, when you write my_ship.accel, you're just referring to the number 10. And when you write my_ship.accel(), you're trying to call the number 10 as if it were a function. Hence the TypeError.
The solution is to not reuse the same name for two different things. Often, naming functions after verbs and attributes after nouns is a good way to avoid this problem—although you also have to avoid gratuitous abbreviations, because otherwise you're probably going to abbreviate acceleration and accelerate to the same accel, as you did here.
Related
I'm currently working on building the game Pong. One aspect of the game is displaying the current level. What I want to happen is have 3 levels. When the score is equal to 5, I want to display "Level 2". When the score is equal to 10, I want to display "Level 3" and then at 15 I want to display "You Win!" What's happening right now is as soon as the score is equal to 5, the Level number counts up every time it updates the window (super fast). Where or how do I write this so that it functions the way I want?
I created a function in the Pong class called level_up for this.
import arcade
import random
# These are Global constants to use throughout the game
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 300
BALL_RADIUS = 10
PADDLE_WIDTH = 10
PADDLE_HEIGHT = 50
MOVE_AMOUNT = 5
SCORE_HIT = 1
SCORE_MISS = 5
LEVEL_UP = 1
"""Point will identify the x and y coordinates of the ball"""
class Point:
def __init__(self):
"""The __init__ initializes the x and y coordinates"""
self.x=float(0)
self.y=float(0)
"""Velocity will identify the velocity"""
class Velocity:
def __init__(self):
"""The __init__ initializes the x and y velocity"""
self.dx=float(0)
self.dy=float(0)
"""Ball will identify the coordinates and the movement of the ball"""
class Ball:
def __init__(self):
"""The __init__ will initialize the Point and Velocity class values"""
self.center=Point()
#self.center will call the self.x and self.y float values that are found in the Point class.
self.velocity=Velocity()
#self.velocity will call the self.dx and self.dy values that are found in the Velocity class.
self.velocity.dx=2
self.velocity.dy=2
def draw(self):
"""This creates the ball"""
arcade.draw_circle_filled(self.center.x, self.center.y,
BALL_RADIUS, arcade.color.FLUORESCENT_YELLOW)
def advance(self):
self.center.x += self.velocity.dx
self.center.y += self.velocity.dy
def bounce_horizontal(self):
self.velocity.dx *=-1
def bounce_vertical(self):
self.velocity.dy *=-1
def restart(self):
self.center.x=0
self.center.y=random.uniform(0,SCREEN_HEIGHT)
self.velocity.dx=random.uniform(1,8)
self.velocity.dy=random.uniform(0,8)
"""Paddle will represent the paddle"""
class Paddle:
def __init__(self):
"""The __init__ will initialize the location of the paddle"""
self.center=Point()
#self.center calls the Point class
self.center.x=SCREEN_WIDTH
self.center.y=SCREEN_HEIGHT//2
def draw(self):
arcade.draw_rectangle_filled(self.center.x, self.center.y,
PADDLE_WIDTH, PADDLE_HEIGHT, arcade.color.FLUORESCENT_PINK)
def move_up(self):
self.center.y+=MOVE_AMOUNT
if self.center.y > SCREEN_HEIGHT:
self.center.y -= MOVE_AMOUNT
def move_down(self):
self.center.y-=MOVE_AMOUNT
if self.center.y < 0:
self.center.y += MOVE_AMOUNT
class Pong(arcade.Window):
"""
This class handles all the game callbacks and interaction
It assumes the following classes exist:
Point
Velocity
Ball
Paddle
This class will then call the appropriate functions of
each of the above classes.
You are welcome to modify anything in this class,
but should not have to if you don't want to.
"""
def __init__(self, width, height):
"""
Sets up the initial conditions of the game
:param width: Screen width
:param height: Screen height
"""
super().__init__(width, height)
self.ball = Ball()
self.paddle = Paddle()
self.score = 0
self.level = 1
# These are used to see if the user is
# holding down the arrow keys
self.holding_left = False
self.holding_right = False
arcade.set_background_color(arcade.color.BLACK)
def on_draw(self):
"""
Called automatically by the arcade framework.
Handles the responsiblity of drawing all elements.
"""
# clear the screen to begin drawing
arcade.start_render()
# draw each object
self.ball.draw()
self.paddle.draw()
self.draw_score()
self.draw_level()
def draw_score(self):
"""
Puts the current score on the screen
"""
score_text = "Score: {}".format(self.score)
start_x = 190
start_y = SCREEN_HEIGHT - 20
arcade.draw_text(score_text, start_x=start_x, start_y=start_y, font_size=12,
color=arcade.color.DEEP_SKY_BLUE)
def draw_level(self):
"""Displays the level"""
level_text = f"LEVEL {self.level}"
start_x= 175
start_y=SCREEN_HEIGHT - 40
arcade.draw_text(level_text, start_x=start_x, start_y=start_y, font_size=20,
color=arcade.color.ELECTRIC_GREEN)
def update(self, delta_time):
"""
Update each object in the game.
:param delta_time: tells us how much time has actually elapsed
"""
# Move the ball forward one element in time
self.ball.advance()
# Check to see if keys are being held, and then
# take appropriate action
self.check_keys()
# check for ball at important places
self.check_miss()
self.check_hit()
self.check_bounce()
def check_hit(self):
"""
Checks to see if the ball has hit the paddle
and if so, calls its bounce method.
:return:
"""
too_close_x = (PADDLE_WIDTH / 2) + BALL_RADIUS
too_close_y = (PADDLE_HEIGHT / 2) + BALL_RADIUS
if (abs(self.ball.center.x - self.paddle.center.x) < too_close_x and
abs(self.ball.center.y - self.paddle.center.y) < too_close_y and
self.ball.velocity.dx > 0):
# we are too close and moving right, this is a hit!
self.ball.bounce_horizontal()
self.score += SCORE_HIT
def level_up(self):
if self.score ==5:
self.level += LEVEL_UP
if self.score ==10:
self.level += LEVEL_UP
def check_miss(self):
"""
Checks to see if the ball went past the paddle
and if so, restarts it.
"""
if self.ball.center.x > SCREEN_WIDTH:
# We missed!
self.score -= SCORE_MISS
self.ball.restart()
def check_bounce(self):
"""
Checks to see if the ball has hit the borders
of the screen and if so, calls its bounce methods.
"""
if self.ball.center.x < 0 and self.ball.velocity.dx < 0:
self.ball.bounce_horizontal()
if self.ball.center.y < 0 and self.ball.velocity.dy < 0:
self.ball.bounce_vertical()
if self.ball.center.y > SCREEN_HEIGHT and self.ball.velocity.dy > 0:
self.ball.bounce_vertical()
def check_keys(self):
"""
Checks to see if the user is holding down an
arrow key, and if so, takes appropriate action.
"""
if self.holding_left:
self.paddle.move_down()
if self.holding_right:
self.paddle.move_up()
def on_key_press(self, key, key_modifiers):
"""
Called when a key is pressed. Sets the state of
holding an arrow key.
:param key: The key that was pressed
:param key_modifiers: Things like shift, ctrl, etc
"""
if key == arcade.key.LEFT or key == arcade.key.DOWN:
self.holding_left = True
if key == arcade.key.RIGHT or key == arcade.key.UP:
self.holding_right = True
def on_key_release(self, key, key_modifiers):
"""
Called when a key is released. Sets the state of
the arrow key as being not held anymore.
:param key: The key that was pressed
:param key_modifiers: Things like shift, ctrl, etc
"""
if key == arcade.key.LEFT or key == arcade.key.DOWN:
self.holding_left = False
if key == arcade.key.RIGHT or key == arcade.key.UP:
self.holding_right = False
# Creates the game and starts it going
window = Pong(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()
I just answered my own question, I changed the level_up function to say:
def level_up(self):
if self.score ==5 and self.level==1:
self.level += LEVEL_UP
if self.score ==10 and self.level==2:
self.level += LEVEL_UP
This question already has an answer here:
How do I get the snake to grow and chain the movement of the snake's body?
(1 answer)
Closed 2 years ago.
I'm a fairly newe programmer and this is the first time I develop a game and I wanted to start with something pretty simple, so I chose the snake game. I have coded everything apart from adding the body part when the food is eaten.
import random
import pygame
from pygame import *
import sys
import os
import time
###objects
class snake:
def __init__(self, win):
self.score = 1
self.length = 25
self.width = 25
self.win = win
self.r = random.randint(0,500)
self.vel = 25
self.update = pygame.display.update()
self.right = True
self.left = False
self.up = False
self.down = False
# 0 = right 1 = left 2 = up 3 = down
self.can = [True, False, True, True]
self.keys = pygame.key.get_pressed()
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.x = self.r
self.y = self.r
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
def move(self, win):
win.fill((0,0,0))
self.keys = pygame.key.get_pressed()
if self.right == True:
self.x += self.vel
if self.left == True:
self.x -= self.vel
if self.up == True:
self.y -= self.vel
if self.down == True:
self.y += self.vel
if self.x > 475:
self.x = 0
if self.x < 0:
self.x = 500
if self.y > 475:
self.y = 0
if self.y < 0:
self.y = 500
if self.keys[pygame.K_RIGHT] and self.can[0] == True:
self.right = True
self.left= False
self.up = False
self.down = False
self.can[1] = False
self.can[0] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_LEFT] and self.can[1] == True:
self.right = False
self.left = True
self.up = False
self.down = False
self.can[0] = False
self.can[1] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_UP] and self.can[2] == True:
self.right = False
self.left = False
self.up = True
self.down = False
self.can[3] = False
self.can[0] = True
self.can[1] = True
self.can[2] = True
if self.keys[pygame.K_DOWN] and self.can[3] == True:
self.right = False
self.left = False
self.up = False
self.down = True
self.can[2] = False
self.can[0] = True
self.can[1] = True
self.can[3] = True
self.length = 25 * self.score
self.snake = pygame.draw.rect(self.win, (0,255,0), (self.x, self.y, self.length, self.width))
def food(self, win):
pygame.draw.rect(self.win, (255,0,0), (self.a, self.b,25,25))
if self.a == self.x and self.b == self.y:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
self.score += 1
###functions
###main game
##variables
screen = (500,500)
W = 25
L = 25
WHITE = 255,255,255
clock = pygame.time.Clock()
##game
pygame.init()
win = pygame.display.set_mode(screen)
title = pygame.display.set_caption("snake game")
update = pygame.display.update()
snake = snake(win)
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
snake.move(win)
snake.food(win)
pygame.display.update()
pygame.quit()
I know the code is a bit messy because I wanted to try to implement OOP, since I never used it.
This is also my first time using pygame, so I be doing something wrong.
So far I have made it so that the snake and food spawn in a random location in an invible grid, and when the head of the snake has the same coordinates of the food, the snake becomes longer (I'm just adding 25 pixels to the snake's body, but when it turns, the whole rectangular shaped snake turns). Also, if the snake reaches the edge of the display, the appears from the opposite side.
The comments below might sound harsh, and I've tried to write them in a neutral way simply pointing out facts and state them as they are. If you are truly a new programmer, this is a pretty good project to learn from and you've done quite good to come this far. So keep an mind that these comments are not meant to be mean, but objective and always comes with a proposed solution to make you an even better programmer, not to bash you.
I also won't go into detail in the whole list as a body thing, others have covered it but I'll use it also in this code.
Here's the result, and blow is the code and a bunch of pointers and tips.
Never re-use variables
First of all, never re-use variable names, as you've overwritten and got lucky with snake = snake() which replaces the whole snake class, and can thus never be re-used again, defeating the whole purpose of OOP and classes. But since you only use it once, it accidentally worked out ok this time. Just keep that in mind for future projects.
Single letter variables
Secondly, I would strongly avoid using single-letter variables unless you really know what you're doing and often that's tied to a math equation or something. I'm quite allergic to the whole concept of self.a and self.b as they don't say anything meaningful, and in a few iterations you probably won't have an idea of what they do either. This is common tho when you're moving quickly and you currently have a grasp on your code - but will bite you in the ass sooner or later (will/should give you bad grades in school or won't land you that dream job you're applying for).
Never mix logic in one function
You've also bundled the food into the player object, which is a big no-no. As well as render logic in the movement logic. So I propose a re-work in the shape of even more OOP where food and player are two separate entities and a function for each logical operation (render, move, eat, etc..).
So I restructured it into this logic:
While I'm at it, I also re-worked the movement mechanics a bit, to use less lines and logic to produce the same thing. I also removed all this logic:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
And replaced it with this, which does the exact same thing, but uses built-ins to produce it. And hopefully the functions/variables are more descriptive than a rogue while loop.
self.r = random.choice(range(0, 500, 25))
And the final result would look something like this:
import random
import pygame
from pygame import *
import sys
import os
import time
# Constants (Used for bitwise operations - https://www.tutorialspoint.com/python/bitwise_operators_example.htm)
UP = 0b0001
DOWN = 0b0010
LEFT = 0b0100
RIGHT = 0b1000
###objects
class Food:
def __init__(self, window, x=None, y=None):
self.window = window
self.width = 25
self.height = 25
self.x, self.y = x, y
if not x or not y: self.new_position()
def draw(self):
pygame.draw.rect(self.window, (255,0,0), (self.x, self.y, 25, 25))
def new_position(self):
self.x, self.y = random.choice(range(0, 500, 25)), random.choice(range(0, 500, 25))
class Snake:
def __init__(self, window):
self.width = 25
self.width = 25
self.height = 25
self.window = window
self.vel = 25
self.update = pygame.display.update()
start_position = random.choice(range(0, 500, 25)), random.choice(range(0, 500, 25))
self.body = [start_position]
self.direction = RIGHT
def move(self, window):
self.keys = pygame.key.get_pressed()
# since key-presses are always 1 or 0, we can multiply each key with their respective value from the
# static map above, LEFT = 4 in binary, so if we multiply 4*1|0 we'll get binary 0100 if it's pressed.
# We can always safely combine 1, 2, 4 and 8 as they will never collide and thus always create a truth map of
# which direction in bitwise friendly representation.
if any((self.keys[pygame.K_UP], self.keys[pygame.K_DOWN], self.keys[pygame.K_LEFT], self.keys[pygame.K_RIGHT])):
self.direction = self.keys[pygame.K_UP]*1 + self.keys[pygame.K_DOWN]*2 + self.keys[pygame.K_LEFT]*4 + self.keys[pygame.K_RIGHT]*8
x, y = self.body[0] # Get the head position, which is always the first in the "history" aka body.
self.body.pop() # Remove the last object from history
# Use modolus to "loop around" when you hit 500 (or the max width/height desired)
# as it will wrap around to 0, try for instance 502 % 500 and it should return "2".
if self.direction & UP:
y = (y - self.vel)%500
elif self.direction & DOWN:
y = (y + self.vel)%500
elif self.direction & LEFT:
x = (x - self.vel)%500
elif self.direction & RIGHT:
x = (x + self.vel)%500 # window.width
self.body.insert(0, (x, y))
def eat(self, food):
x, y = self.body[0] # The head
if x >= food.x and x+self.width <= food.x+food.width:
if y >= food.y and y+self.height <= food.y+food.height:
self.body.append(self.body[-1])
return True
return False
def draw(self):
for x, y in self.body:
pygame.draw.rect(self.window, (0,255,0), (x, y, self.width, self.width))
##variables
clock = pygame.time.Clock()
##game
pygame.init()
window = pygame.display.set_mode((500,500))
pygame.display.set_caption("snake game")
snake = Snake(window)
food = Food(window)
food.new_position()
score = 0
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill((0,0,0)) # Move the render logic OUTSIDE of the player object
snake.move(window)
if snake.eat(food):
score += 1
food.new_position()
snake.draw()
food.draw()
pygame.display.update()
pygame.quit()
draw() now handles all rendering logic within the objects themselves, instead of being entangled in the move().
snake.eat() is now a function that returns True or False based on the snake head (first position in history, aka body) being inside a food object. This function also adds to the body if a eat was successful, perhaps this code should be moved outside as well, but it's one line of code so I skipped on my own rule a bit to keep the code simple.
food.new_position() is a function that simply moves the food to a new position, called when eat() was successful for instance, or if you want to randomly move the food around at a given interval.
move() and finally the move function, which only has one purpose now, and that is to move the snake in a certain direction. It does so by first getting the current head position, then remove the last history item (tail moves with the head) and then adds a new position at the front of the body that is equal to velocity.
The "is inside" logic might look like porridge, but it's quite simple, and the logic is this:
If the snakes head body[0] has it's x greater or equal to the foods x, it means the heads lower upper left corner was at least past or equal to the foods upper left corner. If the heads width (x+width) is less or equal to the foods width, we're at least inside on the X axis. And then we just repeat for the Y axis and that will tell you if the head is inside or outside the boundary of the food.
The movement logic is reworked to make it fractionally faster but also less code and hopefully easier to use once you have a understanding of how it works. I switched to something called bitwise operations. The basic concept is that you can on a "machine level" (bits) do quick operations to determinate if something is true or not with AND operations for instance. To do this, you can compare to bit-sequences and see if at any point two 1 overlap each other, if not, it's False. Here's an overview of the logic used and all the possible combinations of UP, DOWN, LEFT and RIGHT in binary representation:
On a bit level, 1 is simply 0001, 2 would be 0010 and 4 being 0100 and finally 8 being 1000. Knowing this, if we press → (right) we want to convert this into the bit representation that is the static variable RIGHT (1000 in binary). To achieve this, we simply multiply the value pygame gives us when a key is pressed, which is 1. We multiply it by the decimal version of 1000 (RIGHT), which is 8.
So if → is pressed we do 8*1. Which gives us 1000. And we simply repeat this process for all the keys. If we pressed ↑ + → it would result in 1001 because 8*1 + 1*1 and since ← and ↓ weren't pressed, they will become 4*0 and 2*0 resulting in two zeroes at binary positions.
We can then use these binary representations by doing the AND operator shown in the picture above, to determinate if a certain direction was pressed or not, as DOWN will only be True if there's a 1 on the DOWN position, being the second number from the right in this case. Any other binary positional number will result in False in the AND comparitor.
This is quite efficient, and once you get the hang of it - it's pretty useful for other things as well. So it's a good time to learn it in a controlled environment where it hopefully makes sense.
The main thing to take away here (other than what other people have already pointed out, keep the tail in a array/list as a sort of history of positions) is that game objects should be individual objects, and main rendering logic shouldn't be in player objects, only player render specifics should be in the player object (as an example).
And actions such as eat() should be a thing rather than being checked inside the function that handles move(), render() and other things.
And my suggestions are just suggestions. I'm not a game developer by trade, just optimizing things where I can. Hope the concepts come to use or spark an idea or two. Best of luck.
Yo have to mange the body of the snake in a list. Add the current position of the the head at the head of the body list and remove an element at the tail of the list in ever frame.
Add an attribute self.body:
class snake:
def __init__(self, win):
# [...]
self.body = [] # list of body elements
Add the current head to the bode before the head is moved:
class snake:
# [...]
def move(self, win):
# [...]
# move snake
self.body.insert(0, (self.x, self.y))
Remove elements a the end of self.body, as long the length of the snake exceeds the score:
class snake:
# [...]
def move(self, win):
# [...]
# remove element at end
while len(self.body) >= self.score:
del self.body[-1]
Draw the bode of the snake in a loop:
class snake:
# [...]
def move(self, win):
# [...]
# draw smake and body
self.snake = pygame.draw.rect(self.win, (0,255,0), (self.x, self.y, 25, self.width))
for pos in self.body:
pygame.draw.rect(self.win, (0,255,0), (pos[0], pos[1], 25, self.width))
class snake:
class snake:
def __init__(self, win):
self.score = 1
self.length = 25
self.width = 25
self.win = win
self.r = random.randint(0,500)
self.vel = 25
self.update = pygame.display.update()
self.right = True
self.left = False
self.up = False
self.down = False
# 0 = right 1 = left 2 = up 3 = down
self.can = [True, False, True, True]
self.keys = pygame.key.get_pressed()
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.x = self.r
self.y = self.r
self.body = [] # list of body elements
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
def move(self, win):
win.fill((0,0,0))
self.keys = pygame.key.get_pressed()
# move snake
self.body.insert(0, (self.x, self.y))
if self.right == True:
self.x += self.vel
if self.left == True:
self.x -= self.vel
if self.up == True:
self.y -= self.vel
if self.down == True:
self.y += self.vel
if self.x > 475:
self.x = 0
if self.x < 0:
self.x = 500
if self.y > 475:
self.y = 0
if self.y < 0:
self.y = 500
# remove element at end
while len(self.body) >= self.score:
del self.body[-1]
if self.keys[pygame.K_RIGHT] and self.can[0] == True:
self.right = True
self.left= False
self.up = False
self.down = False
self.can[1] = False
self.can[0] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_LEFT] and self.can[1] == True:
self.right = False
self.left = True
self.up = False
self.down = False
self.can[0] = False
self.can[1] = True
self.can[2] = True
self.can[3] = True
if self.keys[pygame.K_UP] and self.can[2] == True:
self.right = False
self.left = False
self.up = True
self.down = False
self.can[3] = False
self.can[0] = True
self.can[1] = True
self.can[2] = True
if self.keys[pygame.K_DOWN] and self.can[3] == True:
self.right = False
self.left = False
self.up = False
self.down = True
self.can[2] = False
self.can[0] = True
self.can[1] = True
self.can[3] = True
# draw smake and body
self.snake = pygame.draw.rect(self.win, (0,255,0), (self.x, self.y, 25, self.width))
for pos in self.body:
pygame.draw.rect(self.win, (0,255,0), (pos[0], pos[1], 25, self.width))
def food(self, win):
pygame.draw.rect(self.win, (255,0,0), (self.a, self.b,25,25))
if self.a == self.x and self.b == self.y:
self.r = random.randint(0,500)
while True:
if self.r % 25 == 0:
break
else:
self.r = random.randint(0,500)
continue
self.a = self.r
self.b = self.r
self.score += 1
I would make a body part object and when the snake gets longer you add a body part. The head does the movement and the body parts follow the head.
Each game turn you just move the head then go over all of the body parts starting from the one closest to the head and move them to their parents location. So head moves 1 block, next part moves the previous head location, third part moves to second parts previous location, ...
So I currently have an enemy class that moves left, encounters the end of the screen, then flips direction 180 degrees and walks right. It does this constantly.
I wish to make it randomly change direction, so it's more unpredictable. The issue I'm encountering is when I implement a random number generator for the distance of direction, how would I then inherit it into a variable when I create the instance? Or is there a more efficient way to do this?
Entire enemy class:
class enemy(object):#need to use self to access these
walkRight = [pygame.image.load('Game/R1E.png'),pygame.image.load('Game/R2E.png'),pygame.image.load('Game/R3E.png'),pygame.image.load('Game/R4E.png'),pygame.image.load('Game/R5E.png'),pygame.image.load('Game/R6E.png'),pygame.image.load('Game/R7E.png'),pygame.image.load('Game/R8E.png'),pygame.image.load('Game/R9E.png'),pygame.image.load('Game/R10E.png'),pygame.image.load('Game/R11E.png')]
walkLeft = [pygame.image.load('Game/L1E.png'),pygame.image.load('Game/L2E.png'),pygame.image.load('Game/L3E.png'),pygame.image.load('Game/L4E.png'),pygame.image.load('Game/L5E.png'),pygame.image.load('Game/L6E.png'),pygame.image.load('Game/L7E.png'),pygame.image.load('Game/L8E.png'),pygame.image.load('Game/L9E.png'),pygame.image.load('Game/L10E.png'),pygame.image.load('Game/L11E.png')]
def __init__(self,x,y,width,height,end): #get R3E png
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = [self.x,self.end]
self.walkCount = 0
self.vel = 3
def draw(self,window): #enemy is gonna move from left, to right, left, to right etc between 2 co ordinate points
self.move()
if self.walkCount + 1 >= 33: #if sign is changed, as walkcount would always be less than 33, it wouldn't change
self.walkCount = 0
#rather than using self.left, sel.right, we can use out velocity
if self.vel > 0: #this means we're moving right, integer division 3 so it doesn't look like we going too fast
window.blit(self.walkRight[self.walkCount //3],(self.x,self.y))
self.walkCount += 1
else:
window.blit(self.walkLeft[self.walkCount //3],(self.x,self.y))
self.walkCount += 1 #if not moving right, we're moving left
#check if we're drawing an image to the left or right
def move(self): #move method #to change directions, he needs to change velocity (multiply by -1 etc)
if self.vel > 0:
if self.x < self.path[1] + self.vel:#check if he's about to move past the point on screen, we're accessing the self.end eleme
self.x += self.vel
else: #e.g. if it's greater than, we change direction
self.vel = self.vel * -1 #flipped 180 degrees, so goes other direction
self.x += self.vel
self.walkCount = 0
else: #if our vel is negative
if self.x > self.path[0] - self.vel:
self.x += self.vel #vel is gonna be negative already if we've changed directions
else:
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
Movement function:
def move(self): #move method #to change directions, he needs to change velocity (multiply by -1 etc)
if self.vel > 0:
if self.x < self.path[1] + self.vel:#check if he's about to move past the point on screen, we're accessing the self.end eleme
self.x += self.vel
else: #e.g. if it's greater than, we change direction
self.vel = self.vel * -1 #flipped 180 degrees, so goes other direction
self.x += self.vel
self.walkCount = 0
else: #if our vel is negative
if self.x > self.path[0] - self.vel:
self.x += self.vel #vel is gonna be negative already if we've changed directions
else:
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
Creating the instance just before the main loop(x,y,height,width,walk distance):
goblin = enemy(0,440,64,64,450)
My attempt - still only takes one random number & permanently uses it whilst the game runs.
def timerthing():
pathenemy = random.randint(0,450)
return pathenemy
#now to write main loop which checks for collisions, mouse events etc
#make path randomly change
man = player(200,444,64,66) #creates instance of the player / object
goblin = enemy(0,440,64,64,timerthing())#need to create instance of enemy so he appears on the screen, path ends at timerthing
run = True
bullets = []
while run:
clock.tick(15) #frame rate
timerthing()
Instead of a constant of 450, I'm after a constantly changing variable instead, so his movement is unpredictable. Any other suggestions would be appreciated - thanks.
import random
def timerthing():
pathenemy = random.randint(0,450)
time.sleep(1)
return pathenemy
But I suspect that's not really your problem. It sounds like you have a configuration problem rather than a source problem, and to solve that we'd need an entirely different kind of information from you.
If I had to do code this randomly moving enemy, I would implement two methods first:
One method that returns the possible directions my enemy can go through, given the state of the board. Yet I can't help you with that, since we don't have the "board" part of your code. Let's call this method getPossibleDirections()
One method move, with one argument direction, representing the direction taken by my enemy. Let's call this method move(direction)
Based on your code, I guess you can implement these two methods by yourself. And maybe you want your ennemy to move in the y direction too?
Like #Mark Storer said, you could use the random package, but I will use the random part of the numpy library (already included in python) for my answer:
# you will need to import numpy in the beginning of your file
import numpy as np
class enemy(...)
# your code goes there
def getPossibleDirections(self):
#your implementation
pass
def move(self, direction):
#your implementation
pass
def moveRandomly(self):
possibleDirections = self.getPossibleDirections()
chosenDirection = np.random.randint(0,len(possibleDirections))
# returns one number between 0 and len(possibleDirections) (not included) that can represent your direction
self.move(possibleDirection[chosenDirection])
To move your ennemy randomly, you will just have to call enemy.moveRandomly().
So, I'm writing a snake program using the tkinter Library. The program is globally working but I have a little problem with the inputs' treatment indeed if i give two input too quickly only the last one will be interpret. And i don't really know how to solve this i try to force the update after every player's input but it's clearly not the good solution because it force the snake to move and make it able to teleport so I'm would be glad if someone has an idea to solve this issue. There is my code I'm sure that it could be improved but for now I would like to focus on the first issue.
import tkinter as tk
import numpy.random as rd
class snake:
def __init__(self,n,m):
self.n = n
self.m = m
self.body = [(n//2,m//2),(n//2,m//2-1)]
self.lenght = 2
self.food = (0,0)
self.relocate_food()
self.Game = -2
self.vector = (0,1) #(0,-1) = up, (0,1) = right, (0,1) = down, (-1,0) = left
self.speed = 120
def up(self):
self.vector = (-1,0)
def right(self):
self.vector = (0,1)
def down(self):
self.vector = (1,0)
def left(self):
self.vector = (0,-1)
def relocate_food(self):
x = rd.randint(0,self.n)
y = rd.randint(0,self.m)
i = 0
test = True
while i<self.lenght and test:
if (x,y) == self.body[i]:
test = False
self.relocate_food()
else:
i += 1
if i == self.lenght:
self.food = (x,y)
def collide(self):
head = self.body[0]
for i in range(1,self.lenght):
if head == self.body[i]:
self.Game = -1
break
x,y = head
if x>=self.n or y>=self.m or x<0 or y<0:
self.Game = -1
def eat(self):
head = self.body[0]
if head == self.food:
self.lenght +=1
x0, y0 = self.body[-1]
x1, y1 = self.body[-2]
x = x0 - x1
y = y0 - y1
self.body.append((x0+x,y0+y))
self.relocate_food()
if self.lenght%5 == 0:
self.speed = int(self.speed * 0.90)
def move(self):
dx, dy = self.vector
last_x, last_y = self.body[0]
new_x = last_x + dx
new_y = last_y + dy
self.body[0] = (new_x, new_y)
for k in range(1, self.lenght):
x, y = self.body[k]
self.body[k] = (last_x,last_y)
last_x, last_y = x, y
return
class screen(snake):
def __init__(self,root,n,m):
snake.__init__(self,n,m)
root.minsize(n*20,m*20)
root.maxsize(n*20,m*20)
root.configure(background='white')
self.root = root
self.n = n
self.m = m
self.speed = 130
self.canvas = tk.Canvas(root, width = n*20, height =m*20,bg='black')
self.canvas.bind_all("<Key-Up>",self.move_up)
self.canvas.bind_all("<Key-Down>",self.move_down)
self.canvas.bind_all("<Key-Left>",self.move_left)
self.canvas.bind_all("<Key-Right>",self.move_right)
self.canvas.grid(row=1,column=0)
self.draw_snake()
self.draw_food()
def draw_snake(self):
y,x = self.body[0]
self.canvas.create_rectangle(x*20,y*20,(x+1)*20,(y+1)*20,fill= 'red4')
for k in range(1,self.lenght):
y,x = self.body[k]
self.canvas.create_rectangle(x*20,y*20,(x+1)*20,(y+1)*20,fill= 'red')
def draw_food(self):
y,x =self.food
self.canvas.create_rectangle(x*20,y*20,(x+1)*20,(y+1)*20,fill= 'green')
def move_up(self,event):
if self.Game == -2:
self.Game =0
self.up()
self.update()
else:
self.up()
def move_down(self,event):
if self.Game == -2:
self.Game =0
self.down()
self.update()
else:
self.down()
def move_left(self,event):
if self.Game == -2:
self.Game =0
self.left()
self.update()
else:
self.left()
def move_right(self,event):
if self.Game == -2:
self.Game =0
self.right()
self.update()
else:
self.right()
def update(self):
if self.Game == -2:
return
self.move()
self.eat()
self.collide()
if self.Game == -1:
self.root.destroy()
return
self.canvas.delete("all")
self.draw_snake()
self.draw_food()
self.root.after(self.speed,self.update)
window = tk.Tk()
snake = screen(window,35,35)
snake.update()
window.mainloop()
This is not really a bug. Your animation uses an 'update' function that is executed every 120ms. So if you hit 2 arrow keys within 120ms (i.e. between two successive calls of 'update'), only the last hit is considered, because only one translation vector can be considered for each snake update. Nobody can blame you on that point, as time controlled animation is a discrete process with a given time window. It's the only solution to get fluid and regular animation (all video games are based on such a process), so that's clearly correct.
However, your code may still be improved on several aspects. For instance, at each animation frame, you delete all Canvas items and create a whole new set of items ('create_rectangle') for the snake elements and the food. This is not very efficient. It would be better to simply change the coordinates of the items (check the Canvas.coords function from the doc). Note that animating a snake simply requires to move the previous tail position to the new head position, to give the illusion of a moving beast. So moving only 1 item (2 items when eating food) is necessary at each frame, which is must faster to process.
Thank Furas for the basic idea it was what i needed. New code with my correction :
def __init__(self, root,n,m):
"""
"""
self.input = []
"""
"""
def move_up(self,event):
if self.Game == -2:
self.Game =0
self.up()
self.update()
else:
self.input.append(0)
"""
Same for all the move
"""
def update(self):
if self.Game == -2:
return
if len(self.input)>3: #Make sure that the player doesn't stack instruction
self.pop()
try:
input = self.input.pop(0)
except:
input = -1
if input == 0:
self.up()
elif input == 1:
self.right()
elif input == 2:
self.down()
elif input == 3:
self.left()
self.move()
self.eat()
self.collide()
if self.Game == -1:
self.root.destroy()
return
self.canvas.delete("all")
self.draw_snake()
self.draw_food()
self.root.after(self.speed,self.update)
So a while ago I learned how to create a class for which had the purpose of creating an "opponent" which would basically fight the player and etc. It was a good tutorial and while I did learn how to create this type of class, I also got issues from the code itself when testing it out.
One of the reasons it didn't work properly was because of flipping my sprites horizontally; I have 2 sprite variables, 1 that loads the images and the other which is supposed to contain a list. A loop then "flips" all the images from the original and stores it inside the empty list. This, however, caused an issue and started to make my sprite "flash" on the screen in both directions so I removed the loop, tried again and it worked but this time it only had one sprite(facing the left).
I also tried to remove the variables and the loop outside the class but that ended up not displaying the image at all.
#Goku Black
walkLeftGB = [ pygame.image.load("GB1.png"), pygame.image.load("GB2.png"), pygame.image.load("GB3.png"), pygame.image.load("GB4.png") ]
walkRightGB = []
for l in walkLeftGB:
walkRightGB.append(pygame.transform.flip(l, True, False))
for x in range(len(walkLeftGB)):
walkLeftGB[x] = pygame.transform.smoothscale(walkLeftGB[x], (372, 493))
for x in range(len(walkRightGB)):
walkRightGB[x] = pygame.transform.smoothscale(walkRightGB[x], (372, 493))
# === CLASSES === (CamelCase names)
class Enemy(object):
global vel
global walkCount1
global walkRightGB, walkLeftGB
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = [self.x, self.end]
self.walkCount1 = 0
self.vel = 3
self.walkLeftGB = walkLeftGB
self.walkRightGB = walkRightGB
def draw(self, DS):
self.move()
global walkCount1
if self.walkCount1 + 1 <= 33:
self.walkCount1 = 0
if self.vel > 0:
DS.blit(self.walkRightGB[self.walkCount1 //4], (self.x, self.y))
self.walkCount1 += 1
else:
DS.blit(self.walkLeftGB[self.walkCount1 //4], (self.x, self.y))
self.walkCount1 += 1
def move(self):
global walkCount1
if self.vel > 0:
if self.x + self.vel < self.path[1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount1 = 0
else:
if self.x - self.vel > self.path[0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount1 = 0
man = Enemy(1600, 407, 96, 136, 22)
def redrawGameWindow():
global walkCount
pygame.display.update()
man.draw(DS)
DS.blit(canyon,(0,0))
lastMoved = "left"
if walkCount + 1 >= 27:
walkCount = 0
if left:
DS.blit(walkLeft[walkCount//3],(x,y))
walkCount +=1
lastMoved = "left"
elif right:
DS.blit(walkRight[walkCount//3], (x,y))
walkCount +=1
lastMoved = "right"
else: #this is when its moving neither left or right
if lastMoved == "left":
DS.blit(char2, (x, y))
else:
DS.blit(char, (x, y))
#The "redrawGameWindow" is then called in a loop inside a function.
After I made those changes mentioned above, the output received was now the image not appearing at all, I expected the output for the image to appear(and maybe move)
This does not appear to do what you want it to:
if self.walkCount1 + 1 <= 33:
self.walkCount1 = 0
As a detail, it's weird to add one and compare to 33, when if self.walkCount1 <= 32 would suffice.
More importantly, it looks like you wanted >= there.
You have some hard-coded magic number divisors: // 3 and // 4.
Rather than e.g. 4, it would be much better to
refer to len(self.walkRightGB).
Then you could choose to insert new interpolated walking images,
or delete some, without having to worry about correctness of
other code that may be affected.
Numbers like 27 should also be expressed in more meaningful terms.
It's not clear to me you want // integer division.
Possibly you were looking for % modulo instead.
As written, it looks like there's a danger of the code
trying to access past the end of an image array.
Printing out the counts would help you to debug this,
for example by verifying that incremented count is preserved
across function calls.