Chapter 3. File I/O, Data, and Fonts: The Trivia Game

The purpose of a file is to store data in a logical way so that it can be read back later and updated if necessary. To read and write files, then, one has to understand data types, because the data stored in a file must be specific. This chapter explores data types and file input/output. This chapter has a secondary purpose that works well with data types and file I/O: printing text on the screen with fonts.

Here are the topics covered in this chapter:

  • Python data types

  • Getting user input

  • Handling exceptions

  • The Mad Lib game

  • Working with text files

  • Working with binary files

  • The Trivia Game

Examining the Trivia Game

The Trivia Game demonstrates the concepts covered in this chapter by reading trivia questions out of a file and asking the user to choose from the multiple choice answers. The trivia questions and answers can be easily edited with a text editor or even with IDLE. Figure 3.1 shows what the game will look like when you have finished it in this chapter.

The Trivia Game.

Figure 3.1. The Trivia Game.

Python Data Types

Remember that Python is an interpreted script language, not a compiled language like C++, so it is much more forgiving to the programmer with many more tolerances. A Python variable can be a string, and then a number, and then a string again, without complaint. For instance:

something = 123
print(something)
something = "ABC"
print(something)

That code produces this output:

123
ABC

Note

Python Data Types

Use the str() function when you need to convert a number to a string, and either the int() or float() function to convert a string with a number in it to a numeric variable.

More Printing

The print() function can print more than one variable at a time; just separate each one with a comma. For instance, this:

A = 123
B = "ABC"
C = 456
D = "DEF"
print(A,B,C,D)

produces this:

123 ABC 456 DEF

Note that print() inserts a space in between each item being printed. You can add a blank line to the text output by print() by inserting the character code in a string variable, such as:

name = "John Carpenter
"
birth = "11/11/2011"
print(name,birth)

which outputs:

John Carpenter
 11/11/2011

Note that print() still inserted a blank space after the newline character. The print() function has a second optional parameter that can specify the separator character, which can be changed from the default space character. In fact, print() has four parameters in total! The third specifies the newline character, and the fourth specifies the output destination for redirection (not commonly used).

print("String","Theory", sep='-', end=':')

produces this output:

String-Theory:

There are some values built into Python that we can print out. Let’s print out sys.copyright to display the copyrights of all the modules used in Python. First, add

import sys

to the program so the sys module is available. Then, print the value:

print(sys.copyright)

This prints out:

Copyright (c) 2001-2011 Python Software Foundation.
All Rights Reserved.

Copyright (c) 2000 BeOpen.com.
All Rights Reserved.

Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.

Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved.

Note

More Printing

Some modules are not automatically loaded by Python. If you think your code is correct but python.exe is still giving you syntax errors, make sure you are using the import statement with the required modules.

Another interesting value is sys.platform, which is a string representing the operating system currently in use. This will be “win32” or “win64” for Windows systems, and will vary according to the other operating systems supported by Python. If you ever want to know what version of Python you’re using, you can display it with:

print(sys.version)

which produces output something like this (it will vary from one system to another):

3.2 (r32:88445, Feb 20 2011, 21:29:02) [MSC v.1500 32 bit (Intel)]

How about printing out the date and time? Here’s the code:

import datetime
from datetime import datetime, timezone
print(datetime.now())

which produces this output:

2011-06-14 16:52:00.572000

There’s quite a bit more to the datetime class that allows retrieval of specific datetime components (month, day, year, etc.).

Getting User Input

We can get user input from the console using the input() function, which returns a string. The simplest use of the function is to just pause output when a program has finished running. An optional parameter displays text before waiting for input. For example:

poem = """
Three Rings for the elven kings under the sky,
Seven for the dwarf lords in their halls of stone,
Nine for the mortal men doomed to die,
One for the dark lord on his dark throne.
In the land of mordor where the shadows lie,
One ring to rule them all, One ring to find them,
One ring to bring them all and in the darkness bind them.
 - J.R.R. Tolkien
"""
print(poem)
input("Press Enter to continue...")

