Official website for Linux User & Developer
FOLLOW US ON:
Apr
19

Build tic-tac-toe with Kivy

by Alexander Taylor

Alexander Taylor eases us into the workings of Kivy by creating the pen-and-paper classic in just over 100 lines of Python…

Project Files for this tutorial

Kivy is a highly cross-platform graphical framework for Python, designed for the creation of innovative user interfaces like multitouch apps. Its applications can run not only on the traditional desktop platforms of Linux, OS X and Windows, but also Android and iOS, plus devices like the Raspberry Pi.

That means you can develop cross-platform apps using Python libraries such as Requests, SQLAlchemy or even NumPy. You can even access native mobile APIs straight from Python using some of Kivy’s sister projects. Another great feature is the Cython-optimised OpenGL graphics pipeline, allowing advanced GPU effects even though the basic Python API is very simple.

Kivy is a set of Python/Cython modules that can easily be installed via pip, but you’ll need a few dependencies. It uses Pygame as a rendering backend (though its API is not exposed), Cython for compilation of the speedy graphics compiler internals, and GStreamer for multimedia. These should all be available through your distro’s repositories, or via pip where applicable.

With these dependencies satisfied, you should be able install Kivy with the normal pip incantation. The current version is 1.8.0, and the same codebase supports both python2 and python3. The code in this tutorial is also version- agnostic, running in python2.7 and python3.3.

Our kivy tic-tac-toe game
Our kivy tic-tac-toe game
pip install kivy

If you have any problems with pip, you can use easy_instalvia easy_install kivy.
There are also packages or repositories available for several popular distros. You can find more information on Kivy’s website.

A kivy application is started by instantiating and running an ‘App’ class. This is what initialises our pp’s window, interfaces with the OS, and provides
an entry point for the creation of our GUI. We can start by making the simplest Kivy app possible:

from kivy.app import App

class TicTacToeApp(App):
   pass

if __name__ == “__main__”:
   TicTacToeApp().run()

You can already run this, your app will start up and you’ll get a plain black window. Exciting!

We can build our own GUI out of Kivy widgets. Each is a simple graphics element with some specific behaviour of its own ranging from standard GUI functionality (eg the Button, Label or TextInput), to those that impose positioning on their child widgets (eg the BoxLayout, FloatLayout or GridLayout), to those abstracting a more involved task like interacting with hardware (eg the FileChooser, Camera or VideoPlayer). Most importantly, Kivy’s widgets are designed to be easily combined – rather than including a widget for every need imaginable, widgets are kept simple but are easy to join to invent new interfaces. We’ll see some of that in this tutorial.

Since ‘Hello World!’ is basically compulsory in any programming tutorial, let’s get it over with by using a simple ‘Label’ widget to display the text:

from kivy.uix.label import Label

We’ll display the ‘Label’ by returning it as our app’s root widget. Every app has a single root widget, the top level of its widget tree, and it will automatically be sized to fill the window. We’ll see later how to construct a full GUI by adding more widgets for this one, but for now it’s enough to set the root widget by adding a new method to the ‘App’:

def build(self):
   return Label(text=’Hello World!’,
      font_size=100,
      color=0, 1, 0, 1)) # (r, g, b, a)

The ‘build’ method is called when the ‘App’ is run, and whatever widget is returned automatically becomes the root widget of that App’. In our case that’s a Label, and we’ve set several properties – the ‘text’, ‘font_size’ and ‘color’. All widgets have different properties controlling aspects of their behaviour, which can be dynamically updated to alter their appearance later, though here we set them just once upon instantiation.

Note that these properties are not just Python attributes but instead Kivy properties. These are accessed like normal attributes but provide extra functionality by hooking into Kivy’s event system. We’ll see examples of creating properties shortly, and you should do the same if you want to use your variables with Kivy’s event or binding functionality.

That’s all you need to show some simple text, so run the program again to check that this does work. You can experiment with the parameters if it’s unclear what any of them are doing.

