I'm Brett Slatkin and this is my personal site. I write code. These are my projects:

24 July 2012

Programming for Newbs: An Existential Crisis

At a recently a family gathering a relative of mine mentioned how they were legitimately interested in learning how to program. I was extremely excited to indulge in this and teach what I usually explain in broken metaphors (e.g., "it's like very complicated dominos").

I wanted to provide the simplest introduction that would motivate someone to learn more while not seeming too difficult. I decided that, clearly, Tic-Tac-Toe would be the best demo. The idea is the person would sit at my side while I wrote the program. I would explain each step, with my pupil eventually telling me what the final bits should be.

The challenge

Initially I assumed Python would be the best choice for this, since it reads like pseudo-code and is my go-to language. But after a couple of days I realized that text printing just doesn't excite people like interactive graphics, no matter how simple. I could do this in Python using something like pygame, but I don't know that API and it's hardly the lean introduction I'm looking for. It seemed that the best way to demo Tic-Tac-Toe would be using JavaScript, HTML, and CSS.

As a fun challenge, I wrote the same introductory Tic-Tac-Toe program in JavaScript and Python. I went as fast as I could, and made no more changes as soon as the program worked correctly. I used jQuery for JS and nothing special for Python. In both cases it took me about 20 minutes from nothing to done. The JavaScript/HTML/CSS page is 140 lines. The Python program is 80 lines. The code for both is pretty ugly.

Here's some sample output from the Python program:
X's turn (type xy): 21
  
      1   2   3
    +---+---+---+
  1 |   | X |   |
    +---+---+---+
  2 | O | X |   |
    +---+---+---+
  3 |   |   |   |
    +---+---+---+
Here's the entire JavaScript program in an iframe:


The Outcome: Reevaluating

Though I enjoy writing JavaScript because of what it enables, I hate its gotchas and limitations. In contrast, I enjoy Python thoroughly as a language, and only get frustrated by its occasional bottlenecks. Before this project I've assumed that JS would be awful for beginners. When I read that Khan Academy would be teaching CS with JS I thought that was a bad plan.

But now I think I was wrong. Getting beginners interested in learning programming is the hard part. Once they want to learn, programming itself is rewarding enough that it becomes addictive. So all you need is the right hook. And I find the graphical Tic-Tac-Toe demo to be that so much more than the command-line one.


Postscript

For reference, here's the Python code:
def run():
    board = [
      [' ', ' ', ' '],
      [' ', ' ', ' '],
      [' ', ' ', ' '],
    ]

    color = 'X'

    print 'To play, type the coordinates of where you want to go.'
    print 'For example, the middle square is 22, the top right is'
    print '31, and the bottom left is 13.'

    while True:
      print
      print '    1   2   3'
      print '  +---+---+---+'
      for i, row in enumerate(board):
        row = tuple([i+1] + row)
        print '%d | %s | %s | %s |' % row
        print '  +---+---+---+'

      # Figure out where the move is
      command = raw_input("%s's turn (type xy): " % color)
      if not len(command) == 2:
        print 'Bad command'
        continue

      try:
        column = int(command[0])
        row = int(command[1])
      except ValueError:
        print 'Command not numbers'
        continue

      if not (3 >= column >= 1):
        print 'Bad column'
        continue

      if not (3 >= row >= 1):
        print 'Bad row'
        continue

      column -= 1
      row -= 1
      if board[row][column] != ' ':
        print 'Illegal move'
        continue

      # Apply the move and swap the player
      board[row][column] = color
      if color == 'X':
        color = 'O'
      else:
        color = 'X'

      # Check for a winner
      # Horziontal
      for row in board:
        if row[0] != ' ' and (row[0] == row[1] == row[2]):
          print '%s wins!' % row[0]
          return

      # Vertical
      for i in xrange(3):
        if board[0][i] != ' ' and (board[0][i] == board[1][i] == board[2][i]):
          print '%s wins!' % board[0][i]
          return

      # Diagnol
      if board[0][0] != ' ' and (board[0][0] == board[1][1] == board[2][2]):
        print '%s wins!' % board[0][0]
        return

      if board[0][2] != ' ' and (board[0][2] == board[1][1] == board[2][0]):
        print '%s wins!' % board[0][2]
        return

  run()
