15-110 Summer 2017

Lab 11


In this lab you will focus on three different skills (1) how to draw shapes other than the rectangle (2) how to get input from the user of a program (3) how to write a component for a given program after studying its structure and behavior in a top-down fashion. When you are done, you should be able to do the following:


A file tic_tac_toe.py containing the diplay_grid() function along with the given starter code.

Place this file in a lab11 folder. Before leaving lab, zip up the lab11 folder and hand the zip file in.

Overview of Tic-Tac-Toe

In this lab, you will develop a program that allows two players to play Tic-tac-toe. In Tic-tac-toe, two players alternate placing their marks ("X"'s and "O"'s, respectively) in the of a 9 positions of a 3x3 grid. The first player to put three of their marks in a vertical, horizontal, or diagonal line is the winner. If nine marks have been placed without either player getting three marks in a row, the game ends in a tie.

Starter code: tic_tac_toe.py

See Canvas Commands for more info on drawing graphics.

In order to represent the state of play in a game of Tic-tac-toe, we will use a two-dimensional 3x3 array, where each element is None (if the corresponding position is unoccupied), 0 if the position is occupied by the mark ("O") of the first player ("Player 0"), or 1 if the position is occupied by the mark ("X") of the second player ("Player 1"). For example, the Tic-tac-toe grid shown at right could be represented by the 2d Python list:

[[1, None, 0], 
 [None, 0, None], 
 [None, None, None]]
The function new_grid creates the data representation for a blank 3x3 Tic-tac-toe grid. The function add_mark takes parameters grid, row, col, player, and modifies grid to place player's mark at the position specified by row and col as long as that position is unoccupied. If the position was unoccupied, then after modifying the grid, add_mark returns True. Otherwise (i.e., the position was already occupied), add_mark returns False.


>>> grid = new_grid()
 [[None, None, None], [None, None, None], [None, None, None]]
>>> add_mark(grid,1,2,0)
>>> grid
 [[None, None, None], [None, None, 0], [None, None, None]]
>>> add_mark(grid,1,2,1)
>>> grid
 [[None, None, None], [None, None, 0], [None, None, None]]
>>> add_mark(grid,2,2,1)
>>> grid
 [[None, None, None], [None, None, 0], [None, None, 1]]

Part 1 - Tic Tac Toe

1.1 CA Demonstration

  1. Review Canvas and introduce new Canvas primitives:
    my_window = tkinter.Tk()
    my_canvas = Canvas(my_window, width = 120, height = 120)
    my_canvas.create_rectangle(0, 0, 90, 50, fill = "grey", width = 0)
    my_canvas.create_line(5, 45, 85, 5, width = 3)
    my_canvas.create_oval(75, 35, 85, 45, outline = "black", fill = "gray", width = 2)
    my_canvas.create_text(45, 70, text = "Hello world", anchor = "center", fill = "magenta")
  2. Write the following function

    input_num(prompt) and demonstrate how to use itn

    def input_num(prompt):
        while True:
            s = input(prompt)
            if s == "quit": return 'quit'
            if s == "0": return 0
            if s == "1": return 1
            if s == "2": return 2
            print("input must be a number between 0 and 2 (inclusive) or quit")
  3. Explain the gameplay loop of a simple guessing game - guess the random number python generated
  4. from random import randint
    def guessing_game():
        pythons_number = randint(0, 2)
        num_guesses = 0
        while True:
            num = input_num("Guess a number between 0 and 2, inclusive: ")
            num_guesses += 1
            if pythons_number == num:
                print("You win! It took you " + str(num_guesses) + " guess(es)")
                return None
            print("Oops, you guessed wrong. Try again!")
  5. Reviewing the function play() to observe how the given program for the game Tic-Tac-Toe is structured

1.2 Student Activity

  1. Graphical Display of Board State

    Define a Python function display_grid(grid) that draws a game state to the Canvas. The following algorithm may be used for display_grid:

    Note: tkinter is very exact with the coordinate system. For a more polished looking game, you can consider adding/subtracting offsets from coordinates to "shrink" the X's and O's

    1. Create a Rectangle covering the entire 90x90 Canvas and filled with gray.
    2. Draw the horizontal and vertical lines separating the positions in the grid.
    3. For each grid position (rows in 0..2, columns in 0..2), do the following:
      1. Calculate the x, y coordinates of the center of that grid position.
      2. If the grid indicates that the position should hold an "O", then draw a circle centered at (x,y) with radius 15 and width 3.
      3. If the grid indicates that the position should hold an "X" (i.e., grid[row][col] == 1), then draw an "X" at the correct coordinates.
      4. If the grid indicates that the position should hold neither an "X" nor an "O", then do the following:
        1. Create a string, consisting of the row, a comma, and the column. (Hint: You can convert the integer x to a string with str(x).)
        2. Place this string as "darkblue", bold, Courier New 12 point text centered at (x,y).

    Hint: In implementing step C you can make use of the following function draw_x for drawing an "X".

    # x0, y0 are the coordinates of the top-left corner of the x.
    # x1, y1 are the coordinates of the bottom-right corner of the x.
    # my_canvas is the initialized canvas
    def draw_x(my_canvas, x0, y0, x1, y1):
        my_canvas.create_line(x0, y0, x1, y1, width = 3)
        my_canvas.create_line(x1, y0, x0, y1, width = 3)

    The following usage should result in the image shown above:

  2. Determining a Winner

    In the starter code, tic_tac_toe.py, the functions check_win_horiz(grid, row) and check_win_diagonal1(grid) have been given to you. However, in order for the code to fully work, you must implement check_win_vert(grid, row) and check_win_diagonal2(grid) on your own. The descriptions for the functions are given below:

    check_win_vert(grid, row) - returns True if any of the 3 columns in grid has all elements composed of all X's or all O's.

    check_win_diagonal2(grid) - returns True if any of the elements in the backwards diagonal are all X's or all O's.

    When you have completed these two functions, you should be able to play the game by calling play() in the Python Interpretter.