Make a Tic-Tac-Toe Board

Let's try another exercise where we create a Tic-Tac-Toe board. What would we need to do to accomplish this?

- create a 3x3 board (we can do this with the HTML 'table' tag);

- create 3 images: blank square, 'X' square, and 'O' square;

- need "feedback" variable to track who's turn it is, 'X' or 'O';

- need "feedback" variable(s?) to represent the status of each square;

- need code to check the status of each square and print the appropriate square image;

- if a square is blank, then make it a weblink with updated 'turn' and 'status' variables based on selecting this square;

- If a square is not blank, then print 'X' or 'O';

- we will need a 'clear' or 'reset' button;


We will just code a working board here, but this example could be expanded to add code that checks to see if anyone has won, and if so then it declares a winner.

The first step is to create the 3 images. I used the PAINT program on my Windows laptop to create blank_square.png, x_square.png, and o_square.png. All 3 of these images are 60x60 in pixel size. Then I copied these images to the Raspberry Pi using the pscp.exe binary. To do this I typed 'CMD' in the search bar and launched the 'DOS Command Prompt' application. This gives me a terminal window and puts me in my home directory. I change this directory to 'Desktop' by typing cd Desktop and pressing 'enter'. The I type the following command to copy these images to my Raspberry Pi: pscp.exe blank_square.png x_square.png o_square.png ubuntu@

Now we can craft the CGI script. The beginning of this CGI script can look like our previous ones, with just an update to the title:

ubuntu@ubuntu:~$ cat tictactoe.cgi



use strict;

use warnings;


print "Content-type: text/html\n\n";

print "<html>\n";

print "<head>\n";

print "<title>Tic-Tac-Toe</title>\n";

print "</head>\n";

print "<body><center>\n";


Let's create a '$me' variable for the name of this script, to make it easy to create links back to it. Let's also create the '$turn' and '$board' variables. For this script, I've decided to use a string variable to store the board status, where each individual character represents the status of a square - '0' for a blank square, '1' for an 'X' square, and 2 for an 'O' square:

my $me = "tictactoe.cgi";

# x = 1

# o = 2

my $turn = 1;

# index numbers represent the board like this:

# 0 1 2

# 3 4 5

# 6 7 8

# (this layout aligns with how HTML table data is written)

my $board = "000000000";


Note that lines that begin with '#' are comments in the code and are ignored by the scripting program.

Also note that the index numbering began with '0'. We do this because in code, indexing an array or list variable always begins with '0', and I am planning to split the '$board' string variable into an array variable when it comes time to process and print each square.

Now let's parse the QUERY_STRING and look for any updates to the '$turn' and '$board' variables:

# parse QUERY_STRING for 'turn' and 'board' input

