InfraRuby Vision delivers solutions for mobile and web applications.
You can try InfraRuby live in your browser!
Try the InfraRuby statically typed Ruby compiler live in your browser. Try our examples or write your own code.
Follow @InfraRuby on Twitter for news and updates.

TicTacToe ✦ Examples

InfraRuby is a compiler and runtime for statically typed Ruby. You can use InfraRuby for your own projects with our free download!

coding style

The InfraRuby compiler supports many coding styles. In particular, you may use either spaces or tabs for indentation and you may omit the return keyword where applicable.

For more information:

This example implements an Android app for the game tic-tac-toe:

NOTE: This example includes Java integration syntax, which is not specific to InfraRuby.

Color

## <>
class Color
	BLACK = new
	WHITE = new

	## -> Color
	def next
		return WHITE if equal?(BLACK)
		return BLACK if equal?(WHITE)
		raise "can't happen"
	end
end

Board

## <>
##   @m: Color?[][]
class Board
	SIZE = 3.i32
	RANGE = 0.i32 ... SIZE

	## -> void
	def initialize
		@m = JAVA::ruby.Color[SIZE, SIZE].new
		return
	end

	## int32, int32 -> Color?
	def get(i, j)
		return @m[i][j]
	end

	## int32, int32, Color? -> void
	def set(i, j, color)
		@m[i][j] = color
		return
	end

	## -> void
	def clear
		SIZE.times do |i|
			SIZE.times do |j|
				set(i, j, nil)
			end
		end
		return
	end

	## int32, int32 -> boolean
	def taken?(i, j)
		return get(i, j).not_nil?
	end

	## -> boolean
	def full?
		return RANGE.all? { |i| RANGE.all? { |j| taken?(i, j) } }
	end
end

Line

## <>
##   @i0: int32
##   @j0: int32
##   @di: int32
##   @dj: int32
class Line
	SIZE = 3.i32
	RANGE = 0.i32 ... SIZE

	## int32, int32, int32, int32 -> void
	def initialize(i0, j0, di, dj)
		@i0 = i0
		@j0 = j0
		@di = di
		@dj = dj
		return
	end

	attr_reader :i0, :j0, :di, :dj

	## -> int32
	def i1
		return @i0 + @di * (SIZE - 1)
	end

	## -> int32
	def j1
		return @j0 + @dj * (SIZE - 1)
	end

	## Board, int32 -> Color?
	def get(board, k)
		return board.get(@i0 + @di * k, @j0 + @dj * k)
	end

	## Board, Color -> boolean
	def win?(board, color)
		return RANGE.all? { |k| get(board, k).equal?(color) }
	end
end

Rule

## <>
module Rule
	N = Board::SIZE - Line::SIZE + 1

	class << self
		## &{Line -> void} -> void
		def each_diagonal_line
			N.times do |i|
				N.times do |j|
					yield Line.new(i, j, 1.i32, 1.i32)
					yield Line.new(i, Board::SIZE - j - 1, 1.i32, -1.i32)
				end
			end
			return
		end

		## &{Line -> void} -> void
		def each_orthogonal_line
			N.times do |i|
				Board::SIZE.times do |j|
					yield Line.new(i, j, 1.i32, 0.i32)
					yield Line.new(j, i, 0.i32, 1.i32)
				end
			end
			return
		end

		## &{Line -> void} -> void
		def each_line(&block)
			each_diagonal_line(&block)
			each_orthogonal_line(&block)
			return
		end

		## -> Array<Line>
		def lines
			a = []
			each_line do |line|
				a.push(line)
			end
			return a
		end
	end

	LINES = lines

	class << self
		## Board, Color -> boolean
		def win?(board, color)
			return LINES.any? { |line| line.win?(board, color) }
		end

		## Board, Color, &{Line -> void} -> void
		def each_win_line(board, color)
			LINES.each do |line|
				yield line if line.win?(board, color)
			end
			return
		end
	end
end

Game

## <>
##   @board: Board
##   @color: Color
##   @state: Symbol
class Game
	## -> void
	def initialize
		@board = Board.new
		@color = Color::BLACK
		@state = :play
		return
	end

	attr_reader :board, :color, :state

	## -> void
	def clear
		@board.clear
		@color = Color::BLACK
		@state = :play
		return
	end

	## int32, int32 -> boolean
	def valid?(i, j)
		return Board::RANGE.member?(i) && Board::RANGE.member?(j)
	end

	## int32, int32 -> void
	def play(i, j)
		@board.set(i, j, @color)
		if Rule.win?(@board, @color)
			@state = :end
		elsif @board.full?
			@state = :end
		else
			@color = @color.next
		end
		return
	end

	## int32, int32 -> void
	def play_unless_taken(i, j)
		play(i, j) unless @board.taken?(i, j)
		return
	end

	## int32, int32 -> void
	def touch(i, j)
		case @state
		when :end
			clear
			return
		when :play
			play_unless_taken(i, j)
			return
		else
			raise "can't happen"
		end
	end
end

MainActivity

## <>
class MainActivity < JAVA::android.app.Activity
	## android.os.Bundle -> void
	def onCreate(savedInstanceState)
		super(savedInstanceState)
		setContentView(R::LAYOUT::MAIN)
		return
	end
end

Style

## <>
module Style
	j_import JAVA::android.graphics.Paint

	class << self
		## int32, int32 -> android.graphics.Paint
		def new_paint(color, width)
			paint = Paint.new
			paint.setColor(color)
			paint.setStyle(Paint::Style::STROKE)
			paint.setStrokeWidth(width)
			paint.setAntiAlias(true)
			return paint
		end
	end

	GRID_PAINT = new_paint(0xFF111111.i32, 3.i32)
	LINE_PAINT = new_paint(0xFFEEEEEE.i32, 3.i32)
	BLACK_PAINT = new_paint(0xFF0099CC.i32, 5.i32)
	WHITE_PAINT = new_paint(0xFF00CC99.i32, 5.i32)