Note

Getting User Input

To create a lengthy text string on multiple lines, enclose the lines of text in triple quotes.

This example will wait for the user to press the Enter key before exiting. But, we can also read the input typed in from the input() function, rather than just look for the Enter key to be pressed (which returns an empty string, by the way).

name = input("Pray tell, what is thy name? ")
print("Fare thee well, Master", name)

And the output:

Pray tell, what is thy name? Ambivalent Programmer
Fare thee well, Master Ambivalent Programmer

Note

Getting User Input

Python excels at handling groups of information. To create an array-like list of any data, just set a variable equal to data items in brackets, with each one separated with a comma: mylist = [1,2,3,4,5]. Strings and other data types can also be used.

Handling Exceptions

If you need to have the user type in a number and then use it in a calculation, the text entered can be converted to a numeric variable with the int() or float() function. But, if the user types in non-numeric digits, then the program will crash! We can’t allow that to disrupt the program, because users might type in an invalid input value, and without handling the likelihood of an error, the program definitely will crash. For example:

Enter a number: ABC
Traceback (most recent call last):
  File "InputDemo.py", line 4, in <module>
    number = float(s)
ValueError: could not convert string to float: 'ABC'

We can handle this problem with a try...except block, which will trap errors. In the example below, the questionable line occurs inside the try: block, and the code in the except: block will run if there is an error. In either case, the program continues running.

s = input("Enter a number: ")
try:
    number = float(s)
except:
    number = 0
answer = number * number
print(number,"*",number,"=",answer)

Here is a sample run. If you enter invalid data, then the output will just reflect a value of 0 because of the error handler.

Enter a number: 15
15.0 * 15.0 = 225.0

The Mad Lib Game

This is not the final chapter example, but we know enough about getting input to make a simple Mad Lib game just for fun. The Mad Lib Game is pretty simple. You ask someone to fill in some names, things, places, and then use those words and phrases to fill in a story, with often humorous results that are unexpected. The interesting thing about this little program is how the story is constructed. Instead of building the story out of the user input variables (guy, girl, food, etc.), it uses string.replace() to do a search-and-replace operation on the story string, replacing tagged words (in caps) with user data. There are so many useful classes and methods in the Python modules! Study the Python docs, like an explorer charting an undiscovered country, and learn what great mysteries lie hidden! That is what sets apart a mediocre programmer from a great programmer.

