Noughts and Crosses with PyGame Part 2

A Python and PyGame implementation of the Noughts and Crosses / Tic Tac Toe game.


This is the second and final part of my short series on developing a Noughts and Crosses / Tic Tac Toe game using PyGame. In the first part I created the user interface, including a few buttons and radio buttons, and also added mouse click event handlers. In this article I'll complete the project by connecting it up to a class called NaC which I originally used in a console based version and which provides the game logic.

As I mentioned in Part 1 the NaC class has no front-end functionality and can be hooked up to any UI you may wish to write. It's worth reiterating how this is done, and again stressing that the NaC class does its stuff with no knowledge of the user interface which is "pulling its strings".

This is a summary, repeated from Part 1, of how the interactions between a UI and an instance of NaC work.

  • Front end code creates an instance of the NaC class, passing two functions:

    • on_change, called by NaC whenever a state change requires a UI update

    • on_game_over, called by NaC when the game ends, requiring the UI to notify the player

  • The UI notifies the NaC instance of player actions by calling the following methods:

    • human_move - when the player clicks a square

    • new_game - when the player clicks New

    • computer_move - when the player clicks Start

    • __level_changed - when the player selects a different level

The Project

The source code files, and as well as the graphics can be downloaded as a zip, or you can clone/download the Github repository if you prefer. (The zip and repository also contain, the partial program.)

Source Code Links

ZIP File

The Code

This is the source code for the completed program. If you wish to look at the NaC class it's shown and described in the article on the console version but you can treat it as a "black box" if you are just interested in the PyGame UI. Note that this is, the completed game.

from collections import namedtuple

import pygame

import nac

UIrect = namedtuple("UIrect", "x, y, w, h, func")

class NaCPyGame(object):

    def __init__(self):

        pygame.display.set_caption("CodeDrome NaC") = pygame.display.set_mode((600,800)) = True = self.__init_graphics()
        self.uirects = self.__init_rects()
        self.clock = pygame.time.Clock() = nac.NaC(on_change=self.on_game_changed,



    def on_game_changed(self, column, row, shape):


    def on_game_over(self, winner):


    def __init_graphics(self):

        graphics = {"background": pygame.image.load('graphics/blackboard_600x800.jpg'),
                    "grid": pygame.image.load('graphics/grid.png'),
                    "nought": pygame.image.load('graphics/nought.png'),
                    "cross": pygame.image.load('graphics/cross.png'),
                    "levels": pygame.image.load('graphics/levels.png'),
                    "radio_on": pygame.image.load('graphics/radio_on.png'),
                    "start": pygame.image.load('graphics/start.png'),
                    "new": pygame.image.load('graphics/new.png'),
                    "youwon": pygame.image.load('graphics/youwon.png'),
                    "youlost": pygame.image.load('graphics/youlost.png'),
                    "nowinner": pygame.image.load('graphics/nowinner.png')}

        return graphics

    def __init_rects(self):

        Top left coordinates, size and function
        for each area we wish to handle mouse clicks for

        rects = [UIrect(0,   0,   200, 200, lambda:, 
                 UIrect(200, 0,   200, 200, lambda:,
                 UIrect(400, 0,   200, 200, lambda:,
                 UIrect(0,   200, 200, 200, lambda:,
                 UIrect(200, 200, 200, 200, lambda:,
                 UIrect(400, 200, 200, 200, lambda:,
                 UIrect(0,   400, 200, 200, lambda:,
                 UIrect(200, 400, 200, 200, lambda:,
                 UIrect(400, 400, 200, 200, lambda:,

                 UIrect(28,  612, 246, 76,  lambda:,

                 UIrect(28,  710, 246, 76,  lambda:,

                 UIrect(322, 616, 44,  44,  lambda: self.__level_changed(nac.NaC.Levels.IDIOT)),

                 UIrect(322, 682, 44,  44,  lambda: self.__level_changed(nac.NaC.Levels.AVERAGE)),

                 UIrect(322, 744, 44,  44,  lambda: self.__level_changed(nac.NaC.Levels.GENIUS))]

        return rects

    def __point_in_rect(self, pos, rect):

        Check whether a coordinate is within a rectangle

        x2 = rect.x + rect.w
        y2 = rect.y +rect.h

        in_rect = (rect.x <= pos[0] < x2 and
                   rect.y <= pos[1] < y2)

        return in_rect

    def __click_to_func(self, pos):

        Iterates rectangles and calls corresponding function
        for any which have been clicked

        for rect in self.uirects:

            if(self.__point_in_rect(pos, rect)):


    def __handle_left_mousebuttondown(self, pos):


    def __level_changed(self, new_level): = new_level


    def __draw_game_window(self):["background"], (0,0))["grid"], (0,0))["levels"], (300,600))

        if == nac.NaC.Levels.IDIOT:
  ["radio_on"], (330,622))
        elif == nac.NaC.Levels.AVERAGE:
  ["radio_on"], (332,686))
        else:  # "genius"
  ["radio_on"], (334,748))["start"], (0,700))["new"], (0,600))

        x = 0
        y = 0
        for row in range(0,3):
            for column in range(0,3):
                if[row][column] == "X":
          ["cross"], (x,y))
                elif[row][column] == "O":
          ["nought"], (x,y))
                x += 200
            y += 200
            x = 0

        if == " ":
  ["nowinner"], (0,100))
        elif == "X":
  ["youwon"], (0,100))
        elif == "O":
  ["youlost"], (0,100))

    def __event_loop(self):

        Checks event queue every 50ms



            for event in pygame.event.get():

                if event.type == pygame.QUIT:

           = False

                elif event.type == pygame.MOUSEBUTTONDOWN:

                    if event.button == 1:

game = NaCPyGame()

Most of the code is carried across from Part 1 so I'll just describe the additional code which interact with the NaC class.


This now creates an instance of the NaC class, passing two functions. The first, on_game_changed, will be called by the NaC instance when the game state has changed, and the second, on_game_over, when the game is finished.

Both these functions simply call __draw_game_window which I'll get to shortly, and take arguments which are not used here but could come in useful depending on how the front end is coded. They are used, for example, in the console based game.


The first version of __init_rects function, which maps areas of the window to mouse clicks and their corresponding functions, simply printed to the terminal or selected/deselected radio buttons. In this, the final version, we actually call methods of the NaC class, the method names being self-explanatory.


There are three main areas where this function has been upgraded. Firstly, we need to check the level property of NaC to decide which radio button to select by drawing a small circle in the appropriate place. Next we need to iterate the rows and columns in NaC and draw an X or O where necessary. Finally we need to check whether the game is over, either with no winner, the human player winning or the computer winning. In each case the relevant graphic is shown.

Running the Program

Now we can run the program thus.

Running the Program


This is a screenshot of a completed game.

Noughts and Crosses or Tic Tac Toe with PyGame and Python

As I mentioned in the previous article this is my first PyGame project and I am reasonably pleased with the result. Seasoned PyGame developers might be shocked at my amateurishness - constructive comments welcome!

Along the way I have thought up a number of ways PyGame development in general could be improved and streamlined. These include:

  • Object oriented controls

  • The Command pattern to simplify mouse and keyboard options for the same function

  • Skinning

  • Binding controls to state

  • The Observer pattern

  • Separating UI design from code (possibly using XML?)

These will form the subjects of future articles, and I also have a Pyglet version of the Nought and Crosses game in the pipeline.