Python onkey not stopping loop - python

def startgame():
start.state = False
print start.state
def restart():
end.state = False
start.state = True
print game.state, end.state
s.listen()
s.onkey(startgame, "Return")
s.onkey(restart, 'r')
# This Loop stops when you hit Enter
while start.state:
start.enter()
s.reset()
# I tried repeating it here but it doesn't work
while end.state:
end.enter()
s.reset()
game.state = 'playing'
Both loops are nested in a main while loop but the second one is nested in another while loop (If that helps) so it would look something like this
while True:
while start.state:
start.flash()
s.reset()
while True:
# main game code here
while end.state:
end.flash()
s.reset()
game.state = 'playing'
break
I simply want it to show the end screen and have 'Press r to play again' flash on screen until the player hits r and then it should restart the game and go back to the start screen. The end.state variable wont update during the while loop, but start.state does during its while loop.

Your drawn out (in time) while loops have no place in an event-driven environment like turtle. In general, we need to control everything with events, specifically timer events. Let's rewrite your code as a state machine. (Since I don't have your object classes, entities like start.state become globals like start_state in my example code.)
from turtle import Screen, Turtle
start_state = True
end_state = False
def start_game():
global start_state
start_state = False
def end_game():
global end_state
if not start_state:
end_state = True
def restart_game():
global end_state, start_state
end_state = False
start_state = True
def flash(text):
turtle.clear()
turtle.write(text, align="center", font=('Arial', 24, 'bold'))
color = turtle.pencolor()
turtle.pencolor(turtle.fillcolor())
turtle.fillcolor(color)
def game_states():
if start_state:
flash("Start")
elif end_state:
flash("End")
else:
flash("Playing")
screen.ontimer(game_states, 500)
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.fillcolor(screen.bgcolor())
screen.onkey(start_game, 'Return')
screen.onkey(restart_game, 'r')
screen.onkey(end_game, 'e')
screen.listen()
game_states()
screen.mainloop()
The state machine rules are:
Start via <Return> -> Playing
Playing via <e> -> End and via <r> -> Start
End via <r> -> Start

Related

Detect NO KEY PRESSES with keyboard module python

I'm making a tkinter program and want to detect a key press and do something when there is, however if no key is pressed the keyboard.read_key() will not execute and not let any other code run until a key is pressed.
Is there an alternative I can use or a way to check if no key was pressed?
def on_press():
global running
global turn
if keyboard.read_key() == 'q':
ws.destroy()
elif keyboard.read_key() == 'a':
if turn:
print('turning off')
running = False
turn = False
else:
print('turning on')
running = True
turn = True
start()
else:
start()
def start():
global running
global turn
print('start')
if running:
print('clicking')
mouse.click(Button.left, cps_val)
time.sleep(1)
on_press()
on_press()
Key is no pressed for the most of the time. Because computer is fast and human is slow so I would assumed that that key is no pressed at the beginnig and I would run code at once (without checking if it no pressed). And I would run "clicking code" all time in while at the beginning and I would use keys only to pause this loop (key a) or exit this loop (key q) - without checking if key is no pressed.
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
def main():
global counter
print('start')
while running:
if turn:
counter += 1
print('clicking', counter)
#mouse.click(Button.left, cps_val)
time.sleep(1)
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
main()
If it has to run with tkinter then while loop will block tkinter (tkinter will freeze because it will have no time to get key/mouse events from system, send them to widgets, update widgets, and redraw widgets in window).
I would use tkinter.after(milliseconds, function) instead of sleep() and while and tkinter will have time to work.
import tkinter as tk
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
root.destroy()
def main():
print('start')
loop() # run first time at once
#root.after(1000, loop) # run first time after 1s
def loop():
global counter
if turn:
counter += 1
#print('clicking', counter)
label['text'] = f'clicking {counter}'
#mouse.click(Button.left, cps_val)
if running:
root.after(1000, loop) # run again after 1s
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
root = tk.Tk()
label = tk.Label(root, text='clicking 0')
label.pack()
main()
root.mainloop()

Why do I need the time.sleep(x) function in this code for it to work?

