Archive for January, 2010

27
Jan
10

Tic-Tac-Toe: C

Coming back to C feels comfortable and nostalgic. It was my first real language as a programmer, not counting a brief dalliance with Perl and childhood BASIC programs typed in from a magazine. If you look closely, you can still see dried blood on some of its sharp edges.

I was a little surprised how naturally it all came back, and there’s something comforting about the almost complete lack of unpredictable magic. It’s pretty much doing exactly what you tell it to do, which, especially on days when I’ve been fighting with rails, is kind of refreshing.

Admittedly, I avoided some of the pitfalls. I used the stack entirely, sidestepping malloc headaches, and of course this is a single-module program, so no fighting with the preprocessor and linker.

I’m surprised to find that it’s among the shorter implementations so far, but maybe I shouldn’t be.

This is compiled against gcc 4.2.1 in pedantic ANSI mode, so it should build most anywhere.


#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MIN_SPACE 1
#define MAX_SPACE 9

typedef enum PLAYER { EMPTY, X, O } player;
typedef player board[3][3];
typedef enum BOOL { false, true } bool;

#define SPACE(board, space) board[((space-1)/3)][(space-1)%3]

char P2C[3] = { ' ', 'X', 'O' }; 

/*
 * Player Functions
 */
player other_player(player player) {
	if (X == player) return O;
	if (O == player) return X;
	return EMPTY;
}

/*
 * Board functions
 */
bool space_available(board board, int space) {
	return SPACE(board, space) == EMPTY;
}

void make_move(board board, player player, int space) { 
	SPACE(board, space) = player;
}

bool triple_match(board board, int s1, int s2, int s3) {
	return SPACE(board, s1) != EMPTY 
		&& SPACE(board, s1) == SPACE(board, s2)
		&& SPACE(board, s1) == SPACE(board, s3);
}

bool winner(board board) {
	return triple_match(board, 1, 2, 3) /* rows */
		|| triple_match(board, 4, 5, 6)
		|| triple_match(board, 7, 8, 9)

		|| triple_match(board, 1, 4, 7) /* columns */
		|| triple_match(board, 2, 5, 8)
		|| triple_match(board, 3, 6, 9)

		|| triple_match(board, 1, 5, 9) /* diagonals */
		|| triple_match(board, 3, 5, 7);
}

bool full(board board) {
	int s;

	for(s = 1; s <= 9; s++) {
		if (space_available(board, s)) {
			return false;
		}
	}
	return true;
}

/*
 * Output
 */

void print_space(player space, int x, int y) {
	if (EMPTY == space) {
		printf("(%d)", (y*3) + x + 1);
	} else {
		printf(" %c ", P2C[space]);
	} 
}

void print_board(board board) {

	int x, y;

	putchar('\n');
	for (y = 0; y < 3; y++) {
		for (x = 0; x < 3; x++) {
			print_space(board[y][x], x, y);
			if (x < 2) putchar('|');
		}
		if (y < 2) puts("\n---+---+---");
	}
	putchar('\n');
	putchar('\n');
}

/*
 * Input
 */
void read_space(int * s) {
	int rcode;
	rcode = scanf("%d", s);
	if (EOF == rcode) {
		exit(1);
	} else if (rcode <= 0) {
		*s = -1;
	}
}

/*
 * Main function
 */
int main(char * argv[], int argc) {
	board board;
	player current_player = X;
	int space;

	memset(board, EMPTY, sizeof(board));

	while (true) {
		print_board(board);

		if (winner(board)) {
			return printf("%c Wins!\n", P2C[other_player(current_player)]) <= 0;
		} else if (full(board)) {
			return printf("It's a Draw!\n") = MIN_SPACE && space <= MAX_SPACE && space_available(board, space)) {
			make_move(board, current_player, space); 
			current_player = other_player(current_player);
		}
	}

	return 0;
}


20
Jan
10

Tic-Tac-Toe: Lua

The first thing I noticed about lua was how absurdly fast the runtime built. I’m used to a language runtime taking a few minutes at least, and as long as hours (haskell). An install via ports, including downloading the source and installing dependencies took less than a minute on my 2-year-old laptop.  One of lua’s stated design goals is compactness, and they’re not kidding.

