How to use multiprocessing or threading with tkinter - python

I am trying to make a simple space invaders game and a problem I have run into is getting things t happen at the same time. I have binded the shooting action to the canvas of the game so that when you click a function is called. I would like it so that this function can be called multiple times at once so that multiple "lasers/bullets" can be seen on the screen at any one time. At the minute when you click and a "laser/bullet" is already on screen, the previous one disappears and a new one appears. CODE:
class Game1():
def __init__(self, xcoord1=380, ycoord1=550, xcoord2=400, ycoord2=570):
self.Master = Master
self.Master.geometry("800x600+300+150")
Game1Canvas = Canvas(self.Master, bg="black", height=600, width=800)
Game1Canvas.place(x=0, y=0)
self.Canvas = Game1Canvas
self.Canvas.bind("<Button-1>", self.Shoot)
self.Ship = self.Canvas.create_rectangle(self.xcoord1, self.ycoord1, self.xcoord2, self.ycoord2, fill = "red")
def Shoot(self):
self.LaserLocation = 0
for self.LaserLocation in range(0 , 112):
Master.after(1, self.Canvas.create_rectangle(self.xcoord1, self.ycoord1 - (self.LaserLocation * 5), self.xcoord2 - 5, self.ycoord2 - (self.LaserLocation * 5), fill = "pink", tag=str(CurrentTag)))
Master.update()
self.Canvas.delete(str(CurrentTag))
This is a much more "dumbed" down version of the code at the minute because I've been trying a bunch of different ways to get this working and it's a mess. I am aware of the multiprocessing and threading imports and I have tried them both but am unable to get them working for my code. If someone could reply back with a solution I would be very grateful. Cheers.

You don't need to use multithreading or multiprocessing. You also don't need (nor want) to be drawing new rectangles every millisecond, or multiple times per millisecond.
The solution is to have your Shoot function merely create a single rectangle, and add it to a list. Then, using a simple animation mechanism, iterate over the list and move each bullet up one or two pixels. You do this by creating a function that calls itself every 20-30 ms.
The solution looks something like this:
def Shoot(self):
laser = self.Canvas.create_rectangle(...)
self.lasers.append(laser)
def do_animation(self):
# make a copy of the list of lasers to iterate
# over, so we can remove items from the original
# list when they go off screen
lasers = self.lasers[:]
for laser in lasers:
# get current coordinates of this laser
(x0,y0,x1,y1) = self.canvas.bbox(laser)
if x1 < self.canvas.winfo_height():
# if it is not off screen, move it up
self.canvas.move(laser, 0, -10)
else:
# if it IS off screen, delete it
self.canvas.delete(laser)
self.lasers.remove(laser)
self.after(30, self.do_animation)
The above will move the lasers every 30 milliseconds (about 33 fps).

Related

Python Processing: How to call multiple of the same class

I am a noob at Python and I am having trouble with this problem. I am trying to create a raindrop scene with the 'drop' class. Right now, the code only calls one drop at a time. How can I call multiple drops at once?
Here is my code:
import random
class drop():
def __init__(self):
# where the drop starts
self.x = random.randint(20,480)
self.y = 0
#how fast the drop falls
self.yspeed = 5
def fall(self):
self.y = self.y+self.yspeed
def show(self):
# color of drop
stroke(1,1,1)
fill(1,1,1)
strokeWeight(1)
line(self.x,self.y,self.x,self.y+10)
def setup():
size(500,500)
global d
d = drop()
def draw():
background(255,255,255)
d.fall()
d.show()
You can create as many instances of a class as you want. For example the following code creates two drops.
d1 = drop()
d2 = drop()
You can also create a list of drops like this -
drops = []
drops.append(drop())
drops.append(drop())
drops[0].fall()
drops[1].show()
I'm a bit rusty with Python, But I would suggest changing draw to accept an array of elements.
So for example, in setup and can generate an array of droplets by doing:
def setup():
size(500,500) #Idk what this does, I assume its another function somewhere else in your code
droplets = [] #The array of droplets we want to render
for x in range(6): #6 is the number of droplets we want.
droplets.append(drop())
#Pass droplets into draw.
def draw(entities):
background(255,255,255) #Again, IDK what this does but its in your code. I assume it is another function somewhere.
for droplet in entities: #Goes through ALL the droplets in the list you passed.
droplet.fall() #Calls these methods on every droplet in the array.
droplet.show()
This is untested, but from here you would somehow pass the droplets array into draw and BOOM you have a bunch of droplets being rendered every draw cycle. I would suggest NOT using global variables. Its just bad practice. You should have another function somewhere that runs all of this, I assume on a time for the draw cycle.

PysimpleGUI - simple animation