from turtle import Screen, Turtle
import time
import snake
MOVE_DISTANCE = 20
UP = 90
DOWN = 270
LEFT = 180
RIGHT = 0
class InitialSnake:
def __init__(self):
self.number_x = 0
self.snake_first = []
self.create_snake()
self.actual_snake = self.snake_first[0]
def create_snake(self):
for number in range(3):
snake = Turtle(shape="square")
snake.penup()
snake.color("white")
snake.goto(self.number_x, 0)
self.number_x -= 20
self.snake_first.append(snake)
def move(self):
for segments_num in range(len(self.snake_first) - 1, 0, -1):
self.snake_first[segments_num].goto(self.snake_first[segments_num - 1].xcor(),
self.snake_first[segments_num - 1].ycor())
self.snake_first[0].forward(MOVE_DISTANCE)
def up(self):
if self.actual_snake.heading() != DOWN:
self.snake_first[0].setheading(UP)
def down(self):
if self.actual_snake.heading() != UP:
self.snake_first[0].setheading(DOWN)
def left(self):
if self.actual_snake.heading() != RIGHT:
self.snake_first[0].setheading(LEFT)
def right(self):
if self.actual_snake.heading() != LEFT:
self.snake_first[0].setheading(RIGHT)
my_screen = Screen()
my_screen.setup(width=600, height=600)
my_screen.bgcolor("black")
my_screen.title("Snake Game")
my_screen.tracer(0)
snake_1 = snake.InitialSnake()
#snake_here.create_snake()
game_is_on = True
my_screen.listen()
my_screen.onkey(snake_1.up, "Up")
my_screen.onkey(snake_1.down,"Down")
my_screen.onkey(snake_1.left,"Left")
my_screen.onkey(snake_1.right,"Right")
while game_is_on:
my_screen.update()
time.sleep(0.1)
snake_1.move()
my_screen.exitonclick()
I did not really understand the concept of the tracer and the update and how this is linked to the sleep function. I understand that when the tracer is called and turned off, it will not refresh the screen until you call the update() function. But shouldn't it still work without time.sleep(0.1) since this is just creating a short delay before the next functions get called. Can someone help me please to understand this? Thanks in advance (:
If you use print() to see snake position
while game_is_on:
my_screen.update()
snake_1.move()
print(snake_1.snake_first[0].ycor(), snake_1.snake_first[0].xcor())
then you will see it moves very, very fast and it left screen - and you simply can't see it.
On my very old computer after few milliseconds its positon was (0, 125700). Probably on the newest computer it would be much bigger value.
So this code use sleep() to slow down snake and to move it with the same speed on all computers.
BTW:
I would use turtle method to repeate code
def game_loop():
snake_1.move()
my_screen.update()
my_screen.ontimer(game_loop, 100) # repeate after 100ms (0.1s)
# start loop
game_loop()
because sleep may blocks some code and ontimer was created for non-blocking delay.

How to pause and unpause a turtle program - python

So i have an algorithm that can find the best path to exit a maze. However the problem is i am unable to implement a keypress button to pause and to unpause the algorithm using the spacebar as the keypress. For example i expect the algorithm to pause(not exit the window) when i hit the spacebar and when i hit the spacebar again it should continue running. It should work like a toggle switch basically.
The codes are as follows:
# Import libraries
import turtle
import sys
import numpy as np
import os
from collections import deque
# Configure some basic settings for the map
bs = turtle.Screen() # Define the turtle screen
bs.bgcolor("black") # Set the background colour
bs.setup(width=0.9, height=0.9) # Setup the dimensions of the working window
.
.
.
.
.
.
.
in_motion = False
def stopMovement():
global in_motion
if in_motion == True:
in_motion == False
else:
in_motion == True
while True:
if not in_motion:
# Setting up classes
turtle.title("Breadth first search algorithm ; steps taken: 0")
title = title()
building = Building()
road = Road()
start = Start()
end = End()
searcher = Searcher()
route = Route()
sprite = sprite()
# Setting up lists
walls = []
path = []
routeCoords = []
visited = set()
frontier = deque()
solution = {}
# Activation
setupMaze(mazeSet)
search(start_x, start_y)
correctRoute(end_x, end_y)
sprite.moveSprite(end_x, end_y)
else:
bs.update()
bs.listen()
bs.onkeypress(stopMovement, 'space')
The if not in_motion part basically runs the functions for the algorithm. I have already tried implementing the switch to pause and unpause but it still does not seem to work. Help will be greatly appreciated!
Typo:
def stopMovement():
global in_motion
if in_motion == True:
in_motion == False
else:
in_motion == True
should be
def stopMovement():
global in_motion
if in_motion == True:
in_motion = False # here
else:
in_motion = True # and here
Two equal signs too much and such a big effect :)
Not having runnable code to debug, I'm going to suggest that in addition to the = vs. == typos that #Justlearnedit points out, I believe that your while True: is keeping the event handler from seeing the keyboard input as infinite loops have no place in an event-driven world. We should use timed events instead and I would reformulate your code something like:
# Import libraries
from turtle import Screen, Turtle
# ...
# Configure some basic settings for the map
screen = Screen() # Define the turtle screen
screen.bgcolor('black') # Set the background colour
screen.setup(width=0.9, height=0.9) # Setup the dimensions of the working window
# ...
in_motion = False
def flipMovement():
global in_motion
in_motion = not in_motion
def running():
if not in_motion:
screen.update()
else:
screen.title("Breadth first search algorithm ; steps taken: 0")
# Set up classes
title = title()
building = Building()
road = Road()
start = Start()
end = End()
searcher = Searcher()
route = Route()
sprite = sprite()
# Set up lists
walls = []
path = []
routeCoords = []
visited = set()
frontier = deque()
solution = {}
# Activation
setupMaze(mazeSet)
search(start_x, start_y)
correctRoute(end_x, end_y)
sprite.moveSprite(end_x, end_y)
screen.ontimer(running)
screen.onkeypress(flipMovement, 'space')
screen.listen()
running()

