Python: Keyboard input without repeat delay - python

For those who don't know, repeat delay is the slight pause between, when holding down a key, the letter first appearing and it repeating. This might be a useful feature when typing, however when you start to write games, it becomes very annoying. An example is when you need to move a character; it will move a tiny bit, pause, and then start to move again. Tkinter code:
ball = canvas.create_rectangle(50, 50, 100, 100)
def move():
canvas.move(ball, 0, 3)
canvas.bind_all("<space>", move)
If space is pressed, the ball will move down 3 pixels, pause, and then start moving normally. I was wondering if there is any way to avoid that pause, for example a module that reads directly from the keyboard, and not the windows-processed keyboard. I know that it is possible to "cheat" by, for example, automatically running the function when you expect the delay to occur; sadly that is inaccurate and can result in choppy movement. Thanks in advance

Make a recursion loop for while it is pressed. Or that's how I do it, at least.
moving = False
def move():
global moving
moving = True
def stop_moving():
global moving
moving = False
def myloop():
global moving
if moving == True:
canvas.move(ball, 0, 3)
root.after(1, myloop)
root.bind('<space>', lambda e: move())
root.bind('<KeyRelease-space>', lambda e: stop_moving())
just make sure you call your loop once before root.mainloop()
#like this
root.after(1, myloop)
root.mainloop()

Related

Why is function to move canvas object not working

The objective is to move alien1, atarts from 0,0 then moves all the way to the right, goes down and then all the way to the left, and then down.
from tkinter import *
import random
def enemigos():
global Enemigos #Enemigos downloads the image for alien1
n = random.randint(1,3)
if n == 1:
def movalien1():
alien1 = CanvasJuego.create_image(0,0, anchor = NW, image = Enemigos[0], tags= ('alien1'))
RIGHT1 = True
CoordsAlien1 = CanvasJuego.coords(alien1)
if (CoordsAlien1[0] < 1000 and RIGHT1==True):
CanvasJuego.coords(alien1, CoordsAlien1[0]+5, CoordsAlien1[1])
if ((CoordsAlien1[0]+5)==1000):
RIGHT1 = False
CanvasJuego.coords(alien1, CoordsAlien1[0], CoordsAlien1[1]+50)
elif (CoordsAlien1[0]>0 and RIGHT1==False):
CanvasJuego.coords(alien1, CoordsAlien1[0]-5, CoordsAlien1[1])
if ((CoordsAlien1[0]-5)==0):
RIGHT1 = True
CanvasJuego.coords(alien1, CoordsAlien1[0], CoordsAlien1[1]+50)
def rec():
movalien1()
root.after(20,rec)
root.after(20,movalien1())
Alien1 does appear at (0,0), but it won't move.
The problem is that you create a new "alien" every 20 milliseconds. You should be creating alien1 exactly once outside of movalien1. What is happening is that you create it at 0,0, then move it to 5.0. The alien is at 5,0. The next time through the loop you create a new alien at 0,0, and then move it to 5,0. You keep creating new aliens over and over and moving the new alien to 5,0.
Also, you can use the move method to move an item instead of adjusting its coordinates.
Finally, even though it doesn't actually matter in this code, you are calling after incorrectly here: root.after(20, movealien1()). It needs to be either root.after(20, movealien1) or just directly call movealien1() without using after.

I need the "if" to go once but not end the loop

I'm making a program to control the mouse with WASD but whenever I press the button the mouse just moves to the top of the screen when I need it to move slightly then be able to move again if I press the key again
I've tried to have the loop turn off then back on but it just ends the loop before it can switch back on.
import keyboard
from pynput.mouse import Controller
mouse = Controller()
repeat = True
while repeat:
if keyboard.is_pressed('w'):
mouse.move(0, -5)
if keyboard.is_pressed('s'):
mouse.move(0, 5)
I need it to move up slightly when I press w but stop when it isn't pressed so I can press it again.
You might want your keyboard not to be so sensitive, so you may need to sample the pressing time. sleep can help in this case. In the code below, the program will sleep for 0.2 seconds every time it enters while loop.
import keyboard
import time
repeat = True
while repeat:
time.sleep(0.2)
if keyboard.is_pressed('w'):
mouse.move(0, -5)
if keyboard.is_pressed('s'):
mouse.move(0, 5)
# do something
EDIT: I was misunderstanding what you want, just sleep is enough for this. The problem is because your computer does everything too fast, so a while loop might be completed in a microsecond or less. So it sees that you are pressing the key for too long (though you think you did fast) then it just repeats the loop until you release the key. That's why your mouse always moves to the top.
Sampling is the way to tell computer "if you've done, then sleep, don't do anything else".
Another way is to count how many times it loops (ie, scaling), but it looks like dump since the computer has to do worthless things.
import keyboard
from pynput.mouse import Controller
mouse = Controller()
repeat = True
w_count, s_count = 0, 0
while repeat:
if keyboard.is_pressed('w'):
w_count += 1
if w_count == 10: # i'm scaling it
mouse.move(0, -5)
w_count = 0
if keyboard.is_pressed('s'):
s_count += 1
if s_count == 10: # i'm scaling it
mouse.move(0, 5)
s_count = 0
There are also other ways: measure the pressing time, edge detection (detect key release)... You can search about "software debouncing" for more ideas and find one best fits your requirement.

