For my homework I have to draw a rectangle frame and the turtle should be a dot and moving to a random destination.
When I press the space bar(which starts and stops the simulation), the frame starts changing position bouncing with the dot. The dot is also not moving but only bounce in the center.
'''
import turtle
import random
#used to infect
class Virus:
def __init__(self, colour, duration):
self.colour = colour
self.duration = duration
## This class represents a person
class Person:
def __init__(self, world_size):
self.world_size = world_size
self.radius = 7
self.location = turtle.position()
self.destination = self._get_random_location()
#random locations are used to assign a destination for the person
#the possible locations should not be closer than 1 radius to the edge of the world
def _get_random_location(self):
x = random.randint(-349, 349)
y = random.randint(-249, 249)
return (x, y)
#draw a person using a dot. Use colour if implementing Viruses
def draw(self):
turtle.penup()
turtle.home()
turtle.pendown()
turtle.dot(self.radius*2)
#returns true if within 1 radius
def reached_destination(self):
self.location = turtle.position()
distX = abs(abs(self.destination[0])-abs(self.location[0]))
distY = abs(abs(self.destination[1])- abs(self.location[1]))
if distX and distY < self.radius:
return True
else:
pass
#Updates the person each hour.
#- moves each person by calling the move method
#- if the destination is reached then set a new destination
#- progress any illness
def update(self):
self.move()
if self.reached_destination():
self._get_random_location()
else:
self.move()
#moves person towards the destination
def move(self):
turtle.setheading(turtle.towards(self.destination))
turtle.forward(self.radius/2)
class World:
def __init__(self, width, height, n):
self.size = (width, height)
self.hours = 0
self.people = []
self.add_person()
#add a person to the list
def add_person(self):
person = Person(1)
self.people.append(person)
#simulate one hour in the world.
#- increase hours passed.
#- update all people
#- update all infection transmissions
def simulate(self):
self.hours += 1
for item in self.people:
item.update()
#Draw the world. Perform the following tasks:
# - clear the current screen
# - draw all the people
# - draw the box that frames the world
# - write the number of hours and number of people infected at the top of the frame
def draw(self):
turtle.clear()
turtle.hideturtle()
turtle.penup()
turtle.right(180)
turtle.forward(250)
turtle.right(90)
turtle.forward(350)
turtle.left(180)
turtle.pendown()
turtle.forward(700)
turtle.left(90)
turtle.forward(500)
turtle.left(90)
turtle.forward(700)
turtle.left(90)
turtle.forward(500)
turtle.right(180)
turtle.forward(500)
turtle.write(f'Hours: {self.hours}', False, 'left')
turtle.update()
for item in self.people:
item.draw()
#---------------------------------------------------------
#Should not need to alter any of the code below this line
#---------------------------------------------------------
class GraphicalWorld:
""" Handles the user interface for the simulation
space - starts and stops the simulation
'z' - resets the application to the initial state
'x' - infects a random person
'c' - cures all the people
"""
def __init__(self):
self.WIDTH = 800
self.HEIGHT = 600
self.TITLE = 'COMPSCI 130 Project One'
self.MARGIN = 50 #gap around each side
self.PEOPLE = 200 #number of people in the simulation
self.framework = AnimationFramework(self.WIDTH, self.HEIGHT, self.TITLE)
self.framework.add_key_action(self.setup, 'z')
self.framework.add_key_action(self.infect, 'x')
self.framework.add_key_action(self.cure, 'c')
self.framework.add_key_action(self.toggle_simulation, ' ')
self.framework.add_tick_action(self.next_turn)
self.world = None
def setup(self):
""" Reset the simulation to the initial state """
print('resetting the world')
self.framework.stop_simulation()
self.world = World(self.WIDTH - self.MARGIN * 2, self.HEIGHT - self.MARGIN * 2, self.PEOPLE)
self.world.draw()
def infect(self):
""" Infect a person, and update the drawing """
print('infecting a person')
self.world.infect_person()
self.world.draw()
def cure(self):
""" Remove infections from all the people """
print('cured all people')
self.world.cure_all()
self.world.draw()
def toggle_simulation(self):
""" Starts and stops the simulation """
if self.framework.simulation_is_running():
self.framework.stop_simulation()
else:
self.framework.start_simulation()
def next_turn(self):
""" Perform the tasks needed for the next animation cycle """
self.world.simulate()
self.world.draw()
## This is the animation framework
## Do not edit this framework
class AnimationFramework:
"""This framework is used to provide support for animation of
interactive applications using the turtle library. There is
no need to edit any of the code in this framework.
"""
def __init__(self, width, height, title):
self.width = width
self.height = height
self.title = title
self.simulation_running = False
self.tick = None #function to call for each animation cycle
self.delay = 1 #smallest delay is 1 millisecond
turtle.title(title) #title for the window
turtle.setup(width, height) #set window display
turtle.hideturtle() #prevent turtle appearance
turtle.tracer(0, 0) #prevent turtle animation
turtle.listen() #set window focus to the turtle window
turtle.mode('logo') #set 0 direction as straight up
turtle.penup() #don't draw anything
turtle.setundobuffer(None)
self.__animation_loop()
def start_simulation(self):
self.simulation_running = True
def stop_simulation(self):
self.simulation_running = False
def simulation_is_running(self):
return self.simulation_running
def add_key_action(self, func, key):
turtle.onkeypress(func, key)
def add_tick_action(self, func):
self.tick = func
def __animation_loop(self):
try:
if self.simulation_running:
self.tick()
turtle.ontimer(self.__animation_loop, self.delay)
except turtle.Terminator:
pass
gw = GraphicalWorld()
gw.setup()
turtle.mainloop()
'''
The turtle dot should be bouncing slowly to the random location and the frame should stay still when I press the space bar. And I know the code is long sorry about that.
the frame starts changing position bouncing with the dot. The dot is
also not moving but only bounce in the center.
I've reworked your Person and World classes below to address these two issues. This simulation model you're given uses a single turtle which means we have to play by certain rules:
Each Person must keep track of their own position and heading
Only the draw() method should have the pen down, all other Person methods should have the pen up if using the turtle to do calculations
Whenever a Person uses the turtle, you can't assume anything about it as another Person was just using the turtle so you must set your heading, position and pen state
The changed portion of your posted code:
FONT = ('Arial', 16, 'normal')
# This class represents a person
class Person():
def __init__(self, world_size):
self.world_size = world_size
self.radius = 7
self.location = turtle.position()
self.destination = self._get_random_location()
turtle.penup()
turtle.setposition(self.location)
turtle.setheading(turtle.towards(self.destination))
self.heading = turtle.heading()
# random locations are used to assign a destination for the person
# the possible locations should not be closer than 1 radius to the edge of the world
def _get_random_location(self):
x = random.randint(self.radius - 349, 349 - self.radius)
y = random.randint(self.radius - 249, 249 - self.radius)
return (x, y)
# draw a person using a dot. Use colour if implementing Viruses
def draw(self):
x, y = self.location
# use .circle() not .dot() as the latter causes an extra update (flicker)
turtle.penup()
turtle.setposition(x, y - self.radius)
turtle.pendown()
turtle.begin_fill()
turtle.circle(self.radius)
turtle.end_fill()
# returns true if within 1 radius
def reached_destination(self):
distX = abs(self.destination[0] - self.location[0])
distY = abs(self.destination[1] - self.location[1])
return distX < self.radius and distY < self.radius
# Updates the person each hour.
# - moves each person by calling the move method
# - if the destination is reached then set a new destination
# - progress any illness
def update(self):
self.move()
if self.reached_destination():
self.destination = self._get_random_location()
turtle.penup()
turtle.setposition(self.location)
turtle.setheading(turtle.towards(self.destination))
self.heading = turtle.heading()
# moves person towards the destination
def move(self):
turtle.penup()
turtle.setheading(self.heading)
turtle.setposition(self.location)
turtle.forward(self.radius / 2)
self.location = turtle.position()
class World:
def __init__(self, width, height, n):
self.size = (width, height)
self.hours = 0
self.people = []
for _ in range(n):
self.add_person()
# add a person to the list
def add_person(self):
person = Person(1)
self.people.append(person)
# simulate one hour in the world.
# - increase hours passed.
# - update all people
# - update all infection transmissions
def simulate(self):
self.hours += 1
for item in self.people:
item.update()
# Draw the world. Perform the following tasks:
# - clear the current screen
# - draw all the people
# - draw the box that frames the world
# - write the number of hours and number of people infected at the top of the frame
def draw(self):
turtle.clear() # also undoes hideturtle(), etc.
turtle.hideturtle()
turtle.setheading(0)
turtle.penup()
turtle.setposition(-350, -250)
turtle.pendown()
for _ in range(2):
turtle.forward(500)
turtle.right(90)
turtle.forward(700)
turtle.right(90)
for item in self.people:
item.draw()
turtle.penup()
turtle.setposition(-350, -250)
# leave this to the end as .write() forces an extra update (flicker)
turtle.write(f'Hours: {self.hours}', move=False, align='left', font=FONT)
turtle.update()
I've also included some tweaks to reduce flicker. I'm sure there's lots more work to be done.
If we wanted to run this simulation faster, we'd make each Person a separate turtle (e.g. via inheritance) and use the reshaped turtle itself as it's presence on the screen and not draw the Person. And we'd throw separate turtles at the outline frame and the text to simplify updates.
Related
I have a maze game that comprises of 3 different class, which includes 2 maze solving algorithms and a class for players to move around with the up, down, left and right key. So the way to toggle between the classes is to use a tab key. But however, when i reach the class where users can manually control the sprite, there will always be a random black arrow that looks like its going through spasm in the middle of my window. Even though i can manage to control the orange sprite, but that black arrow is still there. Is there anyway that i can remove it? I tried using hideturtle to hide the arrow, but to no avail.
I highly suspect that it was due to this part of my code that resulted in the appearance of the black arrowhead. But i wasn't able to find a replacement for that.
while True:
ManualMovements()
This is how the black sprite looks like right now:
My current code:
from turtle import * # import the turtle library
# define the tile_size and cursor_size for usage later on
TILE_SIZE = 24
CURSOR_SIZE = 20
screen = Screen() # instantiate the Screen class from turtle
screen.setup(700, 700) # determine the size of the turtle pop out window
class Wall(Turtle): # create Wall class to plot out the walls
def __init__(self):
super().__init__() # inherit from Turtle(parent class)
self.hideturtle() # hide the cursor
self.shape('square') # define the shape of the object we wanna draw out
self.shapesize(TILE_SIZE / CURSOR_SIZE) # define the size of the square
self.pencolor('black') # define the color that we are going to plot the grids out
self.penup() # to prevent the pen from leaving a trace
self.speed('fastest') # get the fastest speed
class Path(Turtle): # create Path class to plot out the path
def __init__(self):
super().__init__() # inherit from Turtle(parent class)
self.hideturtle() # hide the cursor
self.shape('square') # define the shape of the object we wanna draw out
self.shapesize(TILE_SIZE / CURSOR_SIZE) # define the size of the square
self.pencolor('white') # define the color that we are going to plot the grids out
self.penup() # to prevent the pen from leaving a trace
self.speed('fastest') # get the fastest speed
class Sprite(Turtle): # create Sprite class to define the turtle and its characteristics
def __init__(self):
super().__init__() # inherit from Turtle(parent class)
self.shape('turtle') # define the shape of the object we wanna draw out
self.shapesize((TILE_SIZE / CURSOR_SIZE)-0.4) # define the size of the square
self.color('orange') # define the color that we are going to plot the turtle
self.penup() # to prevent the pen from leaving a trace
self.speed('slowest') # set speed to slowest to observe the sprite movement
class ManualMovements(Sprite): # create ManualMovement class to let the user manually control the sprite
def __init__(self):
super().__init__()
self.moves = 0
self.hideturtle()
self.screen.onkeypress(self.go_up, "Up")
self.screen.onkeypress(self.go_down, "Down")
self.screen.onkeypress(self.go_left, "Left")
self.screen.onkeypress(self.go_right, "Right")
def go_up(self):
xcor = sprite.xcor()
ycor = sprite.ycor()
if (xcor, ycor + TILE_SIZE) not in walls:
sprite.setheading(90)
sprite.goto(xcor, ycor + TILE_SIZE)
def go_down(self):
xcor = sprite.xcor()
ycor = sprite.ycor()
if (xcor, ycor - TILE_SIZE) not in walls:
sprite.setheading(270)
sprite.goto(xcor, ycor - TILE_SIZE)
def go_left(self):
xcor = sprite.xcor()
ycor = sprite.ycor()
if (xcor - TILE_SIZE, ycor) not in walls:
sprite.setheading(180)
sprite.goto(xcor - TILE_SIZE, ycor)
def go_right(self):
xcor = sprite.xcor()
ycor = sprite.ycor()
if (xcor + TILE_SIZE, ycor) not in walls:
sprite.setheading(0)
sprite.goto(xcor + TILE_SIZE, ycor)
def setup_maze(level): # create a setup_maze function so that we can plot out the map in turtle
# declare maze_height and maze_width first as the limits for the entire maze
maze_height, maze_width = len(level), len(level[0])
# get the center point for each maze
center_horizontal_point = (maze_width + 1) / 2
center_vertical_point = (maze_height + 1) / 2
for y in range(maze_height): # for loop to limit the entire maze
for x in range(maze_width):
character = level[y][x] # get the character at each x,y coordinate
# calculate the screen x, y coordinates
screen_x = ((x - maze_width) * TILE_SIZE) + (center_horizontal_point * TILE_SIZE)
screen_y = ((maze_height - y) * TILE_SIZE) - (center_vertical_point * TILE_SIZE)
if character == "X":
maze.fillcolor('grey')
maze.goto(screen_x, screen_y)
maze.stamp()
walls.append((screen_x, screen_y)) # add coordinates for the wall to the list
else:
maze.fillcolor('white')
maze.goto(screen_x, screen_y)
maze.stamp()
paths.append((screen_x, screen_y)) # add coordinates for the path to the list
if character == "e":
maze.fillcolor(['white', 'red'][character == 'e'])
maze.goto(screen_x, screen_y) # proceed on to the coordinates on turtle
maze.stamp() # stamp out the boxes
finish.append((screen_x, screen_y)) # add coordinates for the endpoint to the list
if character == 's': # if statement to determine if the character is s
maze.fillcolor('green')
maze.goto(screen_x, screen_y)
maze.stamp() # stamp out the boxes
start.append((screen_x, screen_y)) # add coordinates for the startpoint to the list
sprite.goto(screen_x, screen_y) # move the sprite to the location where it is supposed to start
def endProgram(): # exit the entire program upon clicking anywhere in the turtle window
screen.exitonclick()
grid = [] # create a grid list to store the labels while reading from the txt file
walls = [] # create walls coordinate list
start = []
finish = [] # enable the finish array
paths = []
maze = Wall() # enable the Wall class
sprite = Sprite() # enable the Sprite class
path = Path()
with open("map02.txt") as file: # open the txt file and read contents and append it to maze
for line in file:
grid.append(line.strip())
setup_maze(grid) # call the setup maze function
start_x, start_y = (start[0])[0], (start[0])[1]
sprite.seth(0)
sprite.goto(start_x, start_y)
while True:
ManualMovements()
screen.listen() # we need this in order to allow turtle to detect what key we are pressing and respond to it
screen.mainloop()
A sample txt file map:
XXXXXXXXXXXXXXXXXXXX
Xe.................X
XXXXXXX...X..XXXXX.X
XXXXXX....X.....XXXX
XXX.......X...X.XXXX
XXXXXX....X.....XXXX
X.........X...XXXXXX
X.XXXXXX..X...XXXXXX
X.X.......X..XXXXXXX
X.X...XXXX.........X
X.XXXXXsXX..XXXXXXXX
X..............XXXXX
XXXXXXXXXXXXXXXXXXXX
EDIT: Some edits have been made. Please relook again. In order to execute the code, you have to press tab once before pressing the up, down, left, right buttons from my side. I also have removed the 2 classes to prevent confusion and also since i pinpointed that this part of the code is where my error should come from. The current code above does replicate the same error too.
The problem is that your ManualMovements class is wrong-headed. Rather than a bizarre helper class, it should be a subclass of Sprite. (Akin to a fully automated Sprite subclass.) I've reworked your code accordingly below as well as made other fixes and optimizations -- pick and choose as you see fit:
from turtle import Screen, Turtle
# define some global constants for use later
TILE_SIZE = 24
CURSOR_SIZE = 20
class Wall(Turtle):
''' class to plot out walls '''
def __init__(self):
super().__init__(shape='square') # inherit from parent class
self.hideturtle()
self.shapesize(TILE_SIZE / CURSOR_SIZE) # define the size of the square
self.penup() # prevent the pen from leaving a trace
self.speed('fastest')
class Path(Wall):
''' class to plot out the path '''
def __init__(self):
super().__init__() # inherit from parent class
self.pencolor('white') # define the color that we are going to plot the grids out
class Sprite(Turtle):
''' class to define turtle sprite and its characteristics '''
def __init__(self):
super().__init__(shape='turtle') # inherit from parent class
self.shapesize((TILE_SIZE / CURSOR_SIZE) - 0.4) # define the size of the square
self.color('orange') # color that we are going to plot the turtle
self.penup() # prevent the pen from leaving a trace
self.speed('slowest') # set speed to slowest to observe the sprite movement
class ManualSprite(Sprite):
''' class to let the user manually control the sprite '''
def __init__(self):
super().__init__()
screen.onkeypress(self.go_up, 'Up')
screen.onkeypress(self.go_down, 'Down')
screen.onkeypress(self.go_left, 'Left')
screen.onkeypress(self.go_right, 'Right')
def go_up(self):
screen.onkeypress(None, 'Up') # disable handler inside handler
xcor = int(self.xcor())
ycor = int(self.ycor())
if (xcor, ycor + TILE_SIZE) not in walls:
self.setheading(90)
self.sety(ycor + TILE_SIZE)
screen.onkeypress(self.go_up, 'Up') # reenable handler on exit
def go_down(self):
screen.onkeypress(None, 'Down')
xcor = int(self.xcor())
ycor = int(self.ycor())
if (xcor, ycor - TILE_SIZE) not in walls:
self.setheading(270)
self.sety(ycor - TILE_SIZE)
screen.onkeypress(self.go_down, 'Down')
def go_left(self):
screen.onkeypress(None, 'Left')
xcor = int(self.xcor())
ycor = int(self.ycor())
if (xcor - TILE_SIZE, ycor) not in walls:
self.setheading(180)
self.setx(xcor - TILE_SIZE)
screen.onkeypress(self.go_left, 'Left')
def go_right(self):
screen.onkeypress(None, 'Right')
xcor = int(self.xcor())
ycor = int(self.ycor())
if (xcor + TILE_SIZE, ycor) not in walls:
self.setheading(0)
self.setx(xcor + TILE_SIZE)
screen.onkeypress(self.go_right, 'Right')
def setup_maze(level):
''' plot out the map as a maze in turtle '''
# declare the limits for the entire maze
maze_height, maze_width = len(level), len(level[0])
# get the center point for each maze
center_horizontal_point = (maze_width + 1) / 2
center_vertical_point = (maze_height + 1) / 2
start = finish = None
for y in range(maze_height):
for x in range(maze_width):
character = level[y][x] # get the character at each coordinate
# calculate the screen x, y coordinates
screen_x = int((x - maze_width) * TILE_SIZE + center_horizontal_point * TILE_SIZE)
screen_y = int((maze_height - y) * TILE_SIZE - center_vertical_point * TILE_SIZE)
maze.goto(screen_x, screen_y)
if character == 'X':
maze.fillcolor('grey')
walls.append((screen_x, screen_y)) # add coordinates for the wall
else:
paths.append((screen_x, screen_y)) # add coordinates for the path
if character == 'e':
maze.fillcolor('red')
finish = (screen_x, screen_y)
elif character == 's':
maze.fillcolor('green')
start = (screen_x, screen_y)
else:
maze.fillcolor('white')
maze.stamp()
return start, finish
grid = [] # create a grid list to store the labels while reading from the txt file
with open("map02.txt") as file: # open the txt file and read contents and append it to maze
for line in file:
grid.append(line.strip())
screen = Screen() # extract the Screen class from turtle
screen.setup(700, 700) # set the size of the turtle window
sprite = ManualSprite() # instantiate a Sprite instance
walls = [] # walls coordinate list
paths = []
maze = Wall() # instantiate the Wall class
path = Path()
start, finish = setup_maze(grid)
sprite.setheading(0)
sprite.goto(start) # move the sprite to the start location
screen.listen() # allow turtle to detect key press and respond to it
screen.mainloop()
I've tossed comments that simply echo the obvious. Also, your logic didn't take into account the imprecision of floating point coordinates which makes them difficult to compare -- I've converted those comparisons to int.
I have a helper class that I use to spawn rotating images to the screen. The problem is, when I try to rotate the images, the whole layout rotates instead, although I do use PushMatrix and PopMatrix. They seem to have no effect and I cannot understand why.
I have also tried to separate everything with canvas.before and canvas.after, but got the same result.
The helper class:
class WidgetDrawer(Widget):
def __init__(self, imageStr, windowsize, **kwargs):
super(WidgetDrawer, self).__init__(**kwargs)
# this is part of the**kwargs notation
# if you haven't seen with before, here's a link
# http://effbot.org/zone/python-with-statement.html
self.WindowHeigth, self.WindowWidth = windowsize
with self.canvas:
PushMatrix()
# setup a default size for the object
self.size = (self.WindowWidth*.002*25, self.WindowWidth*.002*25)
# center the widget
self.pos = (self.center_x, self.center_y)
self.rot = Rotate()
self.rot.origin = self.pos
self.rot.axis = (0, 0, 1)
self.rot.angle = 30
# this line creates a rectangle with the image drawn on top
self.rect_bg = Rectangle(source=imageStr,
pos=self.pos,
size=self.size)
PopMatrix()
# this line calls the update_graphics_pos function every time the
# position variable is modified
self.bind(pos=self.update_graphics_pos)
self.velocity_x = 0 # initialize velocity_x and velocity_y
self.velocity_y = 0
def update_graphics_pos(self, instance, value):
# if the widgets position moves, the rectangle that contains the image
# is also moved
self.rect_bg.pos = value
# use this function to change widget position
def setPos(self, xpos, ypos):
self.x = xpos
self.y = ypos
def move(self):
self.x = self.x + self.velocity_x
self.y = self.y + self.velocity_y
if self.y > self.WindowHeigth*0.95:
# don't let the ship go up too high
self.y = self.WindowHeigth*0.95
if self.y <= 0:
self.y = 0 # set heigth to 0 if ship too low
def update(self):
# the update function moves the astreoid. Other things could happen
# here as well (speed changes for example)
self.move()
Edit: added minimal reproducible example of the above class in being used: The bellow code spawns items that go from right lo left (and should rotate, but instead the whole layout rotates)
from kivy.config import Config
# don't make the app re-sizeable
Config.set('graphics', 'resizable', False)
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.graphics.vertex_instructions import Rectangle
from kivy.properties import partial
from kivy.graphics.context_instructions import PopMatrix, PushMatrix, Rotate
from random import randint
class WidgetDrawer(Widget):
# This widget is used to draw all of the objects on the screen
# it handles the following:
# widget movement, size, positioning
# whever a WidgetDrawer object is created, an image string needs to be
# specified, along with the windowsize tuple
# example: wid - WidgetDrawer('./image.png', (Window.height, Window.width))
# windowsize is used for the default size and position,
# but can pe updated later
def __init__(self, imageStr, windowsize, **kwargs):
super(WidgetDrawer, self).__init__(**kwargs)
# this is part of the**kwargs notation
# if you haven't seen with before, here's a link
# http://effbot.org/zone/python-with-statement.html
self.WindowHeigth, self.WindowWidth = windowsize
with self.canvas:
PushMatrix()
# setup a default size for the object
self.size = (self.WindowWidth*.002*25, self.WindowWidth*.002*25)
# center the widget
self.pos = (self.center_x, self.center_y)
self.rot = Rotate()
self.rot.origin = self.pos
self.rot.axis = (0, 0, 1)
self.rot.angle = 30
# this line creates a rectangle with the image drawn on top
self.rect_bg = Rectangle(source=imageStr,
pos=self.pos,
size=self.size)
PopMatrix()
# this line calls the update_graphics_pos function every time the
# position variable is modified
self.bind(pos=self.update_graphics_pos)
self.velocity_x = 0 # initialize velocity_x and velocity_y
self.velocity_y = 0
def update_graphics_pos(self, instance, value):
# if the widgets position moves, the rectangle that contains the image
# is also moved
self.rect_bg.pos = value
# use this function to change widget position
def setPos(self, xpos, ypos):
self.x = xpos
self.y = ypos
def move(self):
self.x = self.x + self.velocity_x
self.y = self.y + self.velocity_y
if self.y > self.WindowHeigth*0.95:
# don't let the ship go up too high
self.y = self.WindowHeigth*0.95
if self.y <= 0:
self.y = 0 # set heigth to 0 if ship too low
def update(self):
# the update function moves the astreoid. Other things could happen
# here as well (speed changes for example)
self.move()
class Asteroid(WidgetDrawer):
# Asteroid class. The flappy ship will dodge these
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def update(self):
# self.rot.angle += 1 # this should animate the object, but rotates the layout
super().update()
class Game(Widget):
# this is the main widget that contains the game.
def __init__(self, **kwargs):
super(Game, self).__init__(**kwargs)
self.windowsize = (Window.height, Window.width)
self.asteroidList = [] # use this to keep track of asteroids
self.minProb = 1700 # this variable used in spawning asteroids
def addAsteroid(self):
# add an asteroid to the screen
# self.asteroid
imageNumber = randint(1, 4)
imageStr = './sandstone_'+str(imageNumber)+'.png' #change the image here
tmpAsteroid = Asteroid(imageStr, self.windowsize)
tmpAsteroid.x = Window.width*0.99
# randomize y position
ypos = randint(0, 16)
ypos = ypos*Window.height*.0625
tmpAsteroid.y = ypos
tmpAsteroid.velocity_y = 0
vel = 10
tmpAsteroid.velocity_x = -0.1*vel
self.asteroidList.append(tmpAsteroid)
self.add_widget(tmpAsteroid)
def update(self, dt):
# This update function is the main update function for the game
# All of the game logic has its origin here
# events are setup here as well
# update game objects
# update asteroids
# randomly add an asteroid
tmpCount = randint(1, 1800)
if tmpCount > self.minProb:
self.addAsteroid()
if self.minProb < 1700:
self.minProb = 1900
self.minProb = self.minProb - 1
for k in self.asteroidList:
if k.x < -20:
self.remove_widget(k)
self.asteroidList.remove(k)
k.update()
class ClientApp(App):
def build(self):
# this is where the root widget goes
# should be a canvas
parent = Widget() # this is an empty holder for buttons, etc
Window.clearcolor = (0, 0, 0, 1.)
game = Game()
# Start the game clock (runs update function once every (1/60) seconds
# Clock.schedule_interval(app.update, 1.0/60.0)
parent.add_widget(game)
Clock.schedule_interval(game.update, 1.0/60.0)
# use this hierarchy to make it easy to deal w/buttons
return parent
if __name__ == '__main__':
ClientApp().run()
You code rotates all your Asteroids by the same amount and that rotation does not vary as time goes on. You can fix that by updating the rotation with every call to update(). Try modifying your code like this:
First define a delta_angle for each Asteroid in its __init__() method:
self.delta_angle = randint(-3, 3)
This gives each Asteroid its own random rotation.
Then create a method to actually update the rotation in the WidgetDrawer:
def rotate(self):
self.rot.origin = self.center
self.rot.angle += self.delta_angle
Then call this new method from the update() method:
def update(self):
# the update function moves the astreoid. Other things could happen
# here as well (speed changes for example)
self.move()
self.rotate()
Now, each Asteroid should rotate independently as time passes.
The Point of the game is to make all the circles disappear when they Collide but for some reason some circles aren't disappearing? - Thank you for your help in advance!
import turtle
import random
import math
import time
# Setting up the Screen
ms = turtle.Screen()
ms.bgcolor("red")
ms.title("Space Rocket Minigame #Rafa94")
# Using class functions/Methods
# subclass
class Border(turtle.Turtle):
def __init__(self): # class constrcutor
turtle.Turtle.__init__(self) # adding our Objects attributes all starting with "self"
self.penup()
self.hideturtle()
self.speed(0)
self.color("silver")
self.pensize(5)
def draw_border(self):
self.penup()# getting our pen to start drawing
self.goto(-300, -300)
self.pendown()
self.goto(-300, 300)
self.goto(300, 300)
self.goto(300, -300)
self.goto(-300, -300)
class Player(turtle.Turtle): # since it got inherited this class becomes a Superclass
def __init__(self): # self is our only argument here but it will have multiple attributes
turtle.Turtle.__init__(self) # since we are using the Turtle module, we are able to use it's built in functions
self.penup()# our attributes
self.speed(0)
self.shape("triangle")
self.color("black")
self.velocity = 0.1
def move(self):
self.forward(self.velocity)
# Border Checking
if self.xcor() > 290 or self.xcor() < -290: # Left side is -290 Right side is 290 we also want the coordinates x and y to be below 300 to not go over our border
self.left(60)
if self.ycor() > 290 or self.ycor() < -290:
self.left(60)
def turnleft(self):
self.left(30)
def turnright(self):
self.right(30)
def increasespeed(self):
self.velocity += 1
class Goal(turtle.Turtle): # Sub Class
def __init__(self):
# since we are using the Turtle module we are able to use it's built in functions
turtle.Turtle.__init__(self)
self.penup() # our attributes
self.speed(0)
self.shape("circle")
self.color("green")
self.velocity = 3 #xcor #ycor
self.goto(random.randint(-250, 250), random.randint(-250, 250)) # we are making our turtle "go to" X & Y coordinates by -250 and 250 only randomly. We also have our random module here aswell
self.setheading(random.randint(0, 360)) # setting the heading to see in which direction i want it to go
def jump(self): # Jump = Collidee
self.goto(random.randint(-250, 250), random.randint(-250, 250)) # "jump" stands for Collidee so if the circle "jumps" with player it will move to random postion by 250 and -25
self.setheading(random.randint(0, 360)) # from where it collidee's it goes 360 moves location 360 Right
def move(self): # we copyed the same method cause it will be doing the same movements as the player we want it to go "forward" with our set "speed" & also check for our borders we set
self.forward(self.velocity)
# Border Checking
if self.xcor() > 290 or self.xcor() < -290: # Left side is -290 Right side is 290 we also want the coordinates x and y to be below 300 to not go over our border
self.left(60)
if self.ycor() > 290 or self.ycor() < -290:
self.left(60)
# Collision checking function/Method
# Uses the Pythgorean Theorem to measure the distance between two objects
def isCollision(t1, t2): # t1 = turtle1 t2 = turtle also when a function starts with "is" isCollision most likely it will be a Boolean of True or False
a = t1.xcor()-t2.xcor() # xcor = Right -xcor = Left/ when they collide the xcor is 0
b = t1.ycor()-t2.ycor() # ycor = Right -ycor = Left/ when they collide the ycor is 0
distance = math.sqrt((a ** 2) + (b ** 2))
if distance < 30:
return True
else:
return False
# Create class instances
player = Player() # after creating a class must make instances to call it in other words make an Object of the class
border = Border() # sub class
#goal = Goal() #sub class
# Draw our border
border.draw_border()
#create multiple goals
goals = [] # Creating a list of goals
for count in range(6): # We are making the code repeat 6 times
goals.append(Goal()) # each time the code runs it puts a goal the end 6 times
# Set keyboard bindings
ms.listen()
ms.onkey(player.turnleft, "Left")
ms.onkey(player.turnright, "Right")
ms.onkey(player.increasespeed, "Up")
# speed game up
ms.tracer(0.1)
# main loop
while True:
ms.update()
player.move() # these two are class methods
#goal.move() # the reason we copyed like we said is cause it's gunna have the exact same movements as our player!
# we want the goal to be True to in our while loop in order for the code to be excuted
for goal in goals:
# Basically saying If there is a collision between the player and goal we the want the goal to "jump" / Function in our while True loop
goal.move()
if isCollision(player, goal):
goal.jump() # baiscally saying if the goal collide's move or "jump" to other location
time.sleep(0.005)
As far as I can tell, your circles (goals) disappear and reappear elsewhere as you designed them to do. One possibiliy is that since you move the circles to a random location, that location can be close to where they disappeared from, making it appear that nothing happened.
Generally, your code is a mess. Below is my rewrite of it to bring some structure and style to it. As well as take more advantage of what turtle offers:
from turtle import Screen, Turtle
from random import randint
WIDTH, HEIGHT = 600, 600
CURSOR_SIZE = 20
class Border(Turtle):
def __init__(self): # class initializer
super().__init__(visible=False)
self.penup()
self.color("silver")
self.pensize(5)
def draw_border(self):
self.goto(-WIDTH/2, -HEIGHT/2)
self.pendown()
for _ in range(2):
self.forward(WIDTH)
self.left(90)
self.forward(HEIGHT)
self.left(90)
class Player(Turtle):
def __init__(self):
super().__init__(shape="triangle")
self.color("black")
self.penup()
self.velocity = 0.1
def move(self):
self.forward(self.velocity)
if not (CURSOR_SIZE - WIDTH/2 < self.xcor() < WIDTH/2 - CURSOR_SIZE and CURSOR_SIZE - HEIGHT/2 < self.ycor() < HEIGHT/2 - CURSOR_SIZE):
self.undo() # undo forward motion
self.left(60)
self.forward(self.velocity) # redo forward motion in new heading
def turn_left(self):
self.left(30)
def turn_right(self):
self.right(30)
def increase_speed(self):
self.velocity += 1
class Goal(Turtle):
def __init__(self):
super().__init__(shape="circle")
self.color("green")
self.penup()
self.velocity = 3
self.jump()
def jump(self):
while self.distance(player) < CURSOR_SIZE * 10: # make sure we move far away from player
self.goto(randint(CURSOR_SIZE - WIDTH/2, WIDTH/2 - CURSOR_SIZE), randint(CURSOR_SIZE - HEIGHT/2, HEIGHT/2 - CURSOR_SIZE))
self.setheading(randint(0, 360))
def move(self):
self.forward(self.velocity)
if not (CURSOR_SIZE - WIDTH/2 < self.xcor() < WIDTH/2 - CURSOR_SIZE and CURSOR_SIZE - HEIGHT/2 < self.ycor() < HEIGHT/2 - CURSOR_SIZE):
self.undo() # undo forward motion
self.left(60)
self.forward(self.velocity) # redo forward motion in new heading
def isCollision(t1, t2):
return t1.distance(t2) < CURSOR_SIZE
def move():
player.move()
for goal in goals:
goal.move()
if isCollision(player, goal):
goal.jump()
screen.update()
screen.ontimer(move, 10)
# Setting up the Screen
screen = Screen()
screen.bgcolor("red")
screen.title("Space Rocket Minigame #cdlane")
screen.tracer(False)
# Create class instances
Border().draw_border()
player = Player()
# Create multiple goals
goals = [Goal() for _ in range(6)]
# Set keyboard bindings
screen.onkey(player.turn_left, "Left")
screen.onkey(player.turn_right, "Right")
screen.onkey(player.increase_speed, "Up")
screen.listen()
move()
screen.mainloop()
I am in the process of learning tkinter on Python 3.X. I am writing a simple program which will get one or more balls (tkinter ovals) bouncing round a rectangular court (tkinter root window with a canvas and rectangle drawn on it).
I want to be able to terminate the program cleanly by pressing the q key, and have managed to bind the key to the root and fire the callback function when a key is pressed, which then calls root.destroy().
However, I'm still getting errors of the form _tkinter.TclError: invalid command name ".140625086752360" when I do so. This is driving me crazy. What am I doing wrong?
from tkinter import *
import time
import numpy
class Ball:
def bates():
"""
Generator for the sequential index number used in order to
identify the various balls.
"""
k = 0
while True:
yield k
k += 1
index = bates()
def __init__(self, parent, x, y, v=0.0, angle=0.0, accel=0.0, radius=10, border=2):
self.parent = parent # The parent Canvas widget
self.index = next(Ball.index) # Fortunately, I have all my feathers individually numbered, for just such an eventuality
self.x = x # X-coordinate (-1.0 .. 1.0)
self.y = y # Y-coordinate (-1.0 .. 1.0)
self.radius = radius # Radius (0.0 .. 1.0)
self.v = v # Velocity
self.theta = angle # Angle
self.accel = accel # Acceleration per tick
self.border = border # Border thickness (integer)
self.widget = self.parent.canvas.create_oval(
self.px() - self.pr(), self.py() - self.pr(),
self.px() + self.pr(), self.py() + self.pr(),
fill = "red", width=self.border, outline="black")
def __repr__(self):
return "[{}] x={:.4f} y={:.4f} v={:.4f} a={:.4f} r={:.4f} t={}, px={} py={} pr={}".format(
self.index, self.x, self.y, self.v, self.theta,
self.radius, self.border, self.px(), self.py(), self.pr())
def pr(self):
"""
Converts a radius from the range 0.0 .. 1.0 to window coordinates
based on the width and height of the window
"""
assert self.radius > 0.0 and self.radius <= 1.0
return int(min(self.parent.height, self.parent.width)*self.radius/2.0)
def px(self):
"""
Converts an X-coordinate in the range -1.0 .. +1.0 to a position
within the window based on its width
"""
assert self.x >= -1.0 and self.x <= 1.0
return int((1.0 + self.x) * self.parent.width / 2.0 + self.parent.border)
def py(self):
"""
Converts a Y-coordinate in the range -1.0 .. +1.0 to a position
within the window based on its height
"""
assert self.y >= -1.0 and self.y <= 1.0
return int((1.0 - self.y) * self.parent.height / 2.0 + self.parent.border)
def Move(self, x, y):
"""
Moves ball to absolute position (x, y) where x and y are both -1.0 .. 1.0
"""
oldx = self.px()
oldy = self.py()
self.x = x
self.y = y
deltax = self.px() - oldx
deltay = self.py() - oldy
if oldx != 0 or oldy != 0:
self.parent.canvas.move(self.widget, deltax, deltay)
def HandleWallCollision(self):
"""
Detects if a ball collides with the wall of the rectangular
Court.
"""
pass
class Court:
"""
A 2D rectangular enclosure containing a centred, rectagular
grid of balls (instances of the Ball class).
"""
def __init__(self,
width=1000, # Width of the canvas in pixels
height=750, # Height of the canvas in pixels
border=5, # Width of the border around the canvas in pixels
rows=1, # Number of rows of balls
cols=1, # Number of columns of balls
radius=0.05, # Ball radius
ballborder=1, # Width of the border around the balls in pixels
cycles=1000, # Number of animation cycles
tick=0.01): # Animation tick length (sec)
self.root = Tk()
self.height = height
self.width = width
self.border = border
self.cycles = cycles
self.tick = tick
self.canvas = Canvas(self.root, width=width+2*border, height=height+2*border)
self.rectangle = self.canvas.create_rectangle(border, border, width+border, height+border, outline="black", fill="white", width=border)
self.root.bind('<Key>', self.key)
self.CreateGrid(rows, cols, radius, ballborder)
self.canvas.pack()
self.afterid = self.root.after(0, self.Animate)
self.root.mainloop()
def __repr__(self):
s = "width={} height={} border={} balls={}\n".format(self.width,
self.height,
self.border,
len(self.balls))
for b in self.balls:
s += "> {}\n".format(b)
return s
def key(self, event):
print("Got key '{}'".format(event.char))
if event.char == 'q':
print("Bye!")
self.root.after_cancel(self.afterid)
self.root.destroy()
def CreateGrid(self, rows, cols, radius, border):
"""
Creates a rectangular rows x cols grid of balls of
the specified radius and border thickness
"""
self.balls = []
for r in range(1, rows+1):
y = 1.0-2.0*r/(rows+1)
for c in range(1, cols+1):
x = 2.0*c/(cols+1) - 1.0
self.balls.append(Ball(self, x, y, 0.001,
numpy.pi/6.0, 0.0, radius, border))
def Animate(self):
"""
Animates the movement of the various balls
"""
for c in range(self.cycles):
for b in self.balls:
b.v += b.accel
b.Move(b.x + b.v * numpy.cos(b.theta),
b.y + b.v * numpy.sin(b.theta))
self.canvas.update()
time.sleep(self.tick)
self.root.destroy()
I've included the full listing for completeness, but I'm fairly sure that the problem lies in the Court class. I presume it's some sort of callback or similar firing but I seem to be beating my head against a wall trying to fix it.
You have effectively got two mainloops. In your Court.__init__ method you use after to start the Animate method and then start the Tk mainloop which will process events until you destroy the main Tk window.
However the Animate method basically replicates this mainloop by calling update to process events then time.sleep to waste some time and repeating this. When you handle the keypress and terminate your window, the Animate method is still running and attempts to update the canvas which no longer exists.
The correct way to handle this is to rewrite the Animate method to perform a single round of moving the balls and then schedule another call of Animate using after and provide the necessary delay as the after parameter. This way the event system will call your animation function at the correct intervals while still processing all other window system events promptly.
I have written a turtle program in python, but there are two problems.
It goes way too slow for larger numbers, I was wonder how I can speed up turtle.
It freezes after it finishes and when clicked on, says 'not responding'
This is my code so far:
import turtle
#Takes user input to decide how many squares are needed
f=int(input("How many squares do you want?"))
c=int(input("What colour would you like? red = 1, blue = 2 and green =3"))
n=int(input("What background colour would you like? red = 1, blue = 2 and green =3"))
i=1
x=65
#Draws the desired number of squares.
while i < f:
i=i+1
x=x*1.05
print ("minimise this window ASAP")
if c==1:
turtle.pencolor("red")
elif c==2:
turtle.pencolor("blue")
elif c==3:
turtle.pencolor("green")
else:
turtle.pencolor("black")
if n==1:
turtle.fillcolor("red")
elif n==2:
turtle.fillcolor("blue")
elif n==3:
turtle.fillcolor("green")
else:
turtle.fillcolor("white")
turtle.bk(x)
turtle.rt(90)
turtle.bk(x)
turtle.rt(90)
turtle.bk(x)
turtle.rt(90)
turtle.bk(x)
turtle.rt(90)
turtle.up()
turtle.rt(9)
turtle.down()
By the way: I am on version 3.2!
Set turtle.speed("fastest").
Use the turtle.mainloop() functionality to do work without screen refreshes.
Disable screen refreshing with turtle.tracer(0, 0) then at the end do turtle.update()
Python turtle goes very slowly because screen refreshes are performed after every modification is made to a turtle.
You can disable screen refreshing until all the work is done, then paint the screen, it will eliminate the millisecond delays as the screen furiously tries to update the screen from every turtle change.
For example:
import turtle
import random
import time
screen = turtle.Screen()
turtlepower = []
turtle.tracer(0, 0)
for i in range(1000):
t = turtle.Turtle()
t.goto(random.random()*500, random.random()*1000)
turtlepower.append(t)
for i in range(1000):
turtle.stamp()
turtle.update()
time.sleep(3)
This code makes a thousand turtles at random locations, and displays the picture in about 200 milliseconds.
Had you not disabled screen refreshing with turtle.tracer(0, 0) command, it would have taken several minutes as it tries to refresh the screen 3000 times.
https://docs.python.org/2/library/turtle.html#turtle.delay
For reference, turtle being slow is an existing problem.
Even with speed set to max, turtle can take quite a long time on things like fractals.
Nick ODell reimplemented turtle for speed here: Hide Turtle Window?
import math
class UndrawnTurtle():
def __init__(self):
self.x, self.y, self.angle = 0.0, 0.0, 0.0
self.pointsVisited = []
self._visit()
def position(self):
return self.x, self.y
def xcor(self):
return self.x
def ycor(self):
return self.y
def forward(self, distance):
angle_radians = math.radians(self.angle)
self.x += math.cos(angle_radians) * distance
self.y += math.sin(angle_radians) * distance
self._visit()
def backward(self, distance):
self.forward(-distance)
def right(self, angle):
self.angle -= angle
def left(self, angle):
self.angle += angle
def setpos(self, x, y = None):
"""Can be passed either a tuple or two numbers."""
if y == None:
self.x = x[0]
self.y = x[1]
else:
self.x = x
self.y = y
self._visit()
def _visit(self):
"""Add point to the list of points gone to by the turtle."""
self.pointsVisited.append(self.position())
# Now for some aliases. Everything that's implemented in this class
# should be aliased the same way as the actual api.
fd = forward
bk = backward
back = backward
rt = right
lt = left
setposition = setpos
goto = setpos
pos = position
ut = UndrawnTurtle()