erics_tic_tac_toe 0.1.0 → 0.5.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.
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  pkg/
2
+ coverage/
data/README.markdown CHANGED
@@ -4,12 +4,11 @@ Tic Tac Toe
4
4
  How to play
5
5
  ----
6
6
 
7
- irb -Ilib
8
- require 'game'
9
- Game.new.run
7
+ gem install erics_tic_tac_toe
8
+ tic_tac_toe
10
9
 
11
- Whats coming
12
- ----
10
+ TODO
11
+ ---
13
12
 
14
- Gem and Bin executable for playing games easily
15
- Play games on boards bigger than 3x3
13
+ * Use Curses for a nicer playing experience
14
+ * Colorize diffs so that its easier to see what move was just played
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rake'
2
+ include Rake::DSL
2
3
  require 'bundler'
3
4
  require 'rake/testtask'
4
5
 
@@ -6,7 +7,7 @@ Bundler::GemHelper.install_tasks
6
7
 
7
8
  Rake::TestTask.new do |t|
8
9
  t.libs << 'test'
9
- t.pattern = 'test/**/*_test.rb'
10
+ t.pattern = '**/*_test.rb'
10
11
  end
11
12
 
12
13
  task :default => :test
data/bin/tic_tac_toe CHANGED
@@ -6,7 +6,7 @@ begin
6
6
  rescue LoadError
7
7
  case $!.to_s
8
8
  when /tic_tac_toe/
9
- if !$:.include?(libdir)
9
+ if !$:.include?(libdir)
10
10
  warn "warn: #$!. trying again with #{libdir} on load path"
11
11
  $:.unshift(libdir)
12
12
  retry
@@ -15,4 +15,43 @@ rescue LoadError
15
15
  raise
16
16
  end
17
17
 
18
- TicTacToe::Game.new.run
18
+ def setup
19
+ game = TicTacToe::Game.new
20
+ terminal = TicTacToe::TerminalGame.new(game.board)
21
+
22
+ if terminal.computer_goes_first?
23
+ @player_1 = TicTacToe::ComputerPlayer.new(letter: 'o')
24
+ @player_2 = TicTacToe::HumanPlayer.new(letter: 'x')
25
+ else
26
+ @player_1 = TicTacToe::HumanPlayer.new(letter: 'o')
27
+ @player_2 = TicTacToe::HumanPlayer.new(letter: 'x')
28
+ end
29
+
30
+ game = TicTacToe::Game.new(nil, @player_1, @player_2)
31
+
32
+ [game, terminal]
33
+ end
34
+
35
+ game, terminal = setup
36
+
37
+ loop do
38
+ game.start
39
+ terminal = TicTacToe::TerminalGame.new(game.board)
40
+
41
+ if game.solved? || game.cats?
42
+ terminal.update_board
43
+ break unless terminal.play_again?
44
+ game, terminal = setup
45
+ end
46
+
47
+ terminal.update_board
48
+
49
+ move = terminal.get_move_from_user
50
+ @player_1 = TicTacToe::HumanPlayer.new(letter: @player_1.letter, move: move)
51
+ @player_2 = TicTacToe::ComputerPlayer.new(letter: @player_2.letter)
52
+
53
+ grid = game.grid
54
+ game = TicTacToe::Game.new(grid, @player_1, @player_2)
55
+
56
+ end
57
+
data/lib/tic_tac_toe.rb CHANGED
@@ -7,7 +7,8 @@ module TicTacToe
7
7
  end
8
8
 
9
9
  # Internal Project Requires