The cost is that there are features that I would consider relatively basic that are not included.  For example, despite having a pretty good functional foundation with built-in “lists” and first-class functions, it doesn’t provide any way to do map/collect or filter/select out of the box.  Pretty simple to make, but one of my guidelines is to try to avoid pushing a language in a direction it doesn’t want to go.  So my implementation ended up going in a relatively imperative direction.  Once I accepted that, it was a very smooth ride.  Everything felt pretty familiar.  But for all that, I’m left feeling a bit like I’ve missed some key elements of a subtle and elegant language.  This is perhaps the price I pay for my deliberate superficiality; Lua is on my list to go back and look at more deeply.

More notes:

  • Lua has a unique (?) fundamental data structure called a table.  It will behave like a list, or an array, or a hash, or all three at once!   I didn’t really make use of it except as an array, but it’s intriguing.
  • 1-based array indices.  Bah!  I’m sure the Pascal people are happy.
  • I know it’s used as the scripting language for several packages out there (Lightroom, World of Warcraft) and it seems very well-suited to that.  It’s tiny, and skill-scalable — simple for beginners to get started in, but with room to grow for advanced users.

Here’s the code (runs against Lua 5.1.4):

#!/usr/bin/env lua

-- Constants

X = "X";
O = "O";

-- Starting Data 

board = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
current_player = X;
winning_player = nil;

-- Board I/O

function print_board()
	print();
	print_row(board[1]);
	print_sep();
	print_row(board[2]);
	print_sep();
	print_row(board[3]);
	print();
end

function print_sep()
	print("---+---+---");
end

function print_row(r)
	print(display_cell(r[1]) .. "|" ..  display_cell(r[2]) ..  "|" .. display_cell(r[3]))
end

function display_cell(c)
	if type(c) == "number" then
		return "(" .. c .. ")"
	else
		return " " .. c .. " "
	end
end

function print_prompt(player)
	io.write("Select a square, " .. player .. ": ");
end

-- Placement

function row(space)
	return math.floor((space - 1) / 3) + 1
end

function col(space)
	return (space - 1) % 3 + 1
end

function place_marker(player, space)
	if space > 9 or space < 1 then
		return false
	end

	local the_row = board[row(space)];
	local cell = the_row[col(space)]
	if type(cell) == 'number' then
		the_row[col(space)] = player
		return true
	else
		return false
	end
end

function read_choice()
	return io.read("*n", "*l");
end

function row_equal(n)
	if board[n][1] == board[n][2] and board[n][2] == board[n][3] then
		winning_player = board[n][1];
		return true;
	else 
		return false;
	end
end

function col_equal(n)
	if board[1][n] == board[2][n] and board[2][n] == board[3][n] then
		winning_player = board[1][n];
		return true;
	else 
		return false;
	end
end

-- for our purposes, a draw is a "win",
-- but the current_winner variable is nil
function detect_winner()
	for i = 1, 3 do
		if row_equal(i) then
			return true;
		end
		if col_equal(i) then
			return true;
		end
	end

	-- diagonals
	if board[1][1] == board[2][2] and board[2][2] == board[3][3] then
		winning_player = board[1][1];
		return true;
	end

	if board[1][3] == board[2][2] and board[2][2] == board[3][1] then
		winning_player = board[1][3];
		return true;
	end

	-- draw
	-- if there are any numbers left, it's not a draw,
	for i = 1, 3 do
		local rowstr = table.concat(board[i])
		if string.find(rowstr, "%d") then
			return false;
		end
	end

	return true;

end

function print_winner()
	if winning_player == nil then
		print("It's a Draw!")
	else
		print(winning_player .. " Wins!")
	end
end

function next_player()
	if current_player == X then
		current_player = O
	else
		current_player = X
	end
end

-- "main"

while true do
	print_board(board);
	if detect_winner() then
		print_winner();
		break
	end

	print_prompt(current_player);
	local space_selected = read_choice();
	if place_marker(current_player, space_selected) then
		next_player();
	end
end
13
Jan
10

Tic-Tac-Toe: COBOL