And here's the JavaScript code:
<!doctype html>
  <html>
  <head>
    <meta charset="utf-8">
    <title>Existential Crisis: Tic-Tac-Toe</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>

    <style type="text/css">
      table td {
        width: 200px;
        height: 200px;
        border: 1px solid #000;
        text-align: center;
        font-size: 108px;
        vertical-align: middle;
      }
      table {
        border-collapse: collapse;
      }
      .play-X {
        color: #000;
      }
      .play-O {
        color: #f00;
      }
    </style>

  </head>
  <body>

  <h1>Tic-Tac-Toe</h1>

  <table>
    <tr>
      <td data-row="0" data-column="0">&nbsp;</td>
      <td data-row="0" data-column="1">&nbsp;</td>
      <td data-row="0" data-column="2">&nbsp;</td>
    </tr>
    <tr>
      <td data-row="1" data-column="0">&nbsp;</td>
      <td data-row="1" data-column="1">&nbsp;</td>
      <td data-row="1" data-column="2">&nbsp;</td>
    </tr>
    <tr>
      <td data-row="2" data-column="0">&nbsp;</td>
      <td data-row="2" data-column="1">&nbsp;</td>
      <td data-row="2" data-column="2">&nbsp;</td>
    </tr>
  </table>

  <script type="text/javascript">
  var WINNER = false;
  var BOARD = [
    ['', '', ''],  // [column 0, column 1, column 2]
    ['', '', ''],
    ['', '', '']
  ];
  var COLOR = 'X';

  $('td').click(function(e) {
    if (WINNER) {
      return;
    }

    // Mark the move, if legal.
    var target = $(e.target);
    var column = parseInt(target.data('column'));
    var row = parseInt(target.data('row'));
    if (!BOARD[row][column]) {
      BOARD[row][column] = COLOR;
    } else {
      alert('Illegal move!');
      return;
    }

    // Toggle who's playing.
    if (COLOR == 'X') {
      COLOR = 'O';
    } else {
      COLOR = 'X';
    }

    // Update board state.
    $('td').each(function() {
      var cell = $(e.target);
      var column = parseInt(cell.data('column'));
      var row = parseInt(cell.data('row'));
      if (BOARD[row][column]) {
        cell.text(BOARD[row][column]);
        cell.addClass('play-' + BOARD[row][column]);
      } else {
        cell.html('&nbsp;');
        cell.attr('class', '');
      }
    });

    // Check for a winner.
    // Horizontal
    for (var i = 0; i < 3; i++) {
      if (BOARD[i][0] &&
          BOARD[i][0] == BOARD[i][1] &&
          BOARD[i][1] == BOARD[i][2]) {
        alert(BOARD[i][0] + ' has won!');
        WINNER = true;
        return;
      }
    }

    // Vertical
    for (var i = 0; i < 3; i++) {
      if (BOARD[0][i] &&
          BOARD[0][i] == BOARD[1][i] &&
          BOARD[1][i] == BOARD[2][i]) {
        alert(BOARD[0][i] + ' has won!');
        WINNER = true;
        return;
      }
    }

    // Diagnol
    if (BOARD[0][0] &&
        BOARD[0][0] == BOARD[1][1] &&
        BOARD[1][1] == BOARD[2][2]) {
      alert(BOARD[0][0] + ' has won!');
      WINNER = true;
      return;
    }
    if (BOARD[0][2] &&
        BOARD[0][2] == BOARD[1][1] &&
        BOARD[1][1] == BOARD[2][0]) {
      alert(BOARD[0][2] + ' has won!');
      WINNER = true;
      return;
    }
  });
  </script>

  </body>
  </html>
You can find code for both on github.

Follow-up

Comment with Open ID

Comment with Google+

© 2009-2014 Brett Slatkin