10
Mar
10

Tic-Tac-Toe: Cobra

Cobra is a work-in-progress language that primarily targets the CLR. As far as I could tell, it doesn’t do much pioneering of new ideas, but it does attempt to bring together a bunch of basically good ideas in one place. These include:

  • Mixed static and dynamic typing
  • First class unit tests
  • First class contracts
  • Semantic Indenting
  • Clean, expressive syntax
  • Full-featured OOP (with interfaces, mixins, generics)
  • Accurate decimal arithmetic
  • High performance

How well does it do all this? Reasonably well, I have to say.  The biggest missing features are closures, an an interactive runtime, but according to this presentation they’re on the way. I was excited about the mix of static and dynamic typing. I’ve wanted for a while to be able to hack something up without types and then go back through and tighten things up later. Cobra doesn’t seem like it’s 100% there. You can certainly omit type declarations, but it wasn’t always the case that a semantically valid program would run correctly without its type declarations. For example, there was one place where I left a member variable untyped, and then assigned a list to it in the initializer. It crashed like that. Adding the type declaration fixed it without any other changes.

The documentation is still quite spotty, as you might expect from an unfinished project. Most (all?) of the standard data types seem to be based directly on the .NET standard library, so you can go look some things up on MSDN.

Built-in unit tests are very cool. I could see them being overused and cluttering things up (it would be nice to have the option to pull them back out into another file when you’re done) but having real support from the runtime means that the compiler can tell you things about your test that other languages can’t. For example, assert 2 == 1 will give you an error message like:

(MonoType)
info       = nil
this       = Cell (MonoType)
(2 == 1) = false

No need for awkward constructions like assert_equal or 2.should == 1

There is a JVM port underway, and also what looks like the beginnings of a port to the Objective-C runtime. Looks like one to watch.

Here’s the code (for version 0.8.0 and mono):


class TicTacToe
	"""
	Tic Tac Toe in Cobra
	"""
	def main is shared
		players = Ring("X", "O")
		board = Board(3)

		player = players.next
		cellsRemaining = 9

		while true
			print board

			Console.write('Select a square, [player]: ')
			Console.out.flush
			try
				answer = Console.readLine
				Console.out.flush
			catch System.ArgumentNullException
				break

			cell = board.getCell(answer)
			if not cell
				continue

			cellsRemaining -= 1

			cell.value = player
			if cell.matchAny(3)
				print board
				print "[player] Wins!"
				break

			if cellsRemaining <= 0
				print board
				print "It's a Draw!"
				break

			player = players.next

class Ring
	"""
	A Ring class that takes its inputs and will return one
	after the other ad infinitum.
	"""
	var _enumerator as IEnumerator

	test
		ring = Ring(1, 2, 3)
		assert ring.next == 1
		assert ring.next == 2
		assert ring.next == 3
		assert ring.next == 1

	cue init(items as vari T)
		base.init
		_enumerator = List(items).getEnumerator

	def next as T
		if _enumerator.moveNext
			return _enumerator.current
		else
			_enumerator.reset
			return .next