end

View

## <>
##   @d: float32
##   @x: float32
##   @y: float32
##   @game: Game
class View < JAVA::android.view.View
	## android.content.Context, android.util.AttributeSet -> void
	def initialize(context, attrs)
		super(context, attrs)
		@d = 0.f32
		@x = 0.f32
		@y = 0.f32
		@game = Game.new
		return
	end

	MARGIN = 4.i32

	class << self
		## int32, int32 -> float32
		def compute_dn(w, h)
			sx = w - MARGIN * 2
			sy = h - MARGIN * 2
			return (sx < sy ? sx : sy).to_float32
		end
	end

	## int32, int32, int32, int32 -> void
	def onSizeChanged(w, h, oldw, oldh)
		super(w, h, oldw, oldh)
		update_geometry(w, h)
		return
	end

	## int32, int32 -> void
	def update_geometry(w, h)
		dn = View.compute_dn(w, h)
		@d = dn / 10 / Board::SIZE.to_float32
		@x = (w.to_float32 - dn) / 2
		@y = (h.to_float32 - dn) / 2
		return
	end

	## int32 -> float32
	def compute_dk(k)
		return @d * 10 * k.to_float32
	end

	## android.graphics.Canvas -> void
	def draw_grid(canvas)
		dn = compute_dk(Board::SIZE)
		(1.i32 ... Board::SIZE).each do |k|
			dk = compute_dk(k)
			canvas.drawLine(@x, @y + dk, @x + dn, @y + dk, Style::GRID_PAINT)
			canvas.drawLine(@x + dk, @y, @x + dk, @y + dn, Style::GRID_PAINT)
		end
		return
	end

	## android.graphics.Canvas, float32, float32 -> void
	def __draw_token_black(canvas, x, y)
		d5 = @d * 5
		canvas.drawCircle(x + d5, y + d5, @d * 7 / 2, Style::BLACK_PAINT)
		return
	end

	## android.graphics.Canvas, float32, float32 -> void
	def __draw_token_white(canvas, x, y)
		d2 = @d * 2
		d8 = @d * 8
		canvas.drawLine(x + d2, y + d2, x + d8, y + d8, Style::WHITE_PAINT)
		canvas.drawLine(x + d2, y + d8, x + d8, y + d2, Style::WHITE_PAINT)
		return
	end

	## android.graphics.Canvas, int32, int32 -> void
	def draw_token_black(canvas, i, j)
		x = @x + compute_dk(i)
		y = @y + compute_dk(j)
		__draw_token_black(canvas, x, y)
		return
	end

	## android.graphics.Canvas, int32, int32 -> void
	def draw_token_white(canvas, i, j)
		x = @x + compute_dk(i)
		y = @y + compute_dk(j)
		__draw_token_white(canvas, x, y)
		return
	end

	## android.graphics.Canvas, int32, int32, Color -> void
	def draw_token(canvas, i, j, color)
		case color
		when Color::BLACK
			draw_token_black(canvas, i, j)
			return
		when Color::WHITE
			draw_token_white(canvas, i, j)
			return
		else
			raise "can't happen"
		end
	end

	## android.graphics.Canvas, int32, int32, Color? -> void
	def draw_token_unless_nil(canvas, i, j, color)
		draw_token(canvas, i, j, color) unless color.nil?
		return
	end

	## android.graphics.Canvas, float32, float32, float32, float32 -> void
	def __draw_line(canvas, x0, y0, x1, y1)
		d5 = @d * 5
		canvas.drawLine(x0 + d5, y0 + d5, x1 + d5, y1 + d5, Style::LINE_PAINT)
		return
	end

	## android.graphics.Canvas, int32, int32, int32, int32 -> void
	def draw_line(canvas, i0, j0, i1, j1)
		x0 = @x + compute_dk(i0)
		y0 = @y + compute_dk(j0)
		x1 = @x + compute_dk(i1)
		y1 = @y + compute_dk(j1)
		__draw_line(canvas, x0, y0, x1, y1)
		return
	end

	j_import JAVA::android.view.MotionEvent

	## android.graphics.Canvas -> void
	def onDraw(canvas)
		super(canvas)
		draw_grid(canvas)
		draw_board(canvas)
		draw_win_lines(canvas)
		return
	end

	## android.view.MotionEvent -> boolean
	def onTouchEvent(event)
		action = event.getAction
		case action
		when MotionEvent::ACTION_DOWN
			return true
		when MotionEvent::ACTION_UP
			process_touch(event.getX, event.getY)
			return true
		else
			return false
		end
	end

	## android.graphics.Canvas -> void
	def draw_board(canvas)
		Board::SIZE.times do |i|
			Board::SIZE.times do |j|
				draw_token_unless_nil(canvas, i, j, @game.board.get(i, j))
			end
		end
		return
	end

	## android.graphics.Canvas -> void
	def draw_win_lines(canvas)
		Rule.each_win_line(@game.board, @game.color) do |line|
			draw_line(canvas, line.i0, line.j0, line.i1, line.j1)
		end
		return
	end

	## int32, int32 -> void
	def __process_touch(i, j)
		if @game.valid?(i, j)
			@game.touch(i, j)
			invalidate
		end
		return
	end

	## float32, float32 -> void
	def process_touch(x, y)
		d = @d * 10
		i = ((x - @x) / d).to_int32
		j = ((y - @y) / d).to_int32
		__process_touch(i, j)
		return
	end
end
Follow @InfraRuby on Twitter
Copyright © 2011-2017 InfraRuby Vision Limited