Run multiple functions using turtle.screen.onkey() in Python

For the code:
from turtle import Screen, Turtle, bgcolor
# --- functions ---
def delSec(string):
if len(string) == 1:
return "0" + string
else:
return string
def tick():
global milisecs, ticking
turtle.clear()
if milisecs < 0:
turtle.write("TIMER DONE", align='center', font=FONT)
ticking = False
return
else:
turtle.write(delSec(str(milisecs//(60*60*10)))+":"+delSec(str((milisecs%(60*60*10))//(60*10)))+":"+delSec(str((milisecs%(60*10))//10))+":"+str(milisecs%10), align='center', font=FONT)
## turtle.write(delSec(str(milisecs//(60*60)))+":"+delSec(str((milisecs%(60*60))//(60)))+":"+delSec(str((milisecs%(60))//1)), align='center', font=FONT)
if not paused:
milisecs -= 1
screen.ontimer(tick, 100)
def reset():
global milisecs, ticking, key_reset, key_pause, key_both
#global paused
print("reset")
screen.onkey(None, key_reset) # Disable event handler inside handler
screen.onkey(None, key_pause) # Disable event handler inside handler
screen.onkey(None, key_both)
milisecs = sum(time*10)
if not ticking:
ticking = True
tick()
#paused = False
screen.onkey(reset, key_reset) # Reenable event handler
screen.onkey(pause, key_pause) # Reenable event handler
screen.onkey(reset, key_both)
screen.onkey(pause, key_both)
def pause():
global paused
print("pause/unpause")
paused = not paused
# --- main ---
bgcolor('dodgerblue')
FONT = ("Arial", 60, "normal")
strings = input("Please enter the time: ").strip().split(' ')
time = [60 ** (len(strings) - index - 1) * int(unit) for index, unit in enumerate(strings)]
milisecs = -1
ticking = False
paused = False
key_reset = "r"
key_pause = "p"
key_both = "b"
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.color('white')
reset()
screen.listen()
screen.mainloop()
I want to both pause and reset the timer when I click the key "b". I tried doing this by running both functions reset and pause. However, as expected, it overwrites the first command of running the first function and only runs the second function when I click b (in this case, the function pause) I also tried the line screen.onkey(pause, reset, key_both), but that created an error.
Could someone help me figure out how to run multiple functions? I can't find any parameter of .onkey() where it doesn't disable previous key-based functions. Please help!
Frustratingly, what you're asking for is built-in to the mouse event functions:
onclick(fun, btn=1, add=None)
where the add argument is used to indicate whether fun overrides the current setting or simply adds another function to execute in this circumstance. However, this wasn't included on the various onkey*() event handlers.
Below is what I consider a generic way to go about this:
from turtle import Screen
KEY_RESET = 'r'
KEY_PAUSE = 'p'
KEY_BOTH = 'b'
def reset():
screen.onkey(None, KEY_RESET) # Disable event handlers inside handler
screen.onkey(None, KEY_BOTH)
print("reset")
screen.onkey(reset, KEY_RESET) # Reenable event handlers
screen.onkey(both, KEY_BOTH)
def pause():
screen.onkey(None, KEY_PAUSE) # Disable event handlers inside handler
screen.onkey(None, KEY_BOTH)
print("pause")
screen.onkey(pause, KEY_PAUSE) # Reenable event handlers
screen.onkey(both, KEY_BOTH)
def both():
reset()
pause()
screen = Screen()
reset()
screen.listen()
screen.mainloop()
such that I can run multiple functions when I click b with just one
line of code?
Yes, there's a hack we can do with lambda and a tuple but we shouldn't be thinking this way. "Just one line of code" should not be your goal at this point.

How can i break a for-loop with an key event?

I want to break a for-loop in Python 3.7.3 with a key event. I am trying to make a small game in the turtle graphics.
import turtle
from turtle import *
block1 = turtle.Turtle()
def space1():
block1.hideturtle()
listen()
onkey(space1, "space")
for b in range(1, 200):
block1.backward(537.5)
block1.forward(537.5)
For now I hide the turtle when I press "space", but I want to break the for-loop with the key event.
Make another callback:
...
exit = false
def space1():
global exit
exit = True
block1.hideturtle()
listen()
onkey(space1, "space")
for b in range(1, 200):
if exit:
break
block1.backward(537.5)
block1.forward(537.5)
This is just an example and altough you should not use global variables and you should encapsulate all this behaviour, it will be enough.

Categories