I have to say, COBOL was more fun than I was expecting. I’m sure working on ancient, multi-KLOC mainframe apps is not as fun. But for my purposes, explicitly laying out my working memory and writing keywords in all caps is entertaining. More impressions:

  • COBOL is very bureaucratic. This is not a surprise, in itself, but it is surprising just how bureaucratic it is. Rigid divisions and sections for required metadata, working data, the equivalent of procedure parameters, the procedures themselves. Lots of verbiage and boilerplate. From a modern perspective, I think of high level languages as abstracting away much of the nitty gritty of memory management, function call mechanics, etc. COBOL strikes me, instead, as a way of supporting and enforcing organizational best practices of the day, while leaving a fair bit of the nitty gritty up to the programmer.
  • The punch card legacy is alive and well. Top-level lines must begin in column 8, and any text after column 80 is simply ignored. (If vim’s syntax highlighting had not warned me, I’m sure I’d have lost many hours to this.) There is a “free” layout mode available in more recent COBOL, but what fun is that?
  • You don’t really have typed variables. In fact, everything is, more or less, a string as far as I can tell. Instead, you lay out sections of memory, give them labels, and show the compiler a “picture” of how the memory is structured. The only type information you do have is that you can specify whether a given memory region should contain alphabetic, numeric, or alphanumeric characters. Alphabetic is represented by “A”, numeric by “9″ and alphanumeric by “X”. So, for example “XXX9A” represents a 5-byte region that should contain three characters, a number, and a letter. You can also define totally different overlays on top of the same region of memory — which you would routinely do in order to, say, define an array with initial values. You can see this below where I used the “REDEFINES” keyword.
  • The wild-west memory layout and overlays, together with call by reference, let me do some magic tricks. For instance, I can check for a tie by just asking if the whole region where I keep the board placements is numeric. Or, I lay out the whole, formatted board at once, and overwrite sections of it as I go, rather than reformatting each time. I assume that this is the sort of “cleverness” that modern languages hope to prevent. :)
  • The syntax bears a strong resemblance to SQL. I presume this is not accidental.

Here’s the code:

       IDENTIFICATION DIVISION.
       PROGRAM-ID.  TicTacToe.
       DATA DIVISION.
       WORKING-STORAGE SECTION.

       01 CurrentPlayer     PIC A VALUE "X".
       01 CurrentMove       PIC 9(10).
       01 RowSeparator      PIC X(11) VALUE "---+---+---". 

      * The board, for calculation purposes
       01 CurrentBoard.
           02 CurrentBoardValues        PIC X(9) VALUE "123456789".
           02 CurrentBoardTable REDEFINES CurrentBoardValues.
               03 Cell OCCURS 9 TIMES PIC X.

      * The board, for display purposes
       01 BoardForDisplay.
           02 BoardValuesForDisplay.
               03 RowOne        PIC X(11) VALUE "(1)|(2)|(3)".
               03 FILLER        PIC X.
               03 RowTwo        PIC X(11) VALUE "(4)|(5)|(6)".
               03 FILLER        PIC X.
               03 RowThree      PIC X(11) VALUE "(7)|(8)|(9)".
               03 FILLER        PIC X.

           02 FILLER REDEFINES BoardValuesForDisplay.
               03 DisplayCell   OCCURS 9 TIMES PIC X(4).

       01 GameOver          PIC X VALUE 'F'.

       PROCEDURE DIVISION.
       Begin.
           PERFORM WITH TEST AFTER UNTIL GameOver EQUAL 'T'
               PERFORM DisplayBoard
               DISPLAY "Select a square, " CurrentPlayer ": "
                   WITH NO ADVANCING
               ACCEPT  CurrentMove
               IF CurrentMove > 0 AND CurrentMove < 10 AND
                       Cell(CurrentMove) NUMERIC
                   MOVE CurrentPlayer TO Cell(CurrentMove)
                   CALL "FormatCell" USING BY CONTENT CurrentPlayer
                       BY REFERENCE DisplayCell(CurrentMove)
                   PERFORM CheckForWin
                   PERFORM CheckForDraw
                   PERFORM SwitchPlayer
               END-IF
           END-PERFORM.
           STOP RUN.

       DisplayBoard.
           DISPLAY ""
           DISPLAY RowOne
           DISPLAY RowSeparator
           DISPLAY RowTwo
           DISPLAY RowSeparator
           DISPLAY RowThree
           DISPLAY "".
           
       CheckForWin.
           IF Cell(1) EQUAL Cell(2) AND Cell(2) EQUAL Cell(3)
                   OR Cell(4) EQUAL Cell(5) AND Cell(5) EQUAL Cell(6)
                   OR Cell(7) EQUAL Cell(8) AND Cell(8) EQUAL Cell(9)
                   OR Cell(1) EQUAL Cell(4) AND Cell(4) EQUAL Cell(7)
                   OR Cell(2) EQUAL Cell(5) AND Cell(5) EQUAL Cell(8)
                   OR Cell(3) EQUAL Cell(6) AND Cell(6) EQUAL Cell(9)
                   OR Cell(1) EQUAL Cell(5) AND Cell(5) EQUAL Cell(9)
                   OR Cell(3) EQUAL Cell(5) AND Cell(5) EQUAL Cell(7)
               PERFORM DisplayBoard
               DISPLAY CurrentPlayer " Wins!"
               SET GameOver TO 'T'
           END-IF.

       CheckForDraw.
           IF CurrentBoard ALPHABETIC AND GameOver NOT EQUAL 'T'
               PERFORM DisplayBoard
               DISPLAY "It's a Draw!"
               SET GameOver TO 'T'
           END-IF.

       SwitchPlayer.
           IF CurrentPlayer EQUAL "X" THEN
               SET CurrentPlayer TO "O"
           ELSE
               SET CurrentPlayer TO "X"
           END-IF.


       IDENTIFICATION DIVISION.
       PROGRAM-ID. FormatCell
       DATA DIVISION.
       LINKAGE SECTION.

       01 CellValue             PIC X.

       01 CellRepresentation.
           02 LeftPad           PIC X.
           02 ContentSpace      PIC X.
           02 RightPad          PIC X.
           02 FILLER            PIC X.

       PROCEDURE DIVISION USING CellValue, CellRepresentation.
       Begin.
           MOVE CellValue to ContentSpace
           IF CellValue NUMERIC
               MOVE "(" TO LeftPad
               MOVE ")" TO RightPad
           ELSE
               MOVE " " TO LeftPad
               MOVE " " TO RightPad
           END-IF.
           EXIT PROGRAM.

       END PROGRAM FormatCell.

       END PROGRAM TicTacToe.