Our own widget: tic-tac-toe

Since Kivy doesn’t have a tic-tac-toe widget, we’ll have to make our own! It’s natural to create a new widget class to contain this behaviour:

from kivy.uix.gridlayout import GridLayout
class TicTacToeGrid(GridLayout):
   pass

Now this obviously doesn’t do anything yet, except that it inherits all the behaviour of the Kivy GridLayout widget – that is, we’ll need to tell it how many columns to have, but then it will automatically arrange any child widgets to fit nicely with as many rows as necessary. Tic-tac-toe requires three columns and nine children.

Here we introduce the Kivy language (kv), a special domain-specific language for making rules describing Kivy widget trees. It’s very simple but removes a lot of necessary boilerplate for manipulating the GUI with Python code – as a loose analogy you might think of it as the HTML/CSS to Python’s JavaScript. Python gives us the dynamic power to do anything, but all that power gets in the way if we just want to declare the basic structure of our GUI. Note that you never need kv language, you can always do the same thing in Python alone, but the rest of the example may show why Kivy programmers usually like to use kv.

Kivy comes with all the tools needed to use kv language; the simplest way is to write it in a file with a name based on our App class. That is, we should place the following in a file named ‘tictactoe.kv’:

<TicTacToeGrid>:
   cols: 3 # Number of columns

This is the basic syntax of kv language; for each widget type we may write a rule defining its behaviour, including setting its properties and adding child widgets. This example demonstrates the former, creating a rule for the ‘TicTacToeGrid’ widget by declaring that every ‘TicTacToeGrid’ instantiated should have its ‘cols’ property set to 3.

We’ll use some more kv language features later, but for now let’s go back to Python to create the buttons that will be the entries in our tic-tac-toe grid.

from kivy.uix.button import Button
from kivy.properties import ListProperty

class GridEntry(Button):
   coords = ListProperty([0, 0])

This inherits from Kivy’s ‘Button’ widget, which interacts with mouse or touch input, dispatching events when interactions toggle it. We can hook into these events to call our own functions when a user presses the button, and can set the button’s ‘text’ property to display the ‘X’ or ‘O’. We also created a new Kivy property for our widget, ‘coords’ – we’ll show how this is useful later on. It’s almost identical to making a normal Python attribute by writing ‘self.coords = [0, 0]’ in ‘GridEntry.__init__’.

As with the ‘TicTacToeGrid’, we’ll style our new class with kv language, but this time we get to see a more interesting feature.

<GridEntry>:
   font_size: self.height

As before, this syntax defines a rule for how a ‘GridEntry’ widget should be constructed, this time setting the ‘font_size’ property that controls the size of the text in the button’s label. The extra magic is that kv language automatically detects that we’ve referenced the Button’s own height and will create a binding to update this relationship – when a ‘GridEntry’ widget’s height changes, its ‘font_size’ will change so the text fits perfectly. We could have made these bindings straight from Python (another usage of the ‘bind’ method used later on), but that’s rarely as convenient as referencing the property we want to bind to.

Let’s now populate our ‘TicTacToeGrid’ with ‘GridEntry’ widgets (below). This introduces a few new concepts: When we instantiated our ‘GridEntry’ widgets, we were able to set their ‘coords’ property by simply passing it in as a kwarg. This is a minor feature that is automatically handled by Kivy properties.

class TicTacToeGrid(GridLayout):
   def __init__(self, *args, **kwargs):
      super(TicTacToeGrid, self).__init__(*args, **kwargs)
      for row in range(3):
         for column in range(3):
            grid_entry = GridEntry(
               coords=(row, column))
            grid_entry.bind(on_release=self.button_pressed)
            self.add_widget(grid_entry)

   def button_pressed(self, button):
      # Print output just to see what's going on
      print ('{} button clicked!'.format(instance.coords))

