Keyboard events¶
Of the keyboard-generated events, the most important is the key pressing event, so we will focus on that one the most. When pressing keys on the keyboard, in the event object that represents an event in our programs, the value of event.type will be pg.KEYDOWN
.
When pressing a keyboard key is registered, we almost always want to know which key it is. This we can find out by testing the value of event.key
. As we already mentioned in the lesson on reading the keyboard state, that there is a constant for each key corresponding to that key. Let us recall the names of these constants for some frequently tested keys (you can see a complete list of these constants here ):
constant |
key |
---|---|
pg.K_LEFT |
left arrow |
pg.K_RIGHT |
right arrow |
pg.K_UP |
up arrow |
pg.K_DOWN |
down arrow |
pg.K_SPACE |
space bar |
pg.K_a |
a key |
pg.K_b |
b key |
pg.K_c |
c key |
The value of the event.key field tells us which physical key is pressed, and this value is suitable for keys such as arrow keys, Ctrl, Shift, Alt, Home, End and the like. When it comes to text, it may be more convenient to use the value of the event.unicode
field, which contains the character that was just typed (as a single character string).
The following example illustrates testing a keystroke, or a pg.KEYDOWN event.
Example - crossword
In this example, the user can use the arrow keys on the keyboard to move the frame and enter letters into boxes.
Note the handle_event function in which the event type check occurs, and then, if it is a key press, additional event information, stored in the event.key and event.unicode fields, is checked.
Additionally, you can try to put together some interesting crosswords.
import pygame as pg, petljapg
num_rows, num_cols = 5, 5
a = 50 # square size
(width, height) = (a* num_cols, a * num_rows)
canvas = petljapg.open_window(width, height, "Crossword")
font = pg.font.SysFont("Arial", 30)
board = []
for row in range(num_rows):
board.append([' '] * num_cols)
(frame_row, frame_col) = (0, 0)
def handle_event(event):
global frame_row, frame_col
if event.type == pg.KEYDOWN:
if event.key == pg.K_LEFT:
if frame_col > 0:
frame_col -= 1
elif event.key == pg.K_RIGHT:
if frame_col < num_cols-1:
frame_col += 1
elif event.key == pg.K_UP:
if frame_row > 0:
frame_row -= 1
elif event.key == pg.K_DOWN:
if frame_row < num_rows-1:
frame_row += 1
else:
letter = chr(event.key).upper()
if letter in ' ABCDEFGHIJKLMNOPQRSTUVWXYZ':
board[frame_row][frame_col] = letter
def display_letter(text, row, col):
if text == ' ':
d = 6
square = (a*col + d, a*row + d, a - 2*d, a - 2*d)
pg.draw.rect(canvas, pg.Color("black"), square)
else:
text_image = font.render(text, True, pg.Color("black"))
x = a * col + (a - text_image.get_width()) // 2
y = a * row + (a - text_image.get_height()) // 2
canvas.blit(text_image, (x, y))
def new_frame():
canvas.fill(pg.Color("white")) # paint background
for x in range(0, width+1, a): # vertical lines
pg.draw.line(canvas, pg.Color("black"), (x, 0), (x, height), 2)
for y in range(0, height+1, a): # horizontal lines
pg.draw.line(canvas, pg.Color("black"), (0, y), (width, y), 2)
pg.draw.rect(canvas, pg.Color("blue"), (a*frame_col, a*frame_row, a, a), 4) # frame
for row in range(num_rows): # letters
for col in range(num_cols):
display_letter(board[row][col], row, col)
petljapg.frame_loop(30, new_frame, handle_event)
(PyGame__interact_crossword_eng)
Let’s compare this program to the “Navigation” program from the keyboard status reading lesson:
In the “Navigation” program |
In the “Crossword” program |
---|---|
the yellow circle was moving by one pixel - it was gliding |
the frame moves by one field - it jumps |
we had no precise control of where the circle would stop |
we can precisely control where the frame will stop |
it didn’t matter where exactly the circle would stop |
it matters where exactly the frame will stop |
we were reading the status of the keybard keys (down or up) |
we are using an event (pressing a key) |
Task - navigation through fields
Write a program that works as shown in the example(“Play task” button).
The user can use the arrow keys on the keyboard to guide the character represented with a yellow circle. The character moves around the fields with white dots.
import pygame as pg, petljapg
num_rows, num_cols = 10, 10
a = 50 # square size
(width, height) = (a* num_cols, a * num_rows)
canvas = petljapg.open_window(width, height, "Dot-eater")
(character_row, character_col) = (num_rows // 2, num_cols // 2)
def new_frame():
canvas.fill(pg.Color("black")) # paint canvas
# white dots
for x in range(a // 2, width, a):
for y in range(a // 2, height, a):
pg.draw.circle(canvas, pg.Color("white"), (x, y), 3)
# HERE ADD THE STATEMENTS FOR DRAWING THE YELLOW CIRCLE
# (use character_row, character_col)
def handle_event(event):
global character_row, character_col
# ADD THE EVENT PROCESSING STATEMENTS HERE
petljapg.frame_loop(30, new_frame, handle_event)
(PyGame__interact_pacman1_eng)
Task - making a maze
Write a program that makes a maze. The red frame should be controlled using the arrows, and pressing the space bar should change the color of a square (from black to white and vice versa).
When you complete the task, try the following:
click on the “Play task” button (this time the example program does more than what we asked of you)
make the maze the way you want
press S for start and watch
press P to turn pause mode on and off, that is to stop or resume searching
import pygame as pg, petljapg, random
a = 50 # square size
num_rows = 12
num_cols = 20
(width, height) = (num_cols * a, num_rows * a)
canvas = petljapg.open_window(width, height, "Maze")
def new_frame():
canvas.fill(pg.Color('white')) # paint background
for row in range(num_rows):
for col in range(num_cols):
if board[row][col] == 1: # wall
pg.draw.rect(canvas, pg.Color('black'), (col * a, row * a, a, a))
# frame
pg.draw.rect(canvas, pg.Color('red'), (frame_col * a, frame_row * a, a, a), 3)
def handle_event(event):
global board, frame_row, frame_col
# ADD STATEMENTS FOR KEYSTROKE PROCESSING HERE
# (arrow keys and space bar should be covered)
board = []
for j in range(num_rows):
board.append([])
for i in range(num_cols):
board[-1].append(random.randint(0, 1))
(frame_row, frame_col) = (0, 0)
petljapg.frame_loop(10, new_frame, handle_event)
(PyGame__interact_labyrinth_eng)
Bonus - a typing practice program¶
The program below is intended for typing practice. The program is long, but you should be able to understand a big part of it.
You might want (without obligation) to tailor the program to your needs, especially at the beginning. For example:
to slow down (or later accelerate) the falling of the letters
not to lose points when you make a mistake
when you master the letters that fall the first, to remove them from the list of letters to practice
to change the set of falling characters to numbers and operation signs only (if you want to practice typing only on the numeric keypad on the right side)
to have pressing the spacebar delete the lowest letter (with somewhat reducing the score)
or anything else you can think of.
When you have time, you are encouraged to also try to get as high a score as you can (without cheating).
import pygame as pg, petljapg, random
(width, height) = (600, 400)
canvas = petljapg.open_window(width, height, "Typing - shooting")
def write(message, x, y):
font = pg.font.SysFont("Arial", 32) # font for displaying the message
text_image = font.render(message, True, pg.Color("green"))
canvas.blit(text_image, (x, y)) # display the image
def draw():
canvas.fill(pg.Color("black")) # paint canvas
if not game_over:
# if the game is still running,
# display the falling letters and the number of points
for letter, x, y in falling_letters:
write(letter, x, y)
write('score: ' + str(score), 0, height - 40)
else:
# otherwise write a message
write('Game over', 100, 100)
write('you scored ' + str(score) + ' points', 100, 200)
def new_frame():
global falling_letters, time_until_next_letter, next_x
global choice_starting_letter, letters_to_chose_from, game_over
# move all falling letters two pixels down, check whether the game is still running
new_letters = []
for letter, x, y in falling_letters:
if y < height - 40:
new_letters.append((letter, x, y + 2))
else:
# if a letter has reached the bottom of the window, the game is over
game_over = True
# update list of falling letters
time_until_next_letter -= 1
if time_until_next_letter <= 0: # if another falling letter needs to be added...
time_until_next_letter = 20
next_x += 20
if next_x >= width:
# row of letteres is filled, update letters to chose from
next_x -= width
if choice_starting_letter + NUM_LETTERS_TO_CHOSE_FROM < len(letters):
choice_starting_letter += 1
letters_to_chose_from = letters[choice_starting_letter : choice_starting_letter + NUM_LETTERS_TO_CHOSE_FROM]
# add one more falling letter
letter = random.choice(letters_to_chose_from)
new_letters.append((letter, next_x, 0))
falling_letters = new_letters
draw()
def handle_event(event):
global falling_letters, score
if event.type == pg.KEYDOWN: # keystroke on the keyboard
key_typed = chr(event.key).upper()
if len(falling_letters) > 0: # if there are falling letters
letter, x, y = falling_letters[0] # lowest falling letter
if key_typed == letter:
del falling_letters[0]
score += 10 # reward for typing the lowest letter
else:
score -= 4 # penalty for the wrong letter
letters = 'ASDFJKL;EIRUTYGHWOQPC,VMBNXZ.-'
NUM_LETTERS_TO_CHOSE_FROM = 4
choice_starting_letter = 0
letters_to_chose_from = letters[choice_starting_letter : choice_starting_letter + NUM_LETTERS_TO_CHOSE_FROM]
falling_letters = []
time_until_next_letter = 0
next_x = 0
score = 0
game_over = False
petljapg.frame_loop(30, new_frame, handle_event)
(PyGame__interact_typing_tutor_eng)