print("MAD LIB GAME")
print("Enter answers to the following prompts
")

guy = input("Name of a famous man: ")
girl = input("Name of a famous woman: ")
food = input("Your favorite food (plural): ")
ship = input("Name of a space ship: ")
job = input("Name of a profession (plural): ")
planet = input("Name of a planet: ")
drink = input("Your favorite drink: ")
number = input("A number from 1 to 10: ")

story = "
A famous married couple, GUY and GIRL, went on
" +
        "vacation to the planet PLANET. It took NUMBER
" +
        "weeks to get there travelling by SHIP. They
" +
        "enjoyed a luxurious candlelight dinner over-
" +
        "looking a DRINK ocean while eating FOOD. But,
" +
        "since they were both JOB, they had to cut their
" +
        "vacation short."

story = story.replace("GUY", guy)
story = story.replace("GIRL", girl)
story = story.replace("FOOD", food)
story = story.replace("SHIP", ship)
story = story.replace("JOB", job)
story = story.replace("PLANET", planet)
story = story.replace("DRINK", drink)
story = story.replace("NUMBER", number)
print(story)

Sample output looks like this (note that it will change based on what the user types in). I have highlighted the user data in bold to show how the story was constructed.

The Mad Lib Game.

Figure 3.2. The Mad Lib Game.

MAD LIB GAME
Enter answers to the following prompts

Name of a famous man: Stephen Hawking
Name of a famous woman: Drew Barrymore
Your favorite food (plural): lasagna
Name of a space ship: TIE Fighter
Name of a profession (plural): philanthropists
Name of a planet: Tattooine
Your favorite drink: Raktajino
A number from 1 to 10: 8

A famous married couple, Stephen Hawking and Drew Barrymore, went on
vacation to the planet Tattooine. It took 8 b
weeks to get there travelling by TIE Fighter. They
enjoyed a luxurious candlelight dinner over
looking a Raktajino ocean while eating lasagna. But,
since they were both philanthropists, they had to cut their
vacation short.

It would take some work to line up the resulting story text evenly on each line. It can be done but the code was kept on the short and simple side for the sake of illustration.

File Input/Output

The simplest form of file is a text file that could be opened with a text editor like Notepad. In such a file, we can read data with one significant item per line and then read each line into a variable.

Working with Text Files

To open a file in Python, use the open() function. The first parameter is the filename, and the second is the open mode. The modes are shown in Table 3.1. In most cases, we will just use “r” to read, but all of the modes are available and files can be created, appended, overwritten, and read with the file functions.

Table 3.1. Text File Open Modes

Mode

Description

“r”

Open text file to read data.

“w”

Open text file to write data.

“a”

Open text file to append data.

“r+”

Open text file to read and write data.

“w+”

Open text file to write and read data.

“a+”

Open text file to append and read data.

The open() function might be called like so:

file = open("data.txt", "r")

To close the file after finishing with it:

file.close()

Writing to a Text File

To write data out to a text file, we have to open the file with the “w” write property. There is one primary way to write text data to a file, using the file.write() function. Surprisingly, there is no writeline()-type function to write just a singular line (but there is plural file.writelines() for writing a list of strings), so we have to add a new-line character ( ) to the end of text that needs to be saved on a separate line.

file = open("data2.txt", "w")
file.write("Sample file writing
")
file.write("This is line 2
")
file.close()

Here is another example that writes several lines out at once from a string list:

text_lines = [
    "Chapter 3
",
    "Sample text data file
",
    "This is the third line of text
",
    "The fourth line looks like this
",
    "Edit the file with any text editor
" ]

file = open("data.txt", "w")
file.writelines(text_lines)
file.close()

Reading from a Text File

To read from a file, we must first open it for reading. The code is similar to opening a file for writing, but we just need to change the file mode:

file = open("data.txt", "r")

Once a file has been opened, the data inside can be read, and there are a number of functions available to do this in different ways. To read a single character at a time, use file.read(n), where n is the number of characters to read:

char = file.read(10)
print(char)

reads 10 characters from the file at the current file pointer position. Repeated calls like this will continue to read more characters out of the file and advance the position. To read the whole file into a string variable:

all_data = file.read()
print(all_data)

We can also read a whole line of text data with file.readline(n), where n is an optional number of characters to read from the current line.

one_line = file.readline()
print(one_line)

To read all of the lines in the entire data file, use file.readlines(). Invoking this function does not fill the receiving variable with text data. Instead, a list is created with each line an item in the list. Printing the data in the list variable does not print out the text data as it appears in the file. For instance, this code:

all_data = file.readlines()
print(all_data)

produces this output:

['Chapter 3
', 'Sample text data file
', 'This is the third line of text
',
'The fourth line looks like this
', 'Edit the file with any text editor
']

Strange looking, wouldn’t you agree? Well, since a list was created out of the all_data variable, it can be indexed like an array with a for loop. Note that the string.strip() modifier is used: this removes line-feed characters from the end of lines.

print("Lines: ", len(all_data))
for line in all_data:
    print(line.strip())

The output shows the contents of the text file:

Lines:  5
Chapter 3
Sample text data file
This is the third line of text
The fourth line looks like this
Edit the file with any text editor

Working with Binary Files

Binary files contain bytes. The bytes might be encoded integers, encoded floats, encoded lists (written using pickling, which we’ll cover here shortly), or any other type of data. A PNG bitmap file can be read with binary file access in Python. Now, interpreting the data afterward is up to you, the programmer, but Python can read the data and supply it in a buffer for processing. Table 3.2 lists the binary file modes.

Table 3.2. Binary File Open Modes

Mode

Description

“rb”

Open binary file to read data.

“wb”

Open binary file to write data.

“ab”

Open binary file to append data.

“rb+”

Open binary file to read and write data.

“wb+”

Open binary file to write and read data.

“ab+”

Open binary file to append and read data.

Opening a file in binary mode is similar to what we’ve already seen, and might be called like so:

file = open("data.txt", "rb")

To close the file after finishing with it:

file.close()

Writing to a Binary File

This is debatable, but I think the most useful sort of binary file is one that contains data corresponding to a Python structure. Our ability to write a structure to a file and read it back with the fields intact will really handle any custom data file needs that we are likely to have, either for a game or any other program. Python has no direct correlation between a user-defined data type structure and file input/output. But it does provide a library module called struct with the ability to pack data into a string for output. We can write this data in binary mode, but interestingly enough, it was really designed for writing text data as a buffer.

The way data is encoded into binary format is with the struct.pack() function. When reading the data from the file again, it is decoded with struct.unpack(). struct is a Python module. To use it, we must include it first with an import statement, just like we do with Pygame:

import struct

Let’s see how to read and write a file in binary mode. The following example code writes 1000 integers to a binary file. First, let’s see how to write, and then we’ll read the data back. First, open the file for binary write mode:

file = open("binary.dat", "wb")

Next, write out 1000 integers to the file:

for n in range(1000):
    data = struct.pack('i', n)
    file.write(data)

Lastly, close the file:

file.close()

Reading from a Binary File

Now we’ll see how to read data back out of a binary file and unpack it for display, one value at a time. To verify that the code is working, we should expect to see the values 0 to 999 come up. First, we open the file, and calculate the size of an int with struct.calcsize(), so the struct.unpack() function will know how many bytes to read for each number.

file = open("binary.dat", "rb")
size = struct.calcsize("i")

Next, a while loop reads the data in the file size bytes at a time until all data has been read. As each value is read, it is unpacked, converted from a list to a simple variable, and printed out.

bytes_read = file.read(size)
while bytes_read:
    value = struct.unpack("i", bytes_read)
    value = value[0]
    print(value, end=" ")
    bytes_read = file.read(size)
file.close()

Similar code could be written to store additional data to the file in sequence. As long as the data is read back out in the same order that it was written, then different types of data can be written to the file.

The Trivia Game

It’s time to apply what we’ve learned about file input/output to a game that will help strengthen your grasp of the subject. The game will run in a graphics window using Pygame, so we will need to learn to use text output.

Printing Text with Pygame

We have been printing a lot of text out to the console in this chapter while learning about file input/output, but there comes a point where the console is no longer sufficient and we need a higher level of user interaction that only a graphical system can provide. We’ll bump it up a notch by learning to print text to the screen in graphics mode using Pygame.

The pygame.font module gives us the ability to print font-based text to the screen in graphics mode. We’ve already used pygame.font in the previous chapter but we’ll review it quickly again. The class that produces a printable font is pygame.font.Font. By default, passing None as the font name will cause the pygame.font.Font() constructor to load the default Pygame font. The second parameter to the constructor is the font point size. This line creates a default font with a 30-point size:

myfont = pygame.font.Font(None, 30)

We can also specify a font name to choose a custom font for our game:

myfont = pygame.font.Font("Arial", 30)

To print text, the font.render() function creates a bitmap with the text written on it, which we then draw to the screen using screen.blit().

image = font.render(text, True, (255,255,255))
screen.blit(image, (100, 100))

The Trivia Class

The main program source code in the game is primarily responsible for getting keyboard input and refreshing the screen. The bulk of the gameplay code is found in a new class called Trivia. First, we’ll import the modules needed for the game:

import sys, pygame
from pygame.locals import *

Next, we’ll get the Trivia class started. The constructor, __init__(), has a filename parameter that you pass to it, which contains the trivia data. The data is loaded with a single file.readlines() function call, and then that data is used in its list by the game. There are quite a few field variables (also called properties) in the Trivia class, to handle the game logic. All of the logic is performed by methods in the Trivia class, not in the main program.

class Trivia(object):
    def __init__(self, filename):
        self.data = []
        self.current = 0
        self.total = 0
        self.correct = 0
        self.score = 0
        self.scored = False
        self.failed = False
        self.wronganswer = 0
        self.colors = [white,white,white,white]

Loading the Trivia Data

After the data is loaded, then the trivia data is parsed (from its list object called trivia_data) and copied one line at a time into a new list called Trivia.data. The reason for the new list is so we can strip each line of whitespace (main line-feed characters at the end of each line). The following code is also found in the constructor, __init__().

        #read trivia data from file
        f = open(filename, "r")
        trivia_data = f.readlines()
        f.close()

        #count and clean up trivia data
        for text_line in trivia_data:
            self.data.append(text_line.strip())
            self.total += 1

The trivia data file included with the game has only five questions, but the game supports more, so you are welcome to add more questions. The theme of this trivia game is astronomy. Don’t cheat and look at the answers before at least trying to answer them on your own! You can edit the trivia_data.txt file with any text editor, including IDLE. The format of the trivia data goes like this: line 1 is the question; lines 2–5 are the answers; line 6 is the correct answer. See, simple!

What is the name of the 4th planet from the Sun?
Saturn
Mars
Earth
Venus
2
Which planet has the most moons in the solar system?
Uranus
Saturn
Neptune
Jupiter
4
Approximately how large is the Sun's diameter (width)?
65 thousand miles
45 million miles
1 million miles
825 thousand miles
3
How far is the Earth from the Sun in its orbit (on average)?
13 million miles
93 million miles
250 thousand miles
800 thousand miles
2
What causes the Earth's oceans to have tides?
The Moon
The Sun
Earth's molten core
Oxygen
1

Displaying the Question and Answers

The bulk of the work in the game is found in the Trivia.show_question() method. It draws the entire screen for the game: the title, the footer, the score, the question, answers, and does the colorizing of the answers based on user input. When the player chooses the correct answer, it is printed in green. But if they choose the wrong answer, it will be printed in red, and the correct one printed in green. This could have required quite a bit of logic code, but it was simplified using a list of four colors that is used when drawing the text of each answer. The key to indexing from one question record (in the loaded data) to another is the Trivia.current field. Figure 3.3 shows the resulting display when the user gets an answer right.

Getting the answer right.

Figure 3.3. Getting the answer right.

    def show_question(self):
        print_text(font1, 210, 5, "TRIVIA GAME")
        print_text(font2, 190, 500-20, "Press Keys (1-4) To Answer", purple)
        print_text(font2, 530, 5, "SCORE", purple)
        print_text(font2, 550, 25, str(self.score), purple)

        #get correct answer out of data (first)
        self.correct = int(self.data[self.current+5])
        #display question
        question = self.current // 6 + 1
        print_text(font1, 5, 80, "QUESTION " + str(question))
        print_text(font2, 20, 120, self.data[self.current], yellow)

        #respond to correct answer
        if self.scored:
            self.colors = [white,white,white,white]
            self.colors[self.correct-1] = green
            print_text(font1, 230, 380, "CORRECT!", green)
            print_text(font2, 170, 420, "Press Enter For Next Question", green)
        elif self.failed:
            self.colors = [white,white,white,white]
            self.colors[self.wronganswer-1] = red
            self.colors[self.correct-1] = green
            print_text(font1, 220, 380, "INCORRECT!", red)
            print_text(font2, 170, 420, "Press Enter For Next Question", red)

        #display answers
        print_text(font1, 5, 170, "ANSWERS")
        print_text(font2, 20, 210, "1 - " + self.data[self.current+1], self.colors[0])
        print_text(font2, 20, 240, "2 - " + self.data[self.current+2], self.colors[1])
        print_text(font2, 20, 270, "3 - " + self.data[self.current+3], self.colors[2])
        print_text(font2, 20, 300, "4 - " + self.data[self.current+4], self.colors[3])

Responding to User Input

The Trivia game works by waiting for the user to press the keys 1, 2, 3, or 4, to choose one of the four answers. When the user presses one of these keys, the Trivia.handle_input() method is called. If an answer has not already been chosen, then the user input will be compared to the correct answer, and either self.scored or self.failed will be set to True. The game then responds to these two flags, and is put into a wait state until the user presses the Enter key to continue to the next question.

    def handle_input(self,number):
        if not self.scored and not self.failed:
            if number == self.correct:
                self.scored = True
                self.score += 1
            else:
                self.failed = True
                self.wronganswer = number

Getting the answer wrong.

Figure 3.4. Getting the answer wrong.

Going to the Next Question

After an answer has been chosen, the game displays the result and waits for the user to press the Enter key to continue. That key triggers a call to the method Trivia.next_question(). If the game is in the wait state between questions, then the flags are reset, the colors are reset, and the game jumps to the next question. Since there are 6 lines per question in the data file (1 question, 4 answers, and 1 number representing the correct answer), the Trivia.current field is incremented by 6 to jump to the next question.

    def next_question(self):
        if self.scored or self.failed:
            self.scored = False
            self.failed = False
            self.correct = 0
            self.colors = [white,white,white,white]
            self.current += 6
            if self.current >= self.total:
                self.current = 0

Main Code

The main code is rather tight since so much gameplay functionality was put into the Trivia class. First up, we have a helper function called print_text(). It’s reusable because the first parameter you should pass to the function is a font object.

def print_text(font, x, y, text, color=(255,255,255), shadow=True):
    if shadow:
        imgText = font.render(text, True, (0,0,0))
        screen.blit(imgText, (x-2,y-2))
    imgText = font.render(text, True, color)
    screen.blit(imgText, (x,y))

Next, we have the main program initialization code that creates the Pygame window and gets things set up for the game.

#main program begins
pygame.init()
screen = pygame.display.set_mode((600,500))
pygame.display.set_caption("The Trivia Game")
font1 = pygame.font.Font(None, 40)
font2 = pygame.font.Font(None, 24)
white = 255,255,255
cyan = 0,255,255
yellow = 255,255,0
purple = 255,0,255
green = 0,255,0
red = 255,0,0

Next, the trivia object is created (using the Trivia class) and a data file called trivia_data.txt is loaded. We’ll look at the file in a minute.

#load the trivia data file
trivia = Trivia("trivia_data.txt")

A while loop keeps the game running; it may be considered the game loop. Most of the code is involved in getting user input with keyboard events. Then it just clears the screen and calls trivia.show_question() to update the current state of the game. The last line updates the screen.

#repeating loop
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        elif event.type == KEYUP:
            if event.key == pygame.K_ESCAPE:
                sys.exit()
            elif event.key == pygame.K_1:
                trivia.handle_input(1)
            elif event.key == pygame.K_2:
                trivia.handle_input(2)
            elif event.key == pygame.K_3:
                trivia.handle_input(3)
            elif event.key == pygame.K_4:
                trivia.handle_input(4)
            elif event.key == pygame.K_RETURN:
                trivia.next_question()

    #clear the screen
    screen.fill((0,0,200))

    #display trivia data
    trivia.show_question()
   
    #update the display
    pygame.display.update()

Summary

This chapter offered a fairly robust coverage of data types, input and printing, file input/output, and managing data effectively for a game. The end result in The Trivia Game demonstrated how easily Python can handle data of different types very easily.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.111.179