Phase 3 Final Project

Bruno RossiBruno Rossi
3 min read

For my Phase 3 project, I built a command-line typing game that puts your typing skills to the test using Object-Oriented Python.

In Python Typ-On, your challenge is to advance through the levels, typing the provided sentences with accuracy and speed.

The levels get progressively harder the longer you play.

For each game, Python Typ-On shows the mistakes you made and calculates your typing accuracy and speed.

The games and stats are then saved to an SQLite database.

This game showcases what we learned in phase 3:

  • Building python classes with class properties and one-to-many/many-to-many relationships between classes. In this case, I created 3 classes: Player, Game, and Level. The Game class acts as a joiner for Player and Level.

  • Working with primary data types in Python: strings, integers, floats, lists, dictionaries.

  • Instance methods, class methods, loops and conditionals, list comprehensions, and I even threw in a lambda function for good measure.

  • Writing and executing SQL against a database:

    • Create/Drop tables, insert/update/delete table rows.

    • Associating tables using foreign keys.

  • Using external libraries:

    • Diff checker: difflib library.

    • Color printer: termcolor library.

    • Timer: time library.

Challenges

  1. Building a Python game from scratch.

Thinking about every state and step of the game was a big challenge in this project.

  1. Calculating the difference between the provided sentence.

Initially, I developed my own solution that compared 2 strings, but then I found difflib, a much more powerful library with built-in methods for string comparison.

It's a great example of the old saying: "Don't try to reinvent the wheel".

Correct characters are displayed in green, and mistakes are displayed in red. If the player missed a key, it's displayed with a minus sign ("-"), and if they added an extra key, it's displayed with a plus sign ("+").

  1. Calculating the average typing accuracy and timing for a player to display in the "Stats" section.

Because the database only stores the time and accuracy numbers for each individual game, I decided to calculate the average when the Stats menu option is selected.

I made this decision to keep the code a lot simpler. The alternative would have been to store the stats somewhere in the database, and having to re-update them after every new game.

Here's the sample code for the average calculations:

class Player:
    # ... code
    # Instance method to list all games the player has played:
    def games(self):
        """Return a list of Games played by the current Player instance"""
        from models.game import Game
        sql = """
            SELECT *
            FROM games
            WHERE player_id is ?
        """
        CURSOR.execute(sql, (self.id,),)
        rows = CURSOR.fetchall()
        return [Game.instance_from_db(row) for row in rows]

    # Instance method to return the highest level a player has played:
    def highest_level_played(self):
        highest_level_id = max([game.level_id for game in self.games()])
        return Level.find_by_id(highest_level_id).name

    # Instance method to return the average accuracy of a player's games:
    def get_avg_accuracy(self):
        games_accuracy = [game.accuracy for game in self.games()]
        return sum(games_accuracy) / len(games_accuracy)

    # Instance method to return the average time of a player's games:
    def get_avg_time(self):
        games_times = [float(game.time) for game in self.games()]
        return sum(games_times) / len(games_times)

# Helper function:
def list_all_players():
    # Lists all players, their avg accuracy, and speed, 
    # ordered by avg accuracy:

    # Build a dictionary of players with the name, avg_accuracy, 
    # avg_time, and highest_lvl:
    players = [{"name": player.name,
                "avg_accuracy": player.get_avg_accuracy(), 
                "avg_time": player.get_avg_time(),
                "highest_lvl": player.highest_level_played()}
                for player in Player.get_all()]

    # Sort the players list in reverse order, 
    # using player's avg_accuracy as sorting key:
    players.sort(key=lambda player: player["avg_accuracy"], reverse=True)

    # Iterate through the list of players and 
    # print the stats for each player:
    for player in players:
        print(f"""{player['name']}\t
               | Accuracy: {player['avg_accuracy']:.2f}\t
               | Speed: {player['avg_time']:.2f} seconds\t
               | Highest level: {player['highest_lvl']}""")

It was very interesting working on this project, and I look forward to learning more about Python and SQL databases in the future.

-- Bruno

10
Subscribe to my newsletter

Read articles from Bruno Rossi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bruno Rossi
Bruno Rossi

I am a software engineering student at Flatiron School in New York City. Before embarking on this programming journey, I worked as a Localization Manager at high-growth startups. Originally from Brazil, I've lived in the US for about 10 years as a proud immigrant. Outside of work, I enjoy Pokémon, language-learning, and Drag Race. 🏳️‍🌈