06
Jan
10

Tic-Tac-Toe: Ruby

Here’s the first one, to kick it off, along with my test script. Ruby is my day-to-day language these days, so it was the natural choice for a reference implementation. I love Ruby as a language, for all its warts, for the decidedly squishy reason that it just feels right. No other language has ever given me the sensation of thinking straight into code as clearly and immediately as Ruby. It’s slow, and idiosyncratic, and not the most compact and elegant language around, but it sure gets out of my way when I want to express something.

But enough has been said about it, and I figure most of you reading this already know something about it. So I’ll save my verbosity for the more obscure ones.

The listing is below, and here’s the code if you want to actually run it and satisfy yourself I’m not cheating. :)

http://github.com/kemiller/tic-tac-toe/tree/master/ruby

PS – If anyone can tell me how to get a gist working with hosted WordPress, I’ll replace the plain-jane listing with something better.

#!/usr/bin/env ruby 
 
 
# Represents the state of a single game, including the board, the current player,
# and the winner, if any.
#
class Game
 
  def initialize(first_player)
    @current_player = first_player
    @board = Board.new
    @state = :continue
  end
 
  # Make a move for the current player. 
  def move(space)
    @board.move(@current_player, space)
    @state = 
      if @board.winning?
        :win
      elsif @board.full?
        :draw
      else
        @current_player = @current_player.opponent
        :continue
      end
  rescue InvalidSpaceException
    :continue
  end
 
  def to_s
    "\n#{@board.to_s}\n\n" +
      case @state
      when :win
        "#{@current_player} Wins!\n"
      when :draw
        "It's a Draw!\n"
      when :continue
        "Select a square, #{@current_player}: "
      end
  end
 
  def main
 
    while @state == :continue
      print self
      move(gets.to_i)
    end
 
    print self
  end
end
 
