ttt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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