CPTR 124 Fundamentals of Programming


In this lab you will write functions that control the logic of your graphical Tic-Tac-Toe game.


  1. Teams

    You are welcome to work with a partner on this lab. You and your partner should begin thinking about the problems and begin writing the code before lab time. You may work by yourself if you wish.

  2. What to do

    http://en.wikipedia.org/wiki/Tic-tac-toe provides the rules Tic-Tac-Toe. Your task is to provide the controlling logic for a two-player Tic-Tac-Toe computer game. This game engine should work independently of the application's presentation. It should be able to drive a modified version of your graphical Tic-Tac-Toe prototype, but also must work correctly for a non-graphical test program. A game in progress might look like

    Screenshot of the Tic-Tac-Toe game

    Since it is a two-player game, your program does not play against a human opponent but simply allows the user(s) to interact with the board, making marks with the mouse.

    We divide the code of the program into two separate components: the program logic and the presentation. The program logic forms the game engine, enforcing the rules of the game. The presentation is the interface to the user. The presentation can be graphical (pointing device and graphical screen), text based (keyboard input and text output), or something else, such as hand gestures, voice recognition, brain wave analysis, etc.

    The following table shows some of the division of labor between the program logic and presentation:

    Responsibilty of Engine

    Responsibility of Presentation

    Keep track of whose turn it is

    Indicate the current player (X cursor or O cursor)

    Keep track of the marks in each square

    Indicate to the user the marks in each square (draw an X, O, or nothing)

    Determine if there is a winner

    Show the winning configuration by drawing a visual line across the three-in-a-row marks

    Disallow illegal moves

    Provide feedback to user about an illegal move (display message box to user)

    Place a mark in a specified position, if possible

    Allow user to specify a move (process mouse clicks over the board)

    Reset the game

    Provide a dialog box that indicates the winner and gives the user the option to play again or quit

    The presentation gets its information about what to display from the game engine, and the game engine gets its input from the presentation.

    We will implement this clean separation of control logic and presentation by writing a program that consists of two separate Python source files: tttlogic.py (the game engine) and tictactoe.py (the GUI—an adapted version of your previous assignment).

    • Game Logic. The file tttlogic.py is responsible for making the game work properly and enforcing the rules. Your job is to complete the tttlogic.py file—in essence to program the rules of the game in Python. Some of the things your code must handle include

      • ignore illegal moves like trying to make a mark over an existing mark.
      • control whose turn it is
      • detect when the game is over (win or draw).

      Study the docstrings and comments in the tttlogic.py Python module. The file contains the skeletal definitions of seven functions. Observe that several of the functions accept string parameters and may return string results. It is important to use the exact strings specified in the comments so that the engine and the presentation can communicate successfully. The following is a comprehensive list of the strings the game engine and presentation use for communicating and making the game work:

      String

      Meaning

      Role

      'X'

      Player X or player X's mark

      Player management

      'O'

      Player O or player O's mark

      Player management

      'NorthWest'

      Top-left square

      Board location

      'North'

      Top-middle square

      Board location

      'NorthEast'

      Top-right square

      Board location

      'West'

      Left-middle square

      Board location

      'Center'

      Center square

      Board location

      'East'

      Right-middle square

      Board location

      'SouthWest'

      Bottom-left square

      Board location

      'South'

      Bottom-middle square

      Board location

      'SouthEast'

      Bottom-right square

      Board location

      'Playing'

      No one has won and a move is available

      Game state

      'Win_NW_NE'

      Win across top row

      Game state

      'Win_W_E'

      Win across middle row

      Game state

      'Win_SW_SE'

      Win across bottom row

      Game state

      'Win_NW_SW'

      Win along left column

      Game state

      'Win_N_S'

      Win along center column

      Game state

      'Win_NE_SE'

      Win along right column

      Game state

      'Win_NW_SE'

      Win from left-top corner to right-bottom corner

      Game state

      'Win_NE_SW'

      Win from right-top corner to left-bottom corner

      Game state

      'Draw'

      All squares filled with no winner

      Game state

      Your first task is to implement the missing code in the functions and add global variables as needed to keep track of the state of the game.

      • look expects a location string ('NorthWest', 'North', etc.) as a parameter and returns the mark in that particular square ('X' or 'O') or None (if the square is empty). The graphical system uses the look function to determine what, if anything, to draw in a given square.

      • move puts the mark of a player ('X' or 'O') in a given square, if possible. The exact mark depends on the player with the current turn. A mark may be placed in a square only if the square is unoccupied. The move function requires its parameter to be one of the board location strings ('NorthWest', 'North', etc.). If the move is a valid move, it addition to placing the mark, the function should ensure that turn transitions to the other player and finally return True If the attempted move is not valid, the function should not change the state of the game and simply return False. (You may elect to ignore move's return value in your GUI code, but your function should return the correct result regardless.)

      • initialize_board clears all marks off the board making it ready for a new game. It also makes player X the current player. The function should reset any data the program may be using to monitor or control the progress of a game.

      • current_player returns the player whose turn it is ('X' or 'O').

      • change_player alternates the turn to the other player.

      • set_player forces the current player to be 'X' or 'O'.

      • check_status determines if one player has won, the game is a draw, or if the game can continue. It returns a value the graphical system can use to render the board properly; for example, if player X has three marks in a line from the northeast corner of the board to the southwest corner as shown in the figure below, check_status would return to the graphical interface the tuple ('X', 'Win_NE_SW'). The first element of the tuple is the winner, and the second element represents winning configuration. With this tuple the graphical interface in turn produces the presentation shown in the figure below. The playing and draw tuples would be (None, 'Playing') and (None, 'Draw'), respectively, as neither status involves a winner.

        The check_status function should not in any way alter the state of the game.

        Screenshot of the Tic-Tac-Toe game

      Think about it: If you can clear the board, put marks in specified squares, see what is in a given square, and check to see if the game is over or should continue, you have all the pieces necessary to model the control logic for a two-player Tic-Tac-Toe game.

      In conjunction with implementing the seven functions above, you will need to add global variables in the tttlogic.py module that maintain the state of the game. Your functions will use and potentially modify these global variables as the game progresses. You need a variable to keep track of whose turn it is. You need nine variables that keep track of the marks assigned to each of the squares on the Tic-tac-toe board. You may want to add a variable to keep track of the number of moves during a game (so you can declare a draw if no one has won after nine moves)—although you can handle a tie in other ways.

      Important! Your tttlogic.py file should contain NO input or output statements; that is, print and input should not appear in your code. It is fine to use printing statements during development to help debug your code, but you should remove these statements when your work is complete. Also, your tttlogic.py module should contain no Turtle graphics code. The game engine code should make no assumptions about how to show the user the progress of the game. It is the presentation's job (tictactoe.py) to draw in the graphics window.

    • Presentation. Copy the file tictactoe.py from your previous assignment to a new folder. It must reside in the same folder as the tttlogic.py file. The tictactoe.py Python source file is responsible for the presentation, and you must augment it to work with the game engine. Unlike with the code in the tttlogic module, you have much freedom as to how you organize your functions and code within tictactoe.py. You must, however, use graphics capabilities from the turtle module only, with the exception of the messagebox function that is found in the tkinter module.

      The tictactoe.py file provides a graphical user interface (GUI) that allows the user to make moves with a pointing device such as a mouse or touch pad. The GUI draws a natural looking Tic-tac-toe game board within a window (see the figure above). Your original version of tictactoe.py did not enforce the rules of the game, so it requires some modification to interact properly with the game engine.

      You will need to modify parts of your tictactoe.py file so it cooperates with the game engine. If you correctly implemented the mandated functions from the previous assignment (point_to_square, square_to_point, drawX, and drawO), you should not have to touch these functions. Your prototype had to keep track of the current player, but now a global variable in the tttlogic module will do this. Instead of blindly drawing a mark in a board position, your modified graphical application can do so only as allowed by the game engine. This means you will need to modify your function that processes mouse clicks.

      After each move, the function you registered with onscreenclick in the graphical application must do several things:

      • The function you registered with onscreenclick must check to see if the move was valid (via tttlogic.move's return value) to determine if it should draw a mark in the selected square. If the move was valid, your graphical program can ask the game engine (via tttlogic.look) which player owns the square in order to determine which mark to draw. If the move was not valid (for example, the player attempted to mark an already marked square), your GUI can show a message box that reports the failed move. The game engine can do this checking for you, so you should not do this work inside of your mouse clicked function.

        Note that your GUI does not have to keep track of whose turn it is—the game engine does that. Your GUI does maintain the ability to force the current player to be X or O via the keyboard as before, but users ordinarily do not exercise this power when playing Tic-Tac-Toe under the normal game rules.

      • The function you registered with onscreenclick also must check (via tttlogic.check_status) to see if a valid move resulted in a win or draw to determine if the game should continue. You will need to add code somewhere (most likely in a new function) that draws a line across the appropriate squares for a win or draws lines everywhere for a tie.

      Another helpful function in the GUI would provide a way to initialize the presentation (necessarily calling tttlogic.initialize_board along the way). Your GUI would need to call this initialization function in between games to ensure all remnants of the previous game were cleared.

      In addition to augmenting your screen click callback, you will need to tweak the callback functions you wrote for pressing the X key and O key so that they properly cooperate with the services your tttlogic module provides.

  3. Testing

    When you are finished, thoroughly play with your Tic-Tac-Toe game, trying all combinations of moves, to ensure that your logic is correct. Does your code handle tie games properly? Does player X always have the first turn in a new game? If the very last move in a game uses the last available square but results in a win, does your code recognize the win or does it mistakenly report a draw?

    When you are satisfied that your tttlogic module is correct, run it on the command line (CMD.EXE for Windows or Terminal on a Mac) with the following program: test_tttlogic.py. The program's output should be (the time can vary):

    ... ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
    If you get this output, your tttlogic.py file probably is ready to move to the next level of verification. If the program's output indicates any failures, you need to fix the errors in tttlogic.py before subjecting it to additional scrutiny.

  4. Check out

    Your finished application consisting of your tictactoe.py and tttlogic.py files will be evaluated for correctness and compliance. Double check to ensure the following:

    • that your tttlogic.py file contains no printing statements and no Turtle graphics code, and

    • that two players can play your graphical Tic-Tac-Toe game and things work as they would in the paper version.

    Remember that your tttlogic module is intended to work with different presentations, not just yours; therefore, your tttlogic.py file will receive additional scrutiny. Follow the special instructions given during lab for the evaluation component. When approved, you should submit both Python source files to http://eclass.e.southern.edu. Be sure your name (and your partner's name, if necessary) are included in comments at the top of each source file.