Getting Turtle in Python to recognize click events

I'm trying to make Connect 4 in python, but I can't figure out how to get the coordinates of the screen click so I can use them. Right now, I want to draw the board, then have someone click, draw a dot, then go back to the top of the while loop, wipe the screen and try again. I've tried a couple different options but none have seemed to work for me.
def play_game():
"""
When this function runs, allows the user to play a game of Connect 4
against another person
"""
turn = 1
is_winner = False
while is_winner == False:
# Clears screen
clear()
# Draws empty board
centers = draw_board()
# Decides whose turn it is, change color appropriately
if turn % 2 == 0:
color = RED
else:
color = BLACK
# Gets coordinates of click
penup()
onscreenclick(goto)
dot(HOLE_SIZE, color)
turn += 1
As well intentioned as the other answers are, I don't believe either addresses the actual problem. You've locked out events by introducing an infinite loop in your code:
is_winner = False
while is_winner == False:
You can't do this with turtle graphics -- you set up the event handlers and initialization code but turn control over to the main loop event handler. My following rework show how you might do so:
import turtle
colors = ["red", "black"]
HOLE_SIZE = 2
turn = 0
is_winner = False
def draw_board():
pass
return (0, 0)
def dot(color):
turtle.color(color, color)
turtle.stamp()
def goto(x, y):
global turn, is_winner
# add code to determine if we have a winner
if not is_winner:
# Clears screen
turtle.clear()
turtle.penup()
# Draws empty board
centers = draw_board()
turtle.goto(x, y)
# Decides whose turn it is, change color appropriately
color = colors[turn % 2 == 0]
dot(color)
turn += 1
else:
pass
def start_game():
"""
When this function runs, sets up a new
game of Connect 4 against another person
"""
global turn, is_winner
turn = 1
is_winner = False
turtle.shape("circle")
turtle.shapesize(HOLE_SIZE)
# Gets coordinates of click
turtle.onscreenclick(goto)
start_game()
turtle.mainloop()
Run it and you'll see the desired behavior you described.
I'm assuming that your using Turtle in python(hence the name.)
If that's the case, Here's a link to a helpful post: Turtle in python- Trying to get the turtle to move to the mouse click position and print its coordinates
I know, i know. I hate just link answers as much as the next guy. But The post I gave a link to can probably do a much better job of answering your question than I can.
~Mr.Python
Assuming you're using turtle as mentioned in your title:
>>> import turtle
>>> help(turtle.onscreenclick)
Help on function onscreenclick in module turtle:
onscreenclick(fun, btn=1, add=None)
Bind fun to mouse-click event on canvas.
Arguments:
fun -- a function with two arguments, the coordinates of the
clicked point on the canvas.
num -- the number of the mouse-button, defaults to 1
Example (for a TurtleScreen instance named screen)
>>> onclick(goto)
>>> # Subsequently clicking into the TurtleScreen will
>>> # make the turtle move to the clicked point.
>>> onclick(None)
That means that your callback function, which you have apparently named goto, will take two parameters, an X and Y location.
import turtle
def goto(x, y):
print('Moving to {}, {}'.format(x,y))
turtle.goto(x, y)
turtle.onscreenclick(goto)
turtle.goto(0,0)
Each click that you make will move the turtle to a different position. Note that turtle already has an event loop - you don't need one of your own. Just respond to the clicks.
basically, you need to add an 'x' and 'y' parameter for the onclick and onscreenclick functions. You don't need to use them, they're just dummy params. After filling those out the clicks will work no problem:
window = turtle.Screen()
This function uses the x, y params because i'm saving the clicks in order to specify an area to fill with turtles
def on_left_click_save_coordinates(x, y):
global counter, Fill_COORS1, Fill_COORS2
counter += 1
print(x, y)
if counter == 1:
Fill_COORS1 = (x, y)
elif counter == 2:
Fill_COORS2 = (x, y)
counter = 0
This one doesn't use the x,y params because they are dummies, this one is used to allow multiple options, one of which exits, another tells the turtle to fill in the specified area saved in clicks above.
def on_right_click_open_options(x, y):
global going
last_color = options(window, filler, Fill_COORS1, Fill_COORS2, LAST_BLOCK_USED)
if type(Last_COLOR) == type(bool):
going = True
window.onscreenclick(on_click, btn=1)
window.onscreenclick(open_options, btn=3)
This is an example of a snippet of my code. hope this helps.
btn 3 refers to the right click
btn 1 is the default and isn't necessary to specify and refers to left click
btn 2 is the scroll wheel click, not scroll.
and sorry if this isn't formatted the best, it's my first time posting to stackoverflow. Hope it helps nonetheless

Stop time without freezing the program

