Class: XO::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/xo/engine.rb

Overview

A state machine that encapsulates the game logic for Tic-tac-toe. The operation of the engine is completely determined by the properties:

The engine can be in one of the 3 following states (represented by a symbol):

  • :init

  • :playing

  • :game_over

The engine begins in the :init state. And, the following methods are used to advance a single game of Tic-tac-toe (and transition the engine between its states) by obeying the standard rules of Tic-tac-toe:

The array of symbols after each method lists the states in which the method is allowed to be called.

Examples:

e = Engine.new
e.start(Grid::X).play(1, 1).play(2, 1).play(1, 2).play(2, 2).play(1, 3)

event = e.last_event
puts event[:name]             # => :game_over
puts event[:type]             # => :winner
puts event[:last_move][:turn] # => Grid::X

e.continue_playing(Grid::O).play(2, 2).play(1, 1).play(3, 3).play(1, 3).play(1, 2).play(3, 2).play(2, 1).play(2, 3).play(3, 1)

event = e.last_event
puts event[:name]             # => :game_over
puts event[:type]             # => :squashed
puts event[:last_move][:turn] # => Grid::O

Defined Under Namespace

Classes: IllegalStateError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeEngine

Creates a new XO::Engine with its state set to :init, turn set to :nobody, an empty grid and last_event set to { name: :new }.



60
61
62
63
64
65
66
# File 'lib/xo/engine.rb', line 60

def initialize
  @grid = Grid.new

  reset

  set_event(:new)
end

Instance Attribute Details

#last_eventHash (readonly)

Returns:

  • (Hash)


56
57
58
# File 'lib/xo/engine.rb', line 56

def last_event
  @last_event
end

#state:init, ... (readonly)

Returns:

  • (:init, :playing, :game_over)


50
51
52
# File 'lib/xo/engine.rb', line 50

def state
  @state
end

#turnGrid::X, ... (readonly)

Returns:



53
54
55
# File 'lib/xo/engine.rb', line 53

def turn
  @turn
end

Instance Method Details

#continue_playing(turn) ⇒ self

Similar to start but should only be used to play another round when a game has ended. It transitions the engine from the :game_over state into the :playing state.

Sets the last event to be:

{ name: :game_started, type: :continue_playing }

Parameters:

Returns:

  • (self)

Raises:



177
178
179
180
181
182
183
184
185
186
# File 'lib/xo/engine.rb', line 177

def continue_playing(turn)
  check_turn(turn)

  case state
  when :game_over
    handle_continue_playing(turn)
  else
    raise IllegalStateError, "must be in the :game_over state but state = :#{state}"
  end
end

#gridGrid

Get the grid that’s managed by the engine.

Returns:

  • (Grid)

    a copy of the grid that the engine uses



71
72
73
# File 'lib/xo/engine.rb', line 71

def grid
  @grid.dup
end

#next_turnGrid::X, ...

If the current turn is either Grid::X, Grid::O or :nobody then it returns Grid::O, Grid::X, :nobody respectively.

Returns:



79
80
81
# File 'lib/xo/engine.rb', line 79

def next_turn
  Grid.other_token(turn)
end

#play(r, c) ⇒ self

Makes a move at the given position (r, c) which may transition the engine into the :game_over state or leave it in the :playing state.

Sets the last event as follows:

  • If the position is out of bounds, then

    { name: :invalid_move, type: :out_of_bounds }
    
  • If the position is occupied, then

    { name: :invalid_move, type: :occupied }
    
  • If the move was allowed and didn’t result in ending the game, then

    { name: :next_turn, last_move: { turn: :a_token, r: :a_row, c: :a_column } }
    
  • If the move was allowed and resulted in a win, then

    { name: :game_over, type: :winner, last_move: { turn: :a_token, r: :a_row, c: :a_column }, details: :the_details }
    
  • If the move was allowed and resulted in a squashed game, then

    { name: :game_over, type: :squashed, last_move: { turn: :a_token, r: :a_row, c: :a_column } }
    

Legend:

  • :a_token is one of Grid::X or Grid::O

  • :a_row is one of 1, 2 or 3

  • :a_column is one of 1, 2 or 3

  • :the_details is taken verbatim from the :details key of the returned hash of XO::Evaluator#analyze

Parameters:

  • r (Integer)

    the row

  • c (Integer)

    the column

Returns:

  • (self)

Raises:



157
158
159
160
161
162
163
164
# File 'lib/xo/engine.rb', line 157

def play(r, c)
  case state
  when :playing
    handle_play(r, c)
  else
    raise IllegalStateError, "must be in the :playing state but state = :#{state}"
  end
end

#start(turn) ⇒ self

Transitions the engine from the :init state into the :playing state.

Sets the last event to be:

{ name: :game_started }

Parameters:

Returns:

  • (self)

Raises:



93
94
95
96
97
98
99
100
101
102
# File 'lib/xo/engine.rb', line 93

def start(turn)
  check_turn(turn)

  case state
  when :init
    handle_start(turn)
  else
    raise IllegalStateError, "must be in the :init state but state = :#{state}"
  end
end

#stopself

Transitions the engine from the :playing or :game_over state into the :game_over state.

Sets the last event to be:

{ name: :game_over }

Returns:

  • (self)

Raises:



112
113
114
115
116
117
118
119
# File 'lib/xo/engine.rb', line 112

def stop
  case state
  when :playing, :game_over
    handle_stop
  else
    raise IllegalStateError, "must be in the :playing or :game_over state but state = :#{state}"
  end
end