We used the ‘bind’ method to call the grid’s ‘button_pressed’ method whenever the `GridEntry` widget dispatches an ‘on_release’ event. This is automatically handled by its ‘Button’ superclass, and will occur whenever a user presses, then releases a ‘GridEntry’ button. We could also bind to ‘on_press’, which is dispatched when the button is first clicked, or to any Kivy property of the button, which is dispatched dynamically whenever the property is modified.

We added each ‘GridEntry’ widget to our ‘Grid’ via the ‘add_widget’ method. That means each one is a child widget of the ‘TicTacToeGrid’, and so it will display them and knows it should automatically arrange them into a grid with the number of columns we set earlier.

Now all we have to do is replace our root widget (returned from ‘App.build’) with a ‘TicTacToeGrid’ and we can see what our app looks like.

def build(self):
   return TicTacToeGrid()
   # Replaces the previous label

With this complete you can run your main Python file again and enjoy your new program. All being well, the single Label is replaced by a grid of nine buttons, each of which you can click (it will automatically change colour) and release (you’ll see the printed output information from our binding). We could customise the appearance by modifying other properties of the Button, but for now we’ll leave them as they are.

Has anyone won yet?

We’ll want to keep track of the state of the board to check if anyone has won, which we can do with a couple more Kivy properties:

from kivy.properties import (ListProperty, NumericProperty)

class TicTacToeGrid(GridLayout):
   status = ListProperty([0, 0, 0, 0, 0, 0, 0, 0, 0])
   current_player = NumericProperty(1)

This adds an internal status list representing who has played where, and a number to represent the current player (1 for ‘O’, -1 for ‘X’). By placing these numbers in our status list, we’ll know if somebody wins because the sum of a row, column or diagonal will be +-3. Now we can update our graphical grid when a move is played (below).

def button_pressed(self, button):
   # Create player symbol and colour lookups
   player = {1: 'O', -1: 'X'}
   colours = {1: (1, 0, 0, 1), -1: (0, 1, 0, 1)} # (r, g, b, a)

   row, column = button.coords # The pressed button is automatically
                               # passed as an argument

   # Convert 2D grid coordinates to 1D status index
   status_index = 3*row + column
   already_played = self.status[status_index]

   # If nobody has played here yet, make a new move
   if not already_played:
      self.status[status_index] = self.current_player
      button.text = {1: 'O', -1: 'X'}[self.current_player]
      button.background_color = colours[self.current_player]
      self.current_player *= -1 # Switch current player

You can run your app again to see exactly what this did, and you’ll find that clicking each button now places an ‘O’ or ‘X’ as well as a coloured background depending on whose turn it is to play. Not only that, but you can only play one move in each button thanks to our status array keeping track of existing moves.

This is enough to play the game but there’s one vital element missing… a big pop-up telling you when you’ve won! Before we can do that, we need to add some code to check if the game is over.

Kivy properties have another useful feature here, whenever they change they automatically call an ‘on_propertyname’ method if it exists and dispatch a corresponding event in Kivy’s event system. That makes it very easy to write code that will run when a property changes, both in Python and kv language. In our case we can use it to check the status list every time it is updated, doing something special if a player has filled a column, row or diagonal.

def on_status(self, instance, new_value):
   status = new_value

# Sum each row, column and diagonal.
# Could be shorter, but let’s be extra
# clear what’s going on

sums = [sum(status[0:3]), # rows
        sum(status[3:6]), sum(status[6:9]), sum(status[0::3]), # columns
        sum(status[1::3]), sum(status[2::3]), sum(status[::4]), # diagonals
        sum(status[2:-2:2])]

# Sums can only be +-3 if one player
# filled the whole line

if 3 in sums:
   print(‘Os win!’)
elif -3 in sums:
   print(‘Xs win!’)
elif 0 not in self.status: # Grid full
   print(‘Draw!’)

This covers the basic detection of a won or drawn board, but it only prints the result to stdout. At this stage we probably want to reset the board so that the players can try again, along with displaying a graphical indicator of the result (below).

