ttt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ require 'ttt/ratings'
2
+
3
+ module TTT
4
+ class ComputerPlayer
5
+
6
+ attr_accessor :game
7
+
8
+ def initialize(game)
9
+ self.game = game
10
+ end
11
+
12
+ def player_number
13
+ game.turn
14
+ end
15
+
16
+ def take_turn
17
+ game.mark best_move
18
+ end
19
+
20
+ def best_move
21
+ return imperative_move if imperative_move? # allow to customize certain situations
22
+ move, rating, game = moves_by_rating.first # otherwise go by rating
23
+ move
24
+ end
25
+
26
+ def moves_by_rating
27
+ return to_enum(:moves_by_rating) unless block_given?
28
+ moves = []
29
+ game.available_moves.each do |move|
30
+ new_game = game.pristine_mark move
31
+ moves << [ move, rate(new_game), new_game ]
32
+ end
33
+ moves = moves.sort_by { |move, rating, new_game| -rating } # highest rating first
34
+ moves.each { |move, rating, new_game| yield move, rating, new_game }
35
+ end
36
+
37
+ def rate(game)
38
+ RATINGS[game.board][player_number]
39
+ end
40
+
41
+ # allows us to override ratings in cases where they make the robot look stupid
42
+ def imperative_move
43
+ # if we can win *this turn*, then take it because
44
+ # it rates winning next turn the same as winning in 3 turns
45
+ game.available_moves.each do |move|
46
+ new_game = game.pristine_mark move
47
+ return move if new_game.over? && new_game.winner == player_number
48
+ end
49
+
50
+ # if we can block the opponent from winning *this turn*, then take it, because
51
+ # it rates losing this turn the same as losing in 3 turns
52
+ if moves_by_rating.all? { |move, rating, game| rating == -1 }
53
+ Game.winning_states do |position1, position2, position3|
54
+ a, b, c = board[position1-1, 1].to_i, board[position2-1, 1].to_i, board[position3-1, 1].to_i
55
+ if a + b + c == opponent_number * 2
56
+ return a.zero? ? position1 : b.zero? ? position2 : position3
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def imperative_move?
63
+ !!imperative_move
64
+ end
65
+
66
+ def board
67
+ game.board
68
+ end
69
+
70
+ def opponent_number
71
+ player_number == 1 ? 2 : 1
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,129 @@
1
+ module TTT
2
+ class Game
3
+
4
+ DEFAULT_BOARD = '0'*9
5
+ attr_writer :board
6
+
7
+ def initialize(board=DEFAULT_BOARD)
8
+ self.board = board.dup
9
+ end
10
+
11
+ def turn
12
+ return if over?
13
+ board.scan('1').size - board.scan('2').size + 1
14
+ end
15
+
16
+ def mark(position)
17
+ board[position-1] = turn.to_s
18
+ end
19
+
20
+ def board(style=nil)
21
+ return @board unless style
22
+ " %s | %s | %s \n----|---|----\n %s | %s | %s \n----|---|----\n %s | %s | %s " % @board.gsub('0', ' ').split('')
23
+ end
24
+
25
+ def over?
26
+ winner || board.split(//).all? { |char| char == '1' || char == '2' }
27
+ end
28
+
29
+ def status(player_number)
30
+ return nil unless over?
31
+ (winner == player_number) ? :wins :
32
+ winner ? :loses :
33
+ :ties
34
+ end
35
+
36
+ def tie?
37
+ over? && !winner
38
+ end
39
+
40
+ def winner
41
+ return if winning_positions.empty?
42
+ self[winning_positions.first]
43
+ end
44
+
45
+ def available_moves
46
+ return [] if over?
47
+ to_return = []
48
+ board.split(//).each_with_index do |char, index|
49
+ to_return << index.next if char != '1' && char != '2'
50
+ end
51
+ to_return
52
+ end
53
+
54
+ def pristine_mark(position)
55
+ marked = self.class.new board.dup
56
+ marked.mark position
57
+ marked
58
+ end
59
+
60
+ def winning_states(&block)
61
+ self.class.winning_states(&block)
62
+ end
63
+
64
+ def winning_positions
65
+ winning_states do |pos1, pos2, pos3|
66
+ next unless board[pos1, 1] == board[pos2, 1]
67
+ next unless board[pos1, 1] == board[pos3, 1]
68
+ next unless board[pos1, 1] =~ /^(1|2)$/
69
+ return [pos1+1, pos2+1, pos3+1]
70
+ end
71
+ []
72
+ end
73
+
74
+ def [](position)
75
+ player = board[position-1, 1].to_i
76
+ return player if player == 1 || player == 2
77
+ end
78
+
79
+ end
80
+ end
81
+
82
+
83
+
84
+ module TTT
85
+ class << Game
86
+ def congruent?(board1, board2)
87
+ each_congruent(board2).any? { |congruent| board1 == congruent }
88
+ end
89
+
90
+ def each_congruent(board)
91
+ return to_enum(:each_congruent, board) unless block_given?
92
+ each_rotation(board) { |congruent| yield congruent }
93
+ each_rotation(reflect_board board) { |congruent| yield congruent }
94
+ end
95
+
96
+ def reflect_board(board)
97
+ board = board.dup
98
+ board[0..2], board[6..8] = board[6..8], board[0..2]
99
+ board
100
+ end
101
+
102
+ def each_rotation(board)
103
+ return to_enum(:each_rotation, board) unless block_given?
104
+ board = board.dup
105
+ 4.times do
106
+ yield board.dup
107
+ board = rotate_board(board)
108
+ end
109
+ end
110
+
111
+ def rotate_board(board)
112
+ board = board.dup
113
+ board[0], board[1], board[2], board[3], board[5], board[6], board[7], board[8] =
114
+ board[6], board[3], board[0], board[7], board[1], board[8], board[5], board[2]
115
+ board
116
+ end
117
+
118
+ def winning_states
119
+ yield 0, 1, 2
120
+ yield 3, 4, 5
121
+ yield 6, 7, 8
122
+ yield 0, 3, 6
123
+ yield 1, 4, 7
124
+ yield 2, 5, 8
125
+ yield 0, 4, 8
126
+ yield 2, 4, 6
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,22 @@
1
+ module TTT
2
+ module Interface
3
+ def self.registered
4
+ @registered_interfaces ||= Hash.new
5
+ end
6
+
7
+ def self.registered?(name)
8
+ registered.has_key? name
9
+ end
10
+
11
+ def self.registered_names
12
+ registered.keys
13
+ end
14
+
15
+ def self.register(name, interface)
16
+ registered[name] = interface
17
+ end
18
+ end
19
+ end
20
+
21
+ require 'ttt/interface/cli'
22
+ require 'ttt/interface/limelight'
@@ -0,0 +1,95 @@
1
+ require 'ttt/interface/cli/players'
2
+ require 'ttt/interface/cli/views'
3
+
4
+ module TTT
5
+ module Interface
6
+ class CLI
7
+ Interface.register 'cli', self
8
+
9
+ X = 'X'
10
+ O = 'O'
11
+
12
+ attr_accessor :game, :filein, :fileout, :fileerr, :player1, :player2, :turn
13
+
14
+ def initialize(options={})
15
+ self.filein = options.fetch :filein, $stdin
16
+ self.fileout = options.fetch :fileout, $stdout
17
+ self.fileerr = options.fetch :fileerr, $stderr
18
+ self.turn = 0
19
+ end
20
+
21
+ def play
22
+ fileout.puts "Welcome to Tic Tac Toe"
23
+ fileout.flush
24
+ create_game
25
+ create_player1
26
+ create_player2
27
+ until game.over?
28
+ display_board
29
+ take_current_turn
30
+ end
31
+ display_results
32
+ end
33
+
34
+ def display_results
35
+ display_board
36
+ if game.tie?
37
+ puts "The game ended in a tie."
38
+ else
39
+ puts "Player #{game.winner} won the game."
40
+ end
41
+ puts "Play again soon :)"
42
+ end
43
+
44
+ def take_current_turn
45
+ current_player.take_turn
46
+ self.turn += 1
47
+ end
48
+
49
+ def current_player
50
+ turn.even? ? player1 : player2
51
+ end
52
+
53
+ def create_game
54
+ self.game = Game.new
55
+ end
56
+
57
+ def create_player1
58
+ self.player1 = create_player X, 'first'
59
+ end
60
+
61
+ def create_player2
62
+ self.player2 = create_player O, 'second'
63
+ end
64
+
65
+ def create_player(letter, position)
66
+ type = prompt "#{letter} will go #{position}, would you like #{letter} to be a human or a computer? (h/c) ", :validate => /^[hc]$/i
67
+ if type =~ /c/i
68
+ ComputerPlayer.new game, self, letter
69
+ else
70
+ HumanPlayer.new game, self, letter
71
+ end
72
+ end
73
+
74
+ def prompt(message, validation={})
75
+ validation[:validate] ||= //
76
+ fileout.print message
77
+ input = filein.gets
78
+ until input =~ validation[:validate]
79
+ fileout.puts "Invalid, input."
80
+ fileout.print message
81
+ input = filein.gets
82
+ end
83
+ input
84
+ end
85
+
86
+ def display_board
87
+ Views.new(self).display_board
88
+ end
89
+
90
+ def board
91
+ game.board
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,56 @@
1
+ require 'ttt/computer_player'
2
+ require 'forwardable'
3
+
4
+ module TTT
5
+ module Interface
6
+ class CLI
7
+
8
+
9
+ class Player
10
+ attr_accessor :game, :cli, :marker
11
+
12
+ extend Forwardable
13
+ def_delegators :cli, :fileout, :filein, :fileerr, :prompt
14
+
15
+ def initialize(game, cli, marker)
16
+ self.game = game
17
+ self.cli = cli
18
+ self.marker = marker
19
+ end
20
+ end
21
+
22
+
23
+ class ComputerPlayer < Player
24
+ attr_accessor :computer
25
+ def initialize(*args)
26
+ super
27
+ self.computer = TTT::ComputerPlayer.new game
28
+ end
29
+ def take_turn
30
+ computer.take_turn
31
+ end
32
+ end
33
+
34
+
35
+ class HumanPlayer <Player
36
+ def take_turn
37
+ fileout.puts "The nine squares consecutively map to a number. "\
38
+ "Topleft starts at 1, topright continues with 3, and bottomright ends with 9."
39
+ move = prompt "Where would you like to move? (#{list_available_moves}) ", :validate => available_moves_regex
40
+ game.mark move.to_i
41
+ end
42
+
43
+ def list_available_moves
44
+ game.available_moves.join(', ')
45
+ end
46
+
47
+ def available_moves_regex
48
+ moves = game.available_moves
49
+ /^[#{moves.join}]$/
50
+ end
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,124 @@
1
+ require 'ttt/interface/cli/players'
2
+ require 'ttt/interface/cli/views'
3
+
4
+ module TTT
5
+ module Interface
6
+ class CLI
7
+ class Views
8
+
9
+ attr_accessor :cli
10
+
11
+ def initialize(cli)
12
+ self.cli = cli
13
+ end
14
+
15
+ def method_missing(meth, *args, &block)
16
+ super unless cli.respond_to? meth
17
+ cli.send meth, *args, &block
18
+ end
19
+
20
+ def display_board
21
+ fileout.print row(0) << horizontal_separator <<
22
+ row(1) << horizontal_separator <<
23
+ row(2) << "\n\n"
24
+ end
25
+
26
+ def row(n)
27
+ n *= 3
28
+ line(n) << line(n+1) << line(n+2)
29
+ end
30
+
31
+ def line(n)
32
+ offset, col = (n/3)*3, (n%3)
33
+ square(offset+1, col) << vertical_separator <<
34
+ square(offset+2, col) << vertical_separator <<
35
+ square(offset+3, col) << "\n"
36
+ end
37
+
38
+ def horizontal_separator
39
+ "-----|-----|-----\n"
40
+ end
41
+
42
+ def vertical_separator
43
+ "|"
44
+ end
45
+
46
+ def square(num, line)
47
+ send "line#{line}_for", num
48
+ end
49
+
50
+ def line0_for(square)
51
+ if forward_diagonal_winner?(square)
52
+ "\\ "
53
+ elsif backward_diagonal_winner?(square)
54
+ " /"
55
+ elsif vertical_winner? square
56
+ " | "
57
+ else
58
+ " "
59
+ end
60
+ end
61
+
62
+ def line1_for(square)
63
+ if horizontal_winner? square
64
+ "--%s--"
65
+ else
66
+ " %s "
67
+ end % char_for(square)
68
+ end
69
+
70
+ def line2_for(square)
71
+ if forward_diagonal_winner?(square)
72
+ " \\"
73
+ elsif backward_diagonal_winner?(square)
74
+ "/ "
75
+ elsif vertical_winner? square
76
+ " | "
77
+ else
78
+ " "
79
+ end
80
+ end
81
+
82
+ def winner?(square)
83
+ game.over? && !game.tie? && game.winning_positions.include?(square)
84
+ end
85
+
86
+ def forward_diagonal_winner?(square)
87
+ return false unless winner? square
88
+ [[1, 5], [5, 9], [9, 5]].any? do |s1, s2|
89
+ square == s1 && winner?(s2)
90
+ end
91
+ end
92
+
93
+ def backward_diagonal_winner?(square)
94
+ return false unless winner? square
95
+ [[3, 5], [5, 7], [7, 5]].any? do |s1, s2|
96
+ square == s1 && winner?(s2)
97
+ end
98
+ end
99
+
100
+ def vertical_winner?(square)
101
+ return false unless winner? square
102
+ winner? (square + 2) % 9 + 1
103
+ end
104
+
105
+ def horizontal_winner?(square)
106
+ return false unless winner? square
107
+ winner?(square+1) || winner?(square-1)
108
+ end
109
+
110
+ def char_for(position)
111
+ case game[position]
112
+ when nil
113
+ ' '
114
+ when 1
115
+ player1.marker
116
+ when 2
117
+ player2.marker
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+ end
124
+ end