class Cell
	"""
	A mesh-aware cell on the tic-tac-toe board.  It keeps track
	of its neighbors and can detect winners that way.
	"""
	pro address from var as String
	pro value from var as String?

	enum Dir
		East
		Southeast
		South
		Southwest
		West
		Northwest
		North
		Northeast

	# Neighbors
	var _neighbors = Dictionary() 

	cue init(address as String)
		base.init
		.address = address
		for i in 8
			_neighbors[i to Dir] = nil

	cue init(address as String, value as String)
		.init(address)
		.value = value

	def opposite(dir as Dir) as Dir
		test
			cell = Cell('2')
			assert cell.opposite(Dir.East) == Dir.West
			assert cell.opposite(Dir.West) == Dir.East
			assert cell.opposite(Dir.Northeast) == Dir.Southwest
		body
			val = dir to int
			val = (val + 4) % 8
			return val to Dir

	def add(dir as Dir, cell as Cell?)
		if not _neighbors[dir]
			_neighbors[dir] = cell
			if cell, cell.add(.opposite(dir), this)

	def addOtherSide(cell as Cell)
		.add(Dir.East, cell)
		cell.add(Dir.Northwest, _neighbors[Dir.North])
		cell.addBelow(_neighbors[Dir.Northeast])

	def addAbove(cell as Cell)
		.add(Dir.South, cell)
		cell.add(Dir.Northeast, _neighbors[Dir.East])

	def addBelow(cell as Cell?) as Cell
		if cell, cell.addAbove(this)
		return this

	def addBeside(cell as Cell?) as Cell
		if cell, cell.addOtherSide(this)
		return this

	def match(direction as Dir, cell as Cell) as int
		if .value  cell.value
			return 0
		else if _neighbors[direction]
			return 1 + _neighbors[direction].match(direction, this)
		else
			return 1

	def match(dir as Dir, target as int) as bool
		test
			cell1 = Cell('1', "X")
			cell2 = Cell('2', "X").addBeside(cell1)
			cell3 = Cell('3', "X").addBeside(cell2)
			cell4 = Cell('4', "X").addBelow(cell1)
			cell5 = Cell('5', "X").addBeside(cell4)
			cell6 = Cell('6', "X").addBeside(cell5)
			cell7 = Cell('7', "X").addBelow(cell4)
			cell8 = Cell('8', "X").addBeside(cell7)
			cell9 = Cell('9', "X").addBeside(cell8)

			assert cell1.match(Dir.Southeast, 3)
			assert cell7.match(Dir.Southwest, 3)
			assert cell7.match(Dir.Northeast, 3)
			assert cell3.match(Dir.North, 3)
			assert not cell6.match(Dir.Northeast, 3)
			assert cell9.match(Dir.West, 3)

		body
			opp = .opposite(dir)
			num = 1
			if _neighbors[dir], num += _neighbors[dir].match(dir, this)
			if _neighbors[opp], num += _neighbors[opp].match(opp, this)
			return target == num

	def matchAny(target as int) as bool
		return  .match(Dir.East, target) or _
				.match(Dir.North, target) or _
				.match(Dir.Northeast, target) or _
				.match(Dir.Northwest, target)

	def toString as String is override
		if .value
			return ' [.value] '
		else
			return '([.address])'

	def rowToString(sb as StringBuilder, sep as String)
		sb.append(this)
		if _neighbors[Dir.East]
			sb.append(sep)
			_neighbors[Dir.East].rowToString(sb, sep)

	def allToString(sb as StringBuilder, colsep as String, rowsep as String)
		.rowToString(sb, colsep)
		if _neighbors[Dir.South]
			sb.append(Environment.newLine)
			sb.append(rowsep)
			sb.append(Environment.newLine)
			_neighbors[Dir.South].allToString(sb, colsep, rowsep)

class Board

	pro dim from var as int
	pro head from var as Cell?
	var _cells as IList
	var _rowsep as String

	cue init(dim as int)
		ensure
			dim >= 3
		body
			base.init
			_dim = dim
			_cells = List()
			_constructBoard
			_rowsep = "---"
			for i in 1 : dim
				_rowsep += "+---"

	def _constructBoard
		head = nil
		tail = nil
		for i in .dim
			for j in .dim
				cell = Cell((i*.dim + j + 1).toString)
				_cells.add(cell)
				if j == 0
					head = tail = cell.addBelow(head)
				else
					tail = cell.addBeside(tail)
		.head = _cells[0]

	def getCell(address as String?) as Cell?
		if not address, return nil
		for cell in _cells
			if cell.address == address
				_cells.remove(cell)
				return cell
		return nil

	def toString as String is override
		sb = StringBuilder()
		sb.append(Environment.newLine)
		.head.allToString(sb, "|",_rowsep)
		sb.append(Environment.newLine)
		return sb.toString
Advertisement

0 Responses to “Tic-Tac-Toe: Cobra”



  1. Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.