# Represents the current state of a single space on the board
class Space
 
  attr_reader :player, :number
 
  def initialize(number)
    @number = number
  end
 
  def place(player)
    raise InvalidSpaceException if occupied?
    @player = player
  end
 
  def occupied?
    @player != nil
  end
 
  def ===(other)
    (@player == other.instance_variable_get(:@player)) && self
  end
 
  def to_s
    if occupied?
      " #{@player} "
    else
      "(#{@number})"
    end
  end
end
 
class InvalidSpaceException  @board.size  || n < 1
    @board[n-1]
  end
 
  def row_arrays 
    [@board[0,3], @board[3,3], @board[6,3]]
  end
 
  def rows 
    @rows ||= row_arrays.map { |row| Slice.new(row) }
  end
 
  def columns
    @columns ||= row_arrays.transpose.map { |col| Slice.new(col) }
  end
 
  def diagonals
    @diagonals ||= 
      [ Slice.new(space(1),space(5),space(9)),
        Slice.new(space(7),space(5),space(3)) ]
  end
 
end
 
# Represents a 3-in-a-row subset of the spaces on the board
class Slice
  def initialize(*spaces)
    @spaces = spaces.flatten
  end
 
  def winning?
    (result = @spaces.reduce(:===)) && result.player
  end
 
  def to_s
    @spaces.join("|")
  end
 
end
 
# Players
X = 'X'
O = 'O'
 
def X.opponent; O; end
def O.opponent; X; end
 
Game.new(X).main
05
Jan
10

The Tic-Tac-Toe Project

In the year 2010, I will post a simple text-only tic-tac-toe application in a different programming language every week.

I’m not sure where precisely the inspiration for this arose, but I know I’ve been fascinated with the variety in programming languages for as long as I’ve been exposed to them. Even more than most human languages, a programming language shapes the sorts of ideas it even occurs to you to express, and I believe that to call yourself a professional, you ought to know more than one, more than those you make your living off of, and preferably one or two from each of the high-level language families. This might have a practical benefit if it leads to the next paycheck, but more interesting is the “cross-training” that learning an unfamiliar modality brings. While straining to learn a truly novel abstract concept, I find it’s almost as if I can feel the new neural pathways forming. I may not be able to directly use, say, monads in my day job, but learning how they work changes how I think about state.

But this project is not really about that.  This is at once more extreme, and less serious. Call it adventures in programming dilettantism. A breadth-first exploration of the brilliance and madness our arcane art has to offer.

I chose tic-tac-toe (noughts and crosses) because it’s easy to understand what it should do, complex enough to hit on a variety of language features, yet simple enough to implement that I can reasonably churn them out at a pace of one a week while remaining happily married and employed.

But I need your help. I’ve assembled a partial list of the 52 languages, grouped into categories, but as you can see, there are still empty slots, and room for a few alternates. Let me know what you think should fill them, and I’ll update the list. The only restriction for now is that I must be able to obtain a legal, zero-cost implementation. I’ve already implemented a few “in reserve.”

UPDATE: Thanks for the great suggestions, everyone.  I reserve the right to swap things in if the insane ones kill me.  :)

Mainstream

  1. Ruby
  2. Python
  3. Java
  4. C#
  5. PHP
  6. C
  7. C++
  8. Javascript
  9. Perl
  10. Objective-C
  11. Scala
  12. Visual Basic
  13. x86 Assembly
  14. JVM Opcodes

Academic or Niche

  1. Common Lisp
  2. Scheme
  3. Haskell
  4. Ocaml
  5. Prolog
  6. APL (or J)
  7. Smalltalk
  8. Eiffel
  9. Erlang
  10. Oz
  11. Cobra
  12. Falcon
  13. Rebol
  14. Clojure
  15. Ada
  16. Dylan
  17. factor

Obsolescent (?)

  1. COBOL
  2. Fortran
  3. Simula
  4. Pascal
  5. Basic (Old School)
  6. Hypercard

Insane

  1. brainf*ck
  2. Whitespace
  3. LOLCODE
  4. Piet

Misc/Other

  1. MySQL Procedure Language
  2. Awk
  3. Lua
  4. Self
  5. Go
  6. Io
  7. Logo
  8. Bash
  9. PowerShell
  10. D
  11. Joy
  12. Applescript
  13. CoffeeScript



Follow

Get every new post delivered to your Inbox.