Typing Tutor Game

Typing ...

Now that we have our words dropping from screen, let's add the capability to read user inputs.

Just need to adjust our check event method from game.py into play.py to read TEXTINPUT:

# play.py
    def check_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
elif event.type == pygame.TEXTINPUT: self.key_hit = event.text
elif event.type == pygame.KEYUP: if event.key == pygame.K_ESCAPE: self.playing = False return

Check words

We also need a flag to indicate that we need to check for either all first characters of game words, for just check for the next character in the word we are typing:

class GamePlay():
    def __init__(self, screen):
        self.screen = screen
        pygame.init()
        self.disp_surf = pygame.Surface(self.screen.get_size()).convert()
        self.width, self.height = screen.get_size()
        self.font =  pygame.font.Font('Consolas Bold.ttf', 16)
        self.playing = True
        self.game_objects = GameObjects(self.width, self.height)
        self.key_hit = None
        self.last_add = 0 # time of adding word
        self.add_timeout = 2000 # 2 seconds
        self.fps = 25
        self.fps_clock = pygame.time.Clock()
        self.y_delta = 1
self.typing_flag = False self.key_hit = None self.score = 0

We use the typed_idx flag to indicate the word we are typing. Let's create a method to check our key_hit:

    def check_key_hit(self):
        '''Check user's typed key'''
if not self.key_hit: return if self.typing_flag: for word in self.game_objects.game_words: if word.typed_idx != -1: if word.text[word.typed_idx+1] == self.key_hit: if word.typed(): self.typing_flag = False self.add_score(len(word)) else: word.typed_reset() self.typing_flag = False else: for word in self.game_objects.game_words: if word.text[0] == self.key_hit: if word.typed(): self.add_score(1) else: self.typing_flag = True break # Remove words set for removal self.game_objects.clean_up() self.key_hit = None return

and an additional member to keep track fo the number of characters we typed, which can give us the score:

def add_score(self, score): self.score += score

Let's add two more methods to our Word() object, word.typed():

# word.py
def typed(self): '''Returns True if completed typing word, False otherwise.''' self.typed_idx += 1 if self.typed_idx >= self._len - 1: # completed typing word self.typed_idx = -2 return True return False

and word.typed_reset() to reset the typed_idx if player typed wrongly:

def typed_reset(self): '''User typed incorrectly, reset flag''' self.typed_idx = -1 return

We need to remember to clear the flag if the word we are typing drops below boundary_y. let's add a return flag to indicate we removed a word we are typing in our move method:

# objects.py
    def move(self, delta):
        '''Returns Flags if word is removed and if it is in the middle of typing,
        when it has moved beyond boundary Y
        '''
        removed = False
removed_typing = False
for word in self.game_words: word.move(delta) if word.get_y() >= self.bound_y: removed = True
if word.typed_idx >= 0: removed_typing = True
word.set_remove() self.clean_up()
return removed, removed_typing

Such that we can check the flag returned in play.py:

# play.py
    def loop(self):
        '''Game loop'''
self.check_key_hit()
# If moved beyond boundary Y
removed, removed_typing = self.game_objects.move(self.y_delta) if removed_typing: self.typing_flag = False
self.add_word() return

Score

Now that we have our score, let's display our score on the top of our screen during play:

# play.py
import pygame
from objects import GameObjects

# Colours        R    G    B
BLUE        = (  0,   0, 128)
WHITE       = (255, 255, 255)
GREEN       = (  0, 200,   0)
BLACK = ( 0, 0, 0) YELLOW = (255, 255, 0)
. . .
def draw_score(self): # draw status bar background pygame.draw.rect(self.disp_surf, BLACK, (0,0,self.width,20), 0) # draw score text text = 'Score: %s' % self.score self.draw_word(text, YELLOW, (self.width - 150, 2)) return

Add to our game play's render():

    def render(self):
        '''Render surface'''
        self.disp_surf.fill(BLUE) # Fill background
self.draw_score()
. . .

And display the score on GameOver:

# game.py
    def render(self):
        '''Render surface'''
        self.disp_surf.fill(BLACK)
        
        # set the text for display
        if self.state == START:
            text = 'Typing Tutorial Game'
            message = 'Press space to play.'
        else:
            text = 'Game Over'
message = 'Score: %s' % self.game.score
# draw the messages self.draw_text(text, GREEN, (int(WIDTH / 2), int(HEIGHT / 2) - 50)) self.draw_text(message, GREEN, (int(WIDTH / 2), int(HEIGHT / 2) + 50)) # Blit everything to screen self.screen.blit(self.disp_surf, (0,0)) pygame.display.flip() return

Draw typing characters

To indicate typing, we need to change the characters we typed into a different colour. We shall change it to green colour.

To have different colour, when we draw, we need to draw two rectangles, one with typed characters in green, and one replacing the typed characters with spaces and the rest of untyped characters in white. Which is why it is important we chose a font type with fixed width characters.

Let's adjust our on_render() method

# play.py
    def render(self):
        '''Render surface'''
        self.disp_surf.fill(BLUE) # Fill background
        self.draw_score()
        
        # set the words for display
        for word in self.game_objects.game_words:
if word.typed_idx > -1: typed_text = word.text[:word.typed_idx+1] untyped_text = ' '*(word.typed_idx+1) + word.text[word.typed_idx+1:] self.draw_word(untyped_text, WHITE, word.coord()) self.draw_word(typed_text, GREEN, word.coord()) else:
self.draw_word(word.text, WHITE, word.coord()) # Blit everything to screen self.screen.blit(self.disp_surf, (0,0)) pygame.display.flip() return