# Note the *args parameter! It's important later when we make a binding
# to reset, which automatically passes an argument that we don't care about
def reset(self, *args):
   self.status = [0 for _ in range(9)]

   # self.children is a list containing all child widgets
   for child in self.children:
      child.text = ''
      child.background_color = (1, 1, 1, 1)

   self.current_player = 1

Finally, we can modify the `on_status` method to both reset the board and display the winner in a ‘ModalView’ widget.

from kivy.uix.modalview import ModalView

This is a pop-up widget that draws itself on top of everything else rather than as part of the normal widget tree. It also automatically closes when the user clicks or taps outside it.

winner = None if -3 in sums:
   winner = ‘Xs win!’
elif 3 in sums:
   winner = ‘Os win!’
elif 0 not in self.status:
   winner = ‘Draw...nobody wins!’

if winner:
   popup = ModalView(size_hint=0.75, 0.5))
   victory_label = Label(text=winner, font_size=50)
   popup.add_widget(victory_label)
   popup.bind(on_dismiss=self.reset)
   popup.open()

This mostly uses the same ideas we already covered, adding the ‘Label’ widget to the ‘ModalView’ then letting the ‘ModalView’ take care of drawing itself and its children on top of everything else. We also use another binding; this time to ‘on_dismiss’, which is an event dispatched by the ‘ModalView’ when it is closed. Finally, we made use of the ‘size_hint’ property common to all widgets, which in this case is used to set the ‘ModalView’ size proportional to the window – while a ‘ModalView’ is open you can resize the window to see it dynamically resize, always maintaining these proportions. This is another trick made possible by a binding with the ‘size_hint’ Kivy property, this time managed internally by Kivy.

That’s it, a finished program! We can now not only play tic-tac-toe, but our program automatically tells us when somebody has won, and resets the board so we can play again. Simply run your program and enjoy hours of fun!

Time to experiment

This has been a quick tour through some of Kivy’s features, but hopefully it demonstrates how to think about building a Kivy application. Our programs are built from individual Kivy widgets, interacting by having Python code run when their properties change (eg our ‘on_status’ method) or when they dispatch events (eg ‘Button’ ‘on_ release’). We also briefly saw kv language and experienced how it can automatically create bindings between properties.

You can find a copy of the full program on the disc, which you can reference to check you’ve followed everything correctly. We’ve also added an extra widget, the ‘Interface’, with a structure coded entirely in kv language that demonstrates how to add child widgets this way. You can test it by uncommenting the ‘return Interface()’ line in ‘TicTacToeGrid.build’. It doesn’t do anything fundamentally different to what we already covered, but it does make extensive use of kv language’s binding ability to automatically update a label showing the current player, and to resize the TicTacToeGrid so that it is always square to fit within its parent. You can play with all these settings to see exactly how it fits together, or try things like swapping out the different widget types to see how other widgets behave.

  • Tell a Friend
  • Follow our Twitter to find out about all the latest Linux news, reviews, previews, interviews, features and a whole more.
    • jsfour

      The full application runs, but following along, where the tutorial says “Now all we have to do is replace our root widget (returned from ‘App.build’) with a ‘TicTacToeGrid’ and we can see what our app looks like” the app fails and returns AttributeError: ‘TicTacToeGrid’ object has no attribute ‘_trigger_layout’.

      Also I notice a typo: color=0, 1, 0, 1)) # (r, g, b, a)

      (missing begin paren for color attribute set)

    • http://www.fireboxtraining.com/ Seth Williams

      That’s an excellent tutorial Alexander. Looking forward to more such tutorials on Kivy. You can check out some more tutorials on http://www.fireboxtraining.com/python.

    • Liam

      There’s also another typo, it should be print (‘{} button clicked’.format(button.coords)) , rather than instance.coords

    • Uplink

      The project zip at the top of the article is unusable. It has one main.py file in it that contains both the .py and the .kv files (kv is under a human-readable piece of text), with extra indentation, with extra data (numbers) on some of the lines.