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
          
        