Thank you for reading this.
I'm working on a simple animation that is based on one of two examples from the PysimpleGUI cookbook. The attached code, of course, it is not doing anything. I've looked through many examples trying to figure out how to update the canvas but without success.
My first attempt was based on the sine wave plot example. I have an endless while loop and a display function. The display on the graph area shows the first iteration through the loop but is never updated after that.
The display function contains:
graph.DrawCircle((i,j), 5, line_color='black', etc
A second related question, should I be using the canvas or the graph method (as in the sine wave plot example), or doesn't it matter?
I don't want to overwhelm the reader with too much code. If I can get the following to work then I may have a good chance with the real code.
import PySimpleGUI as sg
import time
layout = [
[sg.Canvas(size=(100, 100), background_color='red', key= 'canvas')],
[sg.T('Change circle color to:'), sg.Button('Red'), sg.Button('Blue')]
]
window = sg.Window('Canvas test')
window.Layout(layout)
window.Finalize()
canvas = window.FindElement('canvas')
cir = canvas.TKCanvas.create_oval(50, 50, 100, 100)
while True:
event, values = window.Read()
'''
if event is None:
break
if event == 'Blue':
canvas.TKCanvas.itemconfig(cir, fill="Blue")
elif event == 'Red':
canvas.TKCanvas.itemconfig(cir, fill="Red")
'''
# this is the part that I need to sort out
for i in range(10):
if i % 2 == 0:
canvas.TKCanvas.itemconfig(cir, fill="Blue")
else:
canvas.TKCanvas.itemconfig(cir, fill="Red")
time.sleep(1)
I discovered the answer and that is, window.Read(timeout = 0).
In order for changes to show up in a window after making changes, you much either call Read or Refresh. I think all you need to do is down in your bottom loop, add the line:
window.Refresh()
From the docs at http://www.PySimpleGUI.org:
Refresh()
Cause changes to the window to be displayed on the screen.
Normally not needed unless the changes are immediately required or if
it's going to be a while before another call to Read.

How do I get an image to move until it gets to a specific point in the window?

Right now I have some code in Python using Zelle graphics that has a robot image and some text scrolling upward. When the user clicks on the window, the program ends.
What I'm trying to do is have pieces of the robot image come in from opposite sides of the window (The head moving down from the top, the eyes moving up from the bottom, and the ears moving in from the left and right). They would stop moving once they come together to form the completed image.
After that, I want the text to come in from the left hand side and stop once it gets to the center of the screen, underneath the image. I don't want the animation to start until the user clicks on the window.
This is what my code looks like so far:
from graphics import *
from random import randint
from time import sleep
screen=GraphWin("Logo",500,700);
screen.setBackground("#b3e2bf");
#---------logo-----------
robotHead=Image(Point(250,250),"robotHead.png");
robotHead.draw(screen);
robotEyes=Image(Point(250,310),"robotEyes.png");
robotEyes.draw(screen);
robotLeftEar=Image(Point(150,290),"robotLeftEar.png");
robotLeftEar.draw(screen);
robotRightEar=Image(Point(350,290),"robotRightEar.png");
robotRightEar.draw(screen);
#--------credits-----------
programmer=Point(250,515);
lineOne=Text(programmer,"Programmer Name");
lineOne.draw(screen);
className=Point(250,535);
lineTwo=Text(className,"CSC 211");
lineTwo.draw(screen);
date=Point(250,555);
lineThree=Text(date,"November 30th, 2017");
lineThree.draw(screen);
copyrightName=Point(250,575);
lineFour=Text(copyrightName,"Copyright Line");
lineFour.draw(screen);
while screen.checkMouse()==None:
robotHead.move(0,-1);
robotEyes.move(0,-1);
robotLeftEar.move(0,-1);
robotRightEar.move(0,-1);
lineOne.move(0,-1);
lineTwo.move(0,-1);
lineThree.move(0,-1);
lineFour.move(0,-1);
sleep(0.1);
screen.close();
You can use robotHead.anchor.getY() and robotHead.anchor.getX() to check current position and move it only if it is not on expected position.
You can also use variables with True/False to control which element has to move (or even which element to show on screen). At start you should have moveHead = Trueand moveLineOne = False. When head is on expected position then you can change moveHead = False and moveLineOne = True.
BTW: graphics has function update(frames_per_second) to control speed of animation and you don't need sleep(). Besides update() executes some functions which graphics may need for correct work. (graphics documentation: Controlling Display Updates (Advanced))
Speed 25-30 FPS is enough for human eye to see smooth animation (and it may uses less CPU power than 60 FPS).
Simple example
from graphics import *
from random import randint
from time import sleep
screen = GraphWin("Logo", 500, 700)
# --- objects ---
robot_head = Image(Point(250, 250), "robotHead.png")
robot_head.draw(screen)
programmer = Point(250, 515)
line_one = Text(programmer, "Programmer Name")
#line_one.draw(screen) # don't show at start
# --- control objects ---
move_robot_head = True # move head at start
move_line_one = False # don't move text at start
# --- mainloop ---
while not screen.checkMouse(): # while screen.checkMouse() is None:
if move_robot_head: # if move_robot_head == True:
# check if head is on destination position
if robot_head.anchor.getY() <= 100:
# stop head
move_robot_head = False
# show text
line_one.draw(screen)
# move text
move_line_one = True
else:
# move head
robot_head.move(0, -10)
if move_line_one: # if move_line_one == True:
# check if head is on destination position
if line_one.anchor.getY() <= 150:
# stop text
move_programmer = False
else:
# move text
line_one.move(0, -10)
# control speed of animation
update(30) # 30 FPS (frames per second)
# --- end ---
screen.close()

How to display text for only a certain amount of time?

I'm looking to display a message on the screen when a player wins, and get the text to display for 5 seconds, and then go back to the main menu start screen. Using the time.delay function however, my screen pauses and then displays the text in a flash, but then immediately goes to the startscreen. Is there a more efficient way of getting the text to be displayed for long enough to be read?
Below is the function I use to actually display the message:
def winnerPlayerOne():
screen.fill(PINK)
winnerP1Message = winnerFont.render("Congrats Player 1. You Win!", True, WHITE)
screen.blit(winnerP1Message, ((400 - (winnerP1Message.get_width()/2)),(300 - (winnerP1Message.get_height()/2))))
pygame.display.update()
pygame.time.delay(5000)
startscreen()
And below this is how I call this function, within the main loop:
if playeroneScore == 5:
winnerPlayerOne()
if playertwoScore == 5:
winnerPlayerTwo()
Any help would be greatly appreciated!
Benjamin's answer probably will work fine in your case. But if you want something that doesn't interfere with your game's visuals, I would consider setting a timer like so...
WITHIN GAME LOOP:
if gameWonScreen:
screen.fill(PINK)
winnerP1Message = winnerFont.render("Congrats Player 1. You Win!", True, WHITE)
screen.blit(winnerP1Message, ((400 - (winnerP1Message.get_width()/2)),(300 - (winnerP1Message.get_height()/2))))
timer = timer + elapsed/1000
elapsed = fpsClock.tick(FPS)
if timer > timeWinScreen:
gameWonScreen = false
Initialize 'timeWinScreen' to the desired message duration at the start of the application and set 'timer' to '0' and gameWonScreen to 'true' when the player wins. Using elapsed = fpsClock.tick(FPS) will hold the time value since the last tick. You don't need to use it for this process (you could just use a fraction of your FPS) but using 'elapsed' is good practice because it helps with smoothing animations of certain objects.
Try out pygame.time.wait(5000). It should behave more in the way you are expecting. It does prevent any code running in the background as well, but that didn't seem like it would be an issue for your use case.
Try this:
Remember that in the class below, time is not the way you measure time in pygame. It is the number of loop happened in the main loop.
class disp_txt:
def __init__(self,text,time,destination):
self.text = text
self.time = time
self.destination = destination
self.shfnt = pygame.font.SysFont("comicsens",30,False)
self.shtxt = self.shfnt.render(self.text,0,(255,0,0))
def show(self,surface):
if self.time>0:
surface.blit(self.shtxt,self.destination)
self.time -= 1
hint = disp_txt("text",100,(100,400)) #example
hint.show(screen) #in the main loop or where you are drawing

In Python, how can I execute two Turtle commands simultaneously?

As in having two Turtles moving at once. For example, I import two turtles, then try to have both of them move forward alongside each other. How can I do this?
bob = turtle.Turtle()
john = turtle.Turtle()
def move_turtles(ammount):
for i in range(ammount // 10):
bob.forward(10)
john.forward(10)
move_turtles(100)
There's no way to move them at the same time, although you can use something like that. It moves the turtles by 10 points each, so it gives the impression that they are moving together, but they are actually moving separately by little ammounts. It repeats the operation (ammount //10) times, and moves 10 in each iteration, so if you were to give 50 as an input, it would move 5 times 10 points, resulting in 50. You can then customize the function to move by a little a turtle so they don't overlap and so on.
You can move multiple turtles independently at the same time using timer events -- you can even have them move at different rates, both in time and space:
import turtle
turtle.setworldcoordinates(0, -100, 100, 100)
bob = turtle.Turtle(shape="turtle")
bob.penup()
bob.sety(20)
john = turtle.Turtle(shape="turtle")
john.penup()
john.sety(-20)
def move_bob():
bob.forward(1)
if bob.xcor() < 90:
turtle.ontimer(move_bob, 75)
def move_john():
john.forward(2)
if john.xcor() < 90:
turtle.ontimer(move_john, 100)
move_bob()
move_john()
turtle.exitonclick()
Other folks also use threads to achieve this but timer events are built into the turtle module.
There is a way to control frames using the Turtle module - you need to change the screen attributes.
screen = turtle.Screen()
screen.tracer(0)
This method makes all turtle movement invisible until you run screen.update(), and then every turtle will be visually updated at the same time on the screen. In your case, you can write screen.update() after the movement of both turtles, and they will appear to move at the same time.

Categories

Resources