if (exists $ENV{'QUERY_STRING'}) {

  my $qstr = $ENV{'QUERY_STRING'};

  my @settings = split /&/, $qstr;

  foreach my $s (@settings) {

    my @items = split /=/, $s;

    next unless (@items == 2);

    if ("$items[0]" eq "turn") {

      $turn = $items[1];


    if ("$items[0]" eq "board") {

      $turn = $items[1];





Now that we have the current '$turn' setting, let's calculate the 'next turn' so we can include it in any blank links that we will create. This is how we "take turns" in the game:

# calculate next turn based on given turn

my $nextturn = 1;

$nextturn = 2 if ($turn == 1);


Let's print the board! We will start with the 'table' HTML tag and the first 'table row':

print "<TABLE cellpadding=5 cellspacing=5 border=1><TR>\n";


The cellpadding, cellspacing, and border settings control how the table looks. When you get the code working, feel free to change these and see how the table changes!

Now we will loop over each position and print it. We know that there are 9 positions on the board, so create a 'for' loop with a '$i' variable that will index from 0 to 8:

for (my $i = 0; $i < 9; $i++) {


Now we are inside the 'for' loop and have been given an 'index' with the variable '$i'. Begin by printing the 'table data' tag for this square. Then let's split the given board layout into an array so we can index our current square status and decide what to print for this square:

  print "<TD>";

  my @pos = split //, $board;


If you give the split command "nothing" to split, then it splits the string into its individual characters.

If our position is '0' or blank, then we need to create a web link back to us that contains an updated board status variable that reflects the choice of this user ('X' or 'O') selecting this square. To create this updated board status, we will change this position in the local '@pos' array from 'blank' or '0' to the '$turn' value, and then use the join command to convert this '@pos' array back into a new 'board status' string that we can include in the web link:

  # if position is 0 then make a link that switches turns and

  # provides an updated board status

  if ("$pos[$i]" eq "0") {

    $pos[$i] = "$turn";

    my $newboard = join "", @pos;

    print "<A HREF=\"$me?turn=$nextturn&board=$newboard\">";

    print "<IMG SRC=\"/blank_square.png\"></A>";

By wrapping the web link 'A' tag around the blank square image, the whole image becomes a link. Note that the image names begin with a '/'. This tells the web server not to look for them in the local cgi-bin directory where the tictactoe.cgi script was found, but rather in the 'DocumentRoot' directory which is /var/www/html.

If this square is not blank then it must have either an 'X' or an 'O' in it. Print them:

  } else {

    # 'x' or 'o' belongs here, so print it

    if ("$pos[$i]" eq "1") {

      print "<IMG SRC=\"/x_square.png\">";

    } else {

      print "<IMG SRC=\"/o_square.png\">";




That's it! We have printed the appropriate "square" image in this table square, so we can print the ending tag for 'table data':

  print "</TD>\n";


Now before we close the 'for' loop, we need to check if we are at the end of the first or second row. If we are, then we need to close this 'table row' and start another one. Remember that array indexing starts with '0' when you calculate the end of a row:

  print "</TR><TR>\n" if ($i == 2 || $i == 5);



Now that we have finished the 'for' loop, we can print the ending tags for the last 'table row' and the 'table'. After that we can add our 'reset' button, which is just a link to this script with no arguments, and then print the ending tags for 'center', 'body', and 'html':

print "</TR></TABLE>\n";

print "<br><br>";

print "<A HREF=\"$me\">Reset</A>\n"; print "</center>\n";

print "</body>\n";

print "</html>\n";

When you finish writing your version, make it executable and run it to test for errors:

ubuntu@ubuntu:~$ chmod a+x tictactoe.cgi

ubuntu@ubuntu:~$ ./tictactoe.cgi

Content-type: text/html







<table cellpadding=5 cellspacing=5 border=1><tr>

<TD><A HREF="tictactoe.cgi?turn=2&board=100000000"><IMG SRC="/blank_square.png"></A></TD>

<TD><A HREF="tictactoe.cgi?turn=2&board=010000000"><IMG SRC="/blank_square.png"></A></TD>

<TD><A HREF="tictactoe.cgi?turn=2&board=001000000"><IMG SRC="/blank_square.png"></A></TD>


<TD><A HREF="tictactoe.cgi?turn=2&board=000100000"><IMG SRC="/blank_square.png"></A></TD>

<TD><A HREF="tictactoe.cgi?turn=2&board=000010000"><IMG SRC="/blank_square.png"></A></TD>

<TD><A HREF="tictactoe.cgi?turn=2&board=000001000"><IMG SRC="/blank_square.png"></A></TD>


<TD><A HREF="tictactoe.cgi?turn=2&board=000000100"><IMG SRC="/blank_square.png"></A></TD>

<TD><A HREF="tictactoe.cgi?turn=2&board=000000010"><IMG SRC="/blank_square.png"></A></TD>

<TD><A HREF="tictactoe.cgi?turn=2&board=000000001"><IMG SRC="/blank_square.png"></A></TD>


<br><br><A HREF="tictactoe.cgi">Reset</A>




ubuntu@ubuntu:~$ sudo cp tictactoe.cgi /var/www/cgi-bin/

ubuntu@ubuntu:~$ sudo cp blank_square.png x_square.png o_square.png /var/www/html/



If it looks good then copy it to the /var/www/cgi-bin/ directory, and copy the images to the /var/www/html directory. Then append /cgi-bin/tictactoe.cgi to your Raspberry Pi IP address in your browser URL window. Does it work?

This code is a really good example of software engineering. Another way that we could have created this Tic-Tac-Toe board would have been to create 9 different variables to represent the status of the 9 squares, and then just print out 9 'if' statements, one for each square. But by using a single '$board' variable and a 'for' loop, we were able to accomplish the same goal with less code.

This is part of the fun of software engineering: as long as the code works, there's no one "right" answer!