10
- require 'tic-tac-toe/board'
11
- require 'tic-tac-toe/solver'
12
- require 'tic-tac-toe/game'
13
-
10
+ require 'tic_tac_toe/board'
11
+ require 'tic_tac_toe/game'
12
+ require 'tic_tac_toe/player'
13
+ require 'tic_tac_toe/presentors/game_presenter'
14
+ require 'tic_tac_toe/presentors/player_presenter'
@@ -0,0 +1,153 @@
1
+ module TicTacToe
2
+ # The main class for managing the state of the game board
3
+ # The board data is represented at a two dimensional array
4
+ # It provides helper methods for access the data
5
+ class Board
6
+
7
+ attr_accessor :grid
8
+
9
+ def initialize(size=3)
10
+ #[[ nil, nil, nil],
11
+ # [ nil, nil, nil],
12
+ # [ nil, nil, nil]]
13
+ @grid = Array.new(size) { Array.new(size) { nil } }
14
+ end
15
+
16
+ def get_cell(row, column)
17
+ @grid[column][row]
18
+ end
19
+
20
+ # Returns the corners of the empty cells
21
+ def empty_positions(&block)
22
+ positions = []
23
+ each_position do |row, column|
24
+ next if get_cell(row, column)
25
+ yield(row, column) if block_given?
26
+ positions << [row, column]
27
+ end
28
+ positions
29
+ end
30
+
31
+ # Plays a letter at a position, unless that position has already been taken
32
+ def play_at(row, column, letter)
33
+ @grid[column][row] ||= letter
34
+ self
35
+ end
36
+
37
+ # Returns true if the grid is empty
38
+ def empty?
39
+ each_cell do |cell|
40
+ return false if cell
41
+ end
42
+ true
43
+ end
44
+
45
+ # Returns true if the grid only has one element
46
+ def only_one?
47
+ counter = 0
48
+ each_cell do |cell|
49
+ counter += 1 if cell
50
+ return false if counter >= 2
51
+ end
52
+ counter == 1
53
+ end
54
+
55
+ # Returns true if every cell is set to a value
56
+ def full?
57
+ each_cell do |cell|
58
+ return false unless cell
59
+ end
60
+ true
61
+ end
62
+
63
+ # Returns true if the board has a wining pattern
64
+ def solved?
65
+ letter = won_across?
66
+ return letter if letter
67
+ letter = won_up_and_down?
68
+ return letter if letter
69
+ letter = won_diagonally?
70
+ return letter if letter
71
+ false
72
+ end
73
+
74
+ def winner
75
+ solved?
76
+ end
77
+
78
+ def to_s
79
+ output = ["-----------\n"]
80
+ @grid.each_with_index do |row, i|
81
+ row.each_with_index do |cell, j|
82
+ output << "#{cell || ((i*@grid.size)+j+1)} | #{"\n" if j==@grid.size-1}"
83
+ end
84
+ end
85
+ output << ["-----------\n\n"]
86
+ output.join
87
+ end
88
+
89
+ # Preform a deep clone of the board
90
+ def clone
91
+ board = Board.new
92
+ board.grid = @grid.map { |row| row.map { |cell| cell } }
93
+ board
94
+ end
95
+
96
+ def size
97
+ @grid.size
98
+ end
99
+
100
+ private
101
+
102
+ def each_cell(&block)
103
+ @grid.each do |row|
104
+ row.each do |cell|
105
+ yield(cell)
106
+ end
107
+ end
108
+ end
109
+
110
+ def each_position(&block)
111
+ @grid.each_with_index do |row, y|
112
+ row.each_with_index do |cell, x|
113
+ yield(y, x)
114
+ end
115
+ end
116
+ end
117
+
118
+ def won_across?(grid = @grid)
119
+ winning_letter = false
120
+ grid.each do |row|
121
+ winning_letter = row[0] if winning_group?(row)
122
+ end
123
+ winning_letter
124
+ end
125
+
126
+ def won_up_and_down?
127
+ won_across?(@grid.transpose)
128
+ end
129
+
130
+ def won_diagonally?
131
+ right_limit = @grid.size-1
132
+ base = (0..right_limit)
133
+
134
+ letter = create_and_check_group(base) { |i| @grid[i][i] }
135
+ return letter if letter
136
+ letter = create_and_check_group(base) { |i| @grid[i][right_limit-i] }
137
+ return letter if letter
138
+
139
+ false
140
+ end
141
+
142
+ def create_and_check_group(base, &block)
143
+ group = base.collect { |i| yield(i) }
144
+ return group[0] if winning_group?(group)
145
+ false
146
+ end
147
+
148
+ def winning_group?(group)
149
+ !group.any? { |cell| cell.nil? } && group.uniq.size == 1
150
+ end
151
+
152
+ end
153
+ end
@@ -0,0 +1,70 @@
1
+ #This is a computer that will play a perfect game of tic-tac-toe
2
+ #Author: Eric Koslow
3
+
4
+ require_relative 'game_types/terminal_game'
5
+ require_relative 'strategies/three_by_three_strategy'
6
+ require_relative 'strategies/minimax_strategy'
7
+ require_relative 'players/human_player'
8
+ require_relative 'players/computer_player'
9
+
10
+ module TicTacToe
11
+ # The main director of the program
12
+ # Directs its gametype when to retrieve information from the user
13
+ class Game
14
+ attr_reader :board
15
+
16
+ attr_reader :player_moves
17
+
18
+ def initialize(board=nil, player_1=nil, player_2=nil)
19
+ @board = Board.new
20
+ @board.grid = board || [[nil,nil,nil],[nil,nil,nil],[nil,nil,nil]]
21
+ @player_1 = player_1
22
+ @player_2 = player_2
23
+ @current_player = @player_1 && @player_1.has_next_move? ? @player_1 : @player_2
24
+ end
25
+
26
+ def start
27
+ while @current_player && (move = @current_player.get_move(@board))
28
+ move = number_to_cords(move) unless move.is_a?(Array)
29
+
30
+ @board.play_at(*move, @current_player.letter)
31
+ break if over?
32
+
33
+ switch_player
34
+ end
35
+ end
36
+
37
+ def grid
38
+ @board.grid
39
+ end
40
+
41
+ def solved?
42
+ @board.solved?
43
+ end
44
+
45
+ def cats?
46
+ @board.full? && !solved?
47
+ end
48
+
49
+ def winner
50
+ @board.winner
51
+ end
52
+
53
+ private
54
+
55
+ def over?
56
+ solved? || cats?
57
+ end
58
+
59
+ def number_to_cords(num)
60
+ num = num.to_i
61
+ num -= 1
62
+ [num % @board.size, num/@board.size]
63
+ end
64
+
65
+ def switch_player
66
+ @current_player = @current_player == @player_1 ? @player_2 : @player_1
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,86 @@
1
+ module TicTacToe
2
+ # Terminal GameType
3
+ # Interacts with the user through the terminal
4
+ # Uses puts for output, and gets for input
5
+ class TerminalGame
6
+ # Internal Error used when a user tries pulling an illegal move
7
+ class IllegalMove < RuntimeError
8
+ end
9
+
10
+ def initialize(board, io=Kernel)
11
+ @board = board
12
+ @io = io
13
+ end
14
+
15
+ def computer_goes_first?
16
+ input = get_input("Would you like to play first or second? (f/s)")
17
+
18
+ return true unless input =~ /^(f|first)$/i
19
+ false
20
+ end
21
+
22
+ def get_move_from_user
23
+ cords = get_cords_from_user
24
+
25
+ raise IllegalMove.new("That cell is already taken") unless empty_cell?(cords)
26
+
27
+ cords
28
+ rescue IllegalMove => error
29
+ display_text "Illegal Move: #{error.message}. Please try again"
30
+ retry
31
+ end
32
+
33
+ def play_again?
34
+ input = get_input "Play again? (y/n)"
35
+ input =~ /^(y|yes)$/i
36
+ end
37
+
38
+ def select(choices)
39
+ loop do
40
+ output = "Select the solver:\n"
41
+ choices.each_with_index { |choice, i| output += "#{i+1}: #{choice}\n" }
42
+
43
+ input = get_input(output)
44
+
45
+ if input =~ /^\d+$/ and input.to_i <= choices.size
46
+ return choices[input.to_i-1]
47
+ end
48
+
49
+ display_text("Not a valid choice")
50
+ end
51
+ end
52
+
53
+ def update_board
54
+ @io.puts @board
55
+ end
56
+
57
+ def display_text(text)
58
+ @io.puts text
59
+ end
60
+
61
+ private
62
+
63
+ def get_input(text)
64
+ @io.print text + ' '
65
+ @io.gets.chomp
66
+ end
67
+
68
+ def empty_cell?(cords)
69
+ !@board.get_cell(*cords)
70
+ end
71
+
72
+ def get_cords_from_user
73
+ input = get_input("Your move (1-#{@board.size**2}):")
74
+
75
+ raise IllegalMove.new("That is not a real location") unless input =~ /^\d+$/
76
+
77
+ cord_from_num(input.to_i)
78
+ end
79
+
80
+ def cord_from_num(num)
81
+ num -= 1
82
+ [num % @board.size, num/@board.size]
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,13 @@
1
+ module TicTacToe
2
+ class Player
3
+ HUMAN = 'human'
4
+ COMPUTER = 'computer'
5
+
6
+ def self.build(params)
7
+ return HumanPlayer.new(params) if params['type'] == HUMAN
8
+
9
+ ComputerPlayer.new(params)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module TicTacToe
2
+
3
+ class ComputerPlayer
4
+ attr_reader :letter
5
+
6
+ def initialize(params, solver=MinimaxStrategy)
7
+ @letter = params['letter'] || params[:letter]
8
+ @solver = solver
9
+ end
10
+
11
+ def get_move(board)
12
+ @solver.new(board, @letter).solve
13
+ end
14
+
15
+ def has_next_move?
16
+ true
17
+ end
18
+
19
+ def type
20
+ Player::COMPUTER
21
+ end
22
+
23
+ end
24
+
25
+
26
+ end