In my pong game, whenever someone scores (the ball goes out to the left or to the right) the ball position is reset to the middle of screen and it waits one second before moving. In that one second I have a little animation going on.
My problem is this: if I pause the game in the middle of the animation, even though none of the objects are updated and only the pause text is drawn, time keeps rolling in. And if I wait time enough, the animation just stops right after I unpause the game. Here's what I mean. This is the ball update:
def update(self, dt):
now = pygame.time.get_ticks() / 1000
# if time elapsed since the ball got out >= BALL_WAIT_TIME
if now - self._spawn_time >= BALL_WAIT_TIME:
self.rect = self.calcnewpos(dt)
self.handle_collision()
# spawn animation
else:
step = 255 / (FPS * BALL_WAIT_TIME)
value = int(self._frame * step)
rgb = (value, value, value)
self._draw_ball(rgb)
self._frame += 1
From http://pygame.org/docs/ref/time.html#pygame.time.get_ticks:
pygame.time.get_ticks()
Return the number of millisconds since pygame.init() was called. Before pygame is initialized this will always be 0.
So even though nothing is drawn or updated while the game is paused, pygame.time.get_ticks() will still return the time elapsed since pygame.init. How can I solve this? Sorry if that is a little hard to understand, I'll post the rest of the code if needed.
Well it looks as though you're just subtracting the time that some event occurred from the current time. If that's your method for checking how much time has elapsed since the event, then it's not going to matter if the game has been paused. If the event happens and you then pause the game for 10 minutes, it's always going to have been 10 minutes since the event happened.
So with that in mind, you need some way to only count time when the game is active. Perhaps the ball could have an attribute that says how long since the ball got out, and you only increase it if the game isn't paused.
Edit: something like:
class Ball:
def spawn(self):
self.sinceSpawn = 0
def update(self, dt):
if not gamePaused:
self.sinceSpawn += dt
if self.sinceSpawn >= BALL_WAIT_TIME:
pass #Do something here

Running a loop alongside a Tkinter application

I am a high school programming student and I have a small question. I have been tasked with writing a simple game in Tkinter where an icicle falls from the ceiling and you have to avoid it with your mouse. Simple enough. However, I have hit an issue. Whenever I run a loop in a Tkinter application, it won't open. I've tried with a for loop that pauses every .5 seconds using time.sleep() and the window opens as soon as the loop finishes. Is there some special thing I need to do to make loops work in Tkinter?
from Tkinter import *
import time
import random
class App:
def __init__(self, parent):
self.frame = Frame(root, bg= '#1987DF', width=800, height=800)
self.frame.bind("<Motion>", self.motionevent)
self.frame.pack()
#self.run()
def randhex(self):
b = "#"
for i in range(1, 7):
a = random.randint(0, 15)
if a == 10:
a = "A"
elif a == 11:
a = "B"
elif a == 12:
a = "C"
elif a == 13:
a = "D"
elif a == 14:
a = "E"
elif a == 15:
a = "F"
b = b+str(a)
return b
def motionevent(self, event):
xpos, ypos, bg = event.x, event.y, self.randhex()
str1 = "X : %d Y : %d BG : %s" % (xpos, ypos, bg)
root.title(str1)
x,y, delta = 100, 100, 10
self.frame.config(bg=bg)
def run(self):
for i in range(0, 10):
time.sleep(.5)
print 'i'
self.frame.config(bg=self.randhex())
root = Tk()
app = App(root)
root.mainloop()
Currently all it is supposed to do is change the background when the mouse moves. When the line in init that says self.run() is uncommented it will print 'i' 10 times then the window will open. Help?
Writing an event based program is not the same as writing traditional programs. There is already an infinite loop running, and like any infinite loop, if you place another loop inside, the outer loop can't continue until the inner loop finishes. Since the outer loop is what causes the screen to refresh and events to be processed, inner loops effectively freeze your app until they are done.
Since there is already a loop running you don't need to create another loop. All you need to do is add little jobs to the event queue one at a time. Each job is, in effect, one iteration of your inner loop.
For example, if you wanted to write an inner loop like this:
for i in range(10):
print "i:", i
... you would instead add an event to the event queue and each time the event loop iterates (or more precisely, each time it finishes processing any other events) it will do one iteration of your loop. You do it like this:
def do_one_iteration(i):
print "i:", i
if i < 9:
root.after_idle(do_one_iteration, i+1)
Then, after the first time you call do_one_iteration, it will place the next iteration of itself on the event queue, and continue to do so until it decides it is done.
Typically you would call do_one_iteration when the user presses a button (eg: the "start" button). Call it once, then it does one bit of work (ie: moving the icicle down a couple of pixels) and then reschedules itself.
In game development you might have a function called update_display which is in charge of redrawing everything. It could, for example, subtract 1 from the Y coordinate of each icicle. You can add bindings for the left and right arrows to move the player (by incrementing or decrementing the X coordinate), and your update function would use these new coordinates to redraw the player.
By the way, you can slow down your program by using after instead of after_idle to call the function after a slight delay. This delay can be used to control the frame rate. For example, assuming the update is nearly instantaneous (and it probably will be in your case), calling after with an argument of 41 (milliseconds) yields a framerate of approximately 24 fps (24 frames times 41 milliseconds equals 984 milliseconds, or roughly 24 frames per second)
It may sound complicated but it's really pretty easy in practice once you do it once or twice.

Categories