jbcden_ttt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e190a50dcf6cc00e3708c46d1791dc622fef6a9b
4
+ data.tar.gz: a08f60ed3d99e4c6ff5d83b27647aa27aa0b5e72
5
+ SHA512:
6
+ metadata.gz: 7c56cf50cb4451678ee89256caa65089662b524c5477e3d9017f121706fdebc948ab5f4e7657e833433ec0c027dfb1dfb172704aba7e2aa059cef14eb441e59f
7
+ data.tar.gz: dda78fbd248ed911730650a91dd0df111dfefeef76621f090a09a9d6ba3a8c526cf292fb459f0facde66e8334d797c46b57e28dda9c010d884da5a42ed3adaff
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jbcden_ttt.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jacob Chae
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # JbcdenTtt
2
+
3
+ ## An unbeatable game of tic-tac-toe!
4
+
5
+ ## Installation
6
+
7
+ - Simply run:
8
+ ```bash
9
+ gem install jbcden_ttt
10
+ ```
11
+
12
+ - And then play!:
13
+ ```bash
14
+ ttt
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ Please note that this gem does require Ruby 1.9 or later.
20
+
21
+ Also note that this gem is based on the repo:
22
+
23
+ https://github.com/jbcden/tic-tac-toe
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/jbcden/jbcden_ttt/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ $:.unshift(File.expand_path('./lib', __FILE__))
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "jbcden_ttt"
8
+ t.test_files = FileList['test/*_test.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task default: :test
data/bin/ttt ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'jbcden_ttt'
4
+
5
+ JbcdenTtt::TicTacToe.start
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jbcden_ttt/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jbcden_ttt"
8
+ spec.version = JbcdenTtt::VERSION
9
+ spec.date = %q{2014-11-18}
10
+ spec.authors = ["Jacob Chae"]
11
+ spec.email = ["jbcden@gmail.com"]
12
+ spec.summary = %q{A basic command line based game of tic tac toe.}
13
+ spec.homepage = "https://github.com/jbcden/tic-tac-toe"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = ["ttt"]
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ end
data/lib/jbcden_ttt.rb ADDED
@@ -0,0 +1,148 @@
1
+ require "jbcden_ttt/version"
2
+ require 'jbcden_ttt/board'
3
+ require 'jbcden_ttt/board_mapper'
4
+ require 'jbcden_ttt/computer'
5
+ require 'jbcden_ttt/display_board'
6
+ require 'jbcden_ttt/game_state'
7
+ require 'jbcden_ttt/player'
8
+ require 'jbcden_ttt/tile'
9
+
10
+ module JbcdenTtt
11
+ class TicTacToe
12
+ attr_reader :board, :turn_num, :players, :computer, :player, :game
13
+
14
+ def self.start
15
+ new.play
16
+ end
17
+
18
+ def initialize
19
+ @board = Board.new(3,3)
20
+ @turn_num = 0
21
+ @players = []
22
+ end
23
+
24
+ def play
25
+ trap('INT') do
26
+ puts 'exiting!'
27
+ exit!
28
+ end
29
+ print_intro
30
+ choose_human
31
+ set_players_order
32
+ initialize_game_state
33
+ game_loop
34
+ end
35
+
36
+
37
+ def print_intro
38
+ clear_screen
39
+
40
+ print "Welcome to my Tic Tac Toe game!"
41
+ end
42
+
43
+ def choose_human
44
+ error = ""
45
+ while 1
46
+ begin
47
+ clear_screen
48
+ puts "Which player would you like to be? (\"x\" or \"o\"): "
49
+ puts error unless error.empty?
50
+ human_symbol = gets.chomp
51
+ @player = Player.new(human_symbol)
52
+ break
53
+ rescue Player::InvalidCharacterError => e
54
+ error = e.message
55
+ retry
56
+ end
57
+ end
58
+ @computer = Computer.new(choose_computer(human_symbol))
59
+ end
60
+
61
+ def set_players_order
62
+ if player.symbol == "x"
63
+ @players << @player
64
+ @players << @computer
65
+ else
66
+ @players << @computer
67
+ @players << @player
68
+ end
69
+ end
70
+
71
+ def initialize_game_state
72
+ @game = GameState.new(@board, @turn_num, @players.first)
73
+ end
74
+
75
+ def game_loop
76
+ while !@game.end_state?
77
+ clear_board
78
+
79
+ current_player = @players[@turn_num % 2]
80
+
81
+ take_turn(current_player)
82
+
83
+ @turn_num += 1
84
+ @game = GameState.new(@board, @turn_num, @players[@turn_num % 2])
85
+ end
86
+
87
+ clear_board
88
+
89
+ puts "The winner is: #{game.end_state?}"
90
+ end
91
+
92
+ private
93
+
94
+ def take_turn(current_player)
95
+ if current_player.human?
96
+ human_move(current_player)
97
+ else
98
+ move = current_player.calculate_best_move(@board, @game)
99
+ computer.make_move(@board, move)
100
+ end
101
+ end
102
+
103
+ def human_move(current_player)
104
+ puts
105
+ error = ""
106
+ while 1
107
+ begin
108
+ clear_board
109
+ print_message
110
+ puts error unless error.empty?
111
+
112
+ move = gets.chomp
113
+ current_player.make_move(@board, move)
114
+ break
115
+ rescue BoardMapper::InvalidCoordinateError => e
116
+ error = e.message
117
+ rescue Tile::InvalidActionError => e
118
+ error = e.message
119
+ end
120
+ end
121
+ end
122
+
123
+ def choose_computer(human)
124
+ if human == "x"
125
+ return "o"
126
+ else
127
+ return "x"
128
+ end
129
+ end
130
+
131
+ def clear_screen
132
+ puts "\033c"
133
+ end
134
+
135
+ def print_board
136
+ puts DisplayBoard.call(@board)
137
+ end
138
+
139
+ def clear_board
140
+ clear_screen
141
+ print_board
142
+ end
143
+
144
+ def print_message
145
+ puts "Please choose a square to mark."
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'tile'
2
+ require 'forwardable'
3
+
4
+ module JbcdenTtt
5
+ class Board
6
+ extend Forwardable
7
+ attr_reader :width, :height, :board
8
+ attr_writer :board
9
+ def initialize(width, height)
10
+ @width = width
11
+ @height = height
12
+ @board = Array.new(height)
13
+
14
+ init_board_array
15
+ init_board
16
+ end
17
+
18
+ def display
19
+ DisplayBoard.call(board)
20
+ end
21
+
22
+ def_delegators :@board, :[], :[]=, :each, :first, :size, :last
23
+ private
24
+
25
+ def init_board_array
26
+ board.each_with_index do |row, index|
27
+ board[index] = Array.new(width)
28
+ end
29
+ end
30
+
31
+ def init_board
32
+ board.each_with_index do |row, rownum|
33
+ row.each_with_index do |column, index|
34
+ set_rows(row, rownum, index)
35
+ end
36
+ end
37
+ end
38
+
39
+ def set_rows(row, rownum, index)
40
+ if rownum != board.size - 1
41
+ set_tile(row, rownum, index, '_')
42
+ else
43
+ set_tile(row, rownum, index, ' ')
44
+ end
45
+ end
46
+
47
+ def set_tile(row, rownum, index, tile_char)
48
+ row[index] = Tile.new(tile_char, rownum, index)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ module JbcdenTtt
2
+ class BoardMapper
3
+ class InvalidCoordinateError < StandardError
4
+ def initialize(msg="An invalid coordinate was passed in, please try again")
5
+ super
6
+ end
7
+ end
8
+
9
+ ROW_MAPPER = {
10
+ "a" => 0, "b" => 1, "c" => 2, "d" => 3, "e" => 4, "f" => 5, "g" => 6, "h" => 7,
11
+ "i" => 8, "j" => 9, "k" => 10, "l" => 11, "m" => 12, "n" => 13, "o" => 14, "p" => 15,
12
+ "q" => 16, "r" => 17, "s" => 18, "t" => 19, "u" => 20, "v" => 21, "w" => 22, "x" => 23,
13
+ "y" => 24, "z" => 25
14
+ }
15
+
16
+ def self.map_coordinate(x, y)
17
+ str = ""
18
+ str << ROW_MAPPER.key(x)
19
+ str << (y+1).to_s
20
+
21
+ str
22
+ end
23
+
24
+ def self.map_string(board, location)
25
+
26
+ row = extract_row(location)
27
+ col = extract_column(location)
28
+
29
+ if is_valid?(board, row, col)
30
+ board[ROW_MAPPER[row]][col]
31
+ else
32
+ raise InvalidCoordinateError
33
+ end
34
+ end
35
+
36
+ private
37
+ def self.extract_column(location)
38
+ column = location.scan(/\d+\Z/).first
39
+ raise InvalidCoordinateError if column.nil?
40
+ get_col(
41
+ Integer(column)
42
+ )
43
+ end
44
+
45
+ def self.extract_row(location)
46
+ row = location.scan(/\A[a-z]+/).first
47
+ raise InvalidCoordinateError if row.nil?
48
+ raise InvalidCoordinateError unless ROW_MAPPER.has_key?(row)
49
+ row
50
+ end
51
+
52
+ def self.get_col(col_num)
53
+ col_num - 1
54
+ end
55
+
56
+ def self.is_valid?(board, row, col)
57
+ (board.height) > ROW_MAPPER[row] && (board.width) > col
58
+ end
59
+
60
+ def self.row_size
61
+ @row_size ||= board.first.size
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,124 @@
1
+ module JbcdenTtt
2
+ class Computer
3
+ attr_reader :symbol
4
+
5
+ def initialize(symbol)
6
+ @symbol = symbol
7
+ end
8
+
9
+ def calculate_best_move(board, game)
10
+ unless new_board?(game.board)
11
+ mini_max(game, symbol, 0)
12
+ BoardMapper.map_coordinate(@move.xval, @move.yval)
13
+ else
14
+ tile = corners(board).sample
15
+ BoardMapper.map_coordinate(tile.xval, tile.yval)
16
+ end
17
+ end
18
+
19
+ def make_move(board, coordinate)
20
+ tile = BoardMapper.map_string(board, coordinate)
21
+ tile.mark(symbol)
22
+ end
23
+
24
+ def mini_max(game, current_player, depth)
25
+ return evaluate(game, depth) if game.end_state?
26
+ scores = []
27
+ moves = []
28
+ depth += 1
29
+
30
+ available_moves(game.board).each do |tile|
31
+ new_state = get_new_state(tile, game, current_player)
32
+
33
+ # I got this idea from the post mentioned in the README
34
+ scores << mini_max(new_state, next_player(current_player), depth)
35
+ moves << tile
36
+ end
37
+ return choose_move(current_player, scores, moves)
38
+ end
39
+
40
+ def available_moves(board)
41
+ unmarked_tiles(board)
42
+ end
43
+
44
+ def human?
45
+ false
46
+ end
47
+
48
+ private
49
+ def new_board?(board)
50
+ unmarked_tiles(board).size == (board.width*board.height)
51
+ end
52
+
53
+ def corners(board)
54
+ corners = []
55
+
56
+ corners << board.first[0]
57
+ corners << board.first.last
58
+
59
+ corners << board.last.first
60
+ corners << board.last.last
61
+ end
62
+
63
+ def initial_game_state(board, turn_num, symbol)
64
+ GameState.new(board, turn_num, symbol)
65
+ end
66
+
67
+ def choose_move(current_player, scores, moves)
68
+ if current_player == symbol
69
+ max_index = scores.each_with_index.max[1]
70
+ @move = moves[max_index]
71
+ return scores[max_index]
72
+ else
73
+ min_index = scores.each_with_index.min[1]
74
+ @move = moves[min_index]
75
+ return scores[min_index]
76
+ end
77
+ end
78
+
79
+ def evaluate(game, depth)
80
+ if game.end_state? == symbol
81
+ return 10 - depth
82
+ elsif game.end_state? == opponent
83
+ return depth - 10
84
+ else
85
+ return 0
86
+ end
87
+ end
88
+
89
+ def get_new_state(tile, game, current_player)
90
+ # copy board class and deep copy the inner board array
91
+ # Marshal is necessary b/c board array does not contain
92
+ # Plain Old Ruby Objects
93
+ temp_board = game.board.dup
94
+ board_array = Marshal.load(Marshal.dump(game.board.board))
95
+ temp_board.board = board_array
96
+
97
+ temp_board[tile.xval][tile.yval].mark(current_player)
98
+
99
+ GameState.new(temp_board, game.turn_num+1, current_player)
100
+ end
101
+
102
+ def next_player(current_player)
103
+ if current_player == "x"
104
+ "o"
105
+ else
106
+ "x"
107
+ end
108
+ end
109
+
110
+ def opponent
111
+ next_player(symbol)
112
+ end
113
+
114
+ def unmarked_tiles(board)
115
+ unmarked = []
116
+ board.each do |row|
117
+ row.each do |tile|
118
+ unmarked << tile unless tile.marked?
119
+ end
120
+ end
121
+ unmarked
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,46 @@
1
+ module JbcdenTtt
2
+ class DisplayBoard
3
+
4
+ ROW_NAMES = [
5
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
6
+ "n", "o", "p", "q", "r", "t", "u", "v", "w", "x", "y", "z"
7
+ ]
8
+
9
+ def self.call(board)
10
+ str = ""
11
+ row_num = 0
12
+ str << print_column_numbers(board.first.size)
13
+
14
+ board.each do |row|
15
+ str << print_row(row, row_num)
16
+ row_num += 1
17
+ end
18
+ str
19
+ end
20
+
21
+ private
22
+ def self.print_column_numbers(row_size)
23
+ str = " "
24
+ col = 1
25
+ count = row_size
26
+
27
+ row_size.times do
28
+ str << col.to_s << " "
29
+ col += 1
30
+ end
31
+ str << "\n"
32
+ str
33
+ end
34
+
35
+ def self.print_row(row, row_num)
36
+ str = ""
37
+ str << ROW_NAMES[row_num] << " "
38
+ row.each_with_index do |column, index|
39
+ str << column.symbol
40
+ str << "|" unless index == row.size-1
41
+ end
42
+ str << "\n"
43
+ str
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,114 @@
1
+ module JbcdenTtt
2
+ class GameState
3
+ attr_reader :board, :turn_num, :current_player
4
+ def initialize(board, turn_num, current_player)
5
+ @board = board
6
+ @turn_num = turn_num
7
+ @player = current_player
8
+ end
9
+
10
+ def end_state?
11
+ if turn_num >= min_turns
12
+ row_win(board) || column_win(board) || diagonal_win(board) || full_board(board)
13
+ else
14
+ false
15
+ end
16
+ end
17
+
18
+ private
19
+ def full_board(board)
20
+ marked = []
21
+ board.each do |row|
22
+ row.each do |tile|
23
+ marked << tile if tile.marked?
24
+ end
25
+ end
26
+ if marked.size == (board.width*board.height)
27
+ "cat"
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ def winner(xcount, ocount, board, method)
34
+ win_num = board.public_method(method.to_sym).call
35
+ cat = (board.width*board.height)/2 + 1
36
+
37
+ if xcount == win_num
38
+ "x"
39
+ elsif ocount == win_num
40
+ "o"
41
+ elsif ocount == cat || xcount == cat
42
+ "cat"
43
+ else
44
+ false
45
+ end
46
+ end
47
+
48
+ def diagonal_iterator(board, col_num, iterator)
49
+ xcount = 0
50
+ ocount = 0
51
+
52
+ board.each do |row|
53
+ xcount += 1 if row[col_num].symbol == "x"
54
+ ocount += 1 if row[col_num].symbol == "o"
55
+ col_num += iterator
56
+ end
57
+ winner(xcount, ocount, board, "height")
58
+ end
59
+
60
+ def right_diagonal(board)
61
+ diagonal_iterator(board, board.width - 1, -1)
62
+ end
63
+
64
+ def left_diagonal(board)
65
+ diagonal_iterator(board, 0, 1)
66
+ end
67
+
68
+ def diagonal_win(board)
69
+ left_diagonal(board) ||
70
+ right_diagonal(board)
71
+ end
72
+
73
+ def column_win(board)
74
+ xcount = 0
75
+ ocount = 0
76
+ col_num = 0
77
+
78
+ board.width.times do
79
+ board.each do |row|
80
+ xcount += 1 if row[col_num].symbol == "x"
81
+ ocount += 1 if row[col_num].symbol == "o"
82
+ end
83
+ if w = winner(xcount, ocount, board, "height")
84
+ return w
85
+ end
86
+ xcount = 0
87
+ ocount = 0
88
+ col_num += 1
89
+ end
90
+ false
91
+ end
92
+
93
+ def row_win(board)
94
+ xcount = 0
95
+ ocount = 0
96
+ board.each do |row|
97
+ row.each do |col|
98
+ xcount += 1 if col.symbol == "x"
99
+ ocount += 1 if col.symbol == "o"
100
+ end
101
+ if w = winner(xcount, ocount, board, "width")
102
+ return w
103
+ end
104
+ xcount = 0
105
+ ocount = 0
106
+ end
107
+ false
108
+ end
109
+
110
+ def min_turns
111
+ 5
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,26 @@
1
+ module JbcdenTtt
2
+ class Player
3
+ class InvalidCharacterError < StandardError
4
+ def initialize(msg='Only "x" and "o" are valid character choices.')
5
+ super
6
+ end
7
+ end
8
+ attr_reader :symbol
9
+ def initialize(symbol)
10
+ if symbol == "x" || symbol == "o"
11
+ @symbol = symbol
12
+ else
13
+ raise InvalidCharacterError
14
+ end
15
+ end
16
+
17
+ def make_move(board, coordinate)
18
+ tile = BoardMapper.map_string(board, coordinate)
19
+ tile.mark(symbol)
20
+ end
21
+
22
+ def human?
23
+ true
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ module JbcdenTtt
2
+ class Tile
3
+ class InvalidActionError < StandardError
4
+ def initialize(msg="This tile has already been marked")
5
+ super
6
+ end
7
+ end
8
+ attr_reader :symbol, :xval, :yval
9
+ attr_writer :marked, :symbol
10
+
11
+ def initialize(symbol, xval, yval)
12
+ @symbol = symbol
13
+ @xval = xval
14
+ @yval = yval
15
+ end
16
+
17
+ def marked?
18
+ @marked ||= false
19
+ end
20
+
21
+ def mark(new_symbol)
22
+ if markable?
23
+ @symbol = new_symbol
24
+ @marked = true
25
+ else
26
+ raise InvalidActionError
27
+ end
28
+ end
29
+
30
+ private
31
+ def markable?
32
+ # we need to accept the ' ' because the symbol for the bottom left and right
33
+ # corners of the board is ' '. Must remember to look at this again later.
34
+ @symbol == '_' || @symbol == ' '
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module JbcdenTtt
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,38 @@
1
+ require_relative './test_helper'
2
+ require 'board'
3
+ require 'board_mapper'
4
+
5
+ module JbcdenTtt
6
+ class BoardMapperTest < MiniTest::Test
7
+ def setup
8
+ @board = Board.new(3,3)
9
+ end
10
+
11
+ def test_coordinate_strings_are_mapped_correctly
12
+ expected = @board[0][0]
13
+ actual = BoardMapper.map_string(@board, "a1")
14
+
15
+ assert_equal expected, actual
16
+ end
17
+
18
+ def test_it_throws_an_error_for_invalid_column
19
+ assert_raises(BoardMapper::InvalidCoordinateError) {
20
+ BoardMapper.map_string(@board, "a4")
21
+ }
22
+ end
23
+
24
+ def test_it_throws_an_error_for_invalid_row
25
+ assert_raises(BoardMapper::InvalidCoordinateError) {
26
+ BoardMapper.map_string(@board, "d3")
27
+ }
28
+ end
29
+
30
+ def test_coordinates_can_be_mapped_into_strings
31
+ actual = BoardMapper.map_coordinate(2,2)
32
+ actual2 = BoardMapper.map_coordinate(6, 7)
33
+
34
+ assert_equal "c3", actual
35
+ assert_equal "g8", actual2
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ require_relative './test_helper'
2
+ require 'board'
3
+
4
+ module JbcdenTtt
5
+ class BoardTest < MiniTest::Test
6
+ def test_board_takes_a_height_and_length
7
+ board = Board.new(3,3)
8
+ assert_equal board.height, 3
9
+ assert_equal board.width, 3
10
+ end
11
+
12
+ def test_internal_board_array_dimensions
13
+ actual = Board.new(4,5)
14
+ assert_equal 4, actual[0].size
15
+ assert_equal 5, actual.size
16
+ end
17
+
18
+ def test_board_is_set_correctly
19
+ actual = Board.new(5,6)
20
+
21
+ assert_equal '_', actual[0][0].symbol
22
+ assert_equal '_', actual[1][1].symbol
23
+ assert_equal '_', actual[2][2].symbol
24
+ assert_equal '_', actual[3][3].symbol
25
+ assert_equal '_', actual[4][4].symbol
26
+
27
+ assert_equal ' ', actual[5][0].symbol
28
+ assert_equal ' ', actual[5][2].symbol
29
+ assert_equal ' ', actual[5][4].symbol
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,89 @@
1
+ require_relative './test_helper'
2
+ require 'computer'
3
+ require 'board'
4
+ require 'game_state'
5
+
6
+ module JbcdenTtt
7
+ class ComputerTest < MiniTest::Test
8
+ def setup
9
+ @board = Board.new(3,3)
10
+ end
11
+
12
+ def test_can_get_list_of_available_moves
13
+ computer = Computer.new("x")
14
+
15
+ @board[0][1].mark("x")
16
+ @board[1][2].mark("x")
17
+ @board[2][1].mark("x")
18
+
19
+ available_moves = computer.available_moves(@board)
20
+
21
+ refute_includes available_moves, @board[0][1]
22
+ refute_includes available_moves, @board[1][2]
23
+ refute_includes available_moves, @board[2][1]
24
+
25
+ assert_includes available_moves, @board[0][0]
26
+ assert_includes available_moves, @board[0][2]
27
+ assert_includes available_moves, @board[1][0]
28
+ assert_includes available_moves, @board[1][1]
29
+ assert_includes available_moves, @board[2][0]
30
+ assert_includes available_moves, @board[2][2]
31
+ end
32
+
33
+ def test_select_winning_move
34
+ computer = Computer.new("x")
35
+
36
+ @board[0][0].mark("o")
37
+ @board[2][1].mark("o")
38
+ @board[2][2].mark("o")
39
+
40
+ @board[0][2].mark("x")
41
+ @board[1][0].mark("x")
42
+ @board[2][0].mark("x")
43
+
44
+ game = GameState.new(@board, 7, computer.symbol)
45
+
46
+ move = computer.calculate_best_move(@board, game)
47
+
48
+ assert_equal "b2", move
49
+ end
50
+
51
+ def test_select_winning_move_depth
52
+ computer = Computer.new("o")
53
+
54
+ @board[2][0].mark("x")
55
+ @board[2][1].mark("x")
56
+
57
+ @board[0][1].mark("o")
58
+ @board[1][2].mark("o")
59
+ @board[2][2].mark("o")
60
+
61
+ game = GameState.new(@board, 7, computer.symbol)
62
+
63
+ move = computer.calculate_best_move(@board, game)
64
+
65
+ assert_equal "a3", move
66
+ end
67
+
68
+ def test_computer_marks_best_move
69
+ computer = Computer.new("o")
70
+
71
+ @board[2][0].mark("x")
72
+ @board[2][1].mark("x")
73
+
74
+ @board[0][1].mark("o")
75
+ @board[1][2].mark("o")
76
+ @board[2][2].mark("o")
77
+
78
+ game = GameState.new(@board, 7, computer.symbol)
79
+
80
+ move = computer.calculate_best_move(@board, game)
81
+ computer.make_move(@board, move)
82
+
83
+ tile = BoardMapper.map_string(@board, move)
84
+
85
+ assert_equal true, @board[tile.xval][tile.yval].marked?
86
+ assert_equal "o", @board[tile.xval][tile.yval].symbol
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,19 @@
1
+ require_relative './test_helper'
2
+ require 'board'
3
+ require 'display_board'
4
+
5
+ module JbcdenTtt
6
+ class DisplayBoardTest < MiniTest::Test
7
+ def test_board_display
8
+ actual = Board.new(3,3).display
9
+ result = <<'BOARD'
10
+ 1 2 3
11
+ a _|_|_
12
+ b _|_|_
13
+ c | |
14
+ BOARD
15
+
16
+ assert_equal result, actual
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,61 @@
1
+ require_relative './test_helper'
2
+ require 'game_state'
3
+
4
+ module JbcdenTtt
5
+ class GameStateTest < MiniTest::Test
6
+ def setup
7
+ @board = Board.new(3,3)
8
+ end
9
+
10
+ def test_will_always_return_false_if_min_turns_is_not_met
11
+ @board[1][0].mark("x")
12
+ @board[1][1].mark("x")
13
+ @board[1][2].mark("x")
14
+ game1 = GameState.new(@board, 1, "x")
15
+ game2 = GameState.new(@board, 2, "x")
16
+ game3 = GameState.new(@board, 3, "x")
17
+ game4 = GameState.new(@board, 4, "x")
18
+
19
+ assert_equal false, game1.end_state?
20
+ assert_equal false, game2.end_state?
21
+ assert_equal false, game3.end_state?
22
+ assert_equal false, game4.end_state?
23
+ end
24
+
25
+ def test_will_end_when_a_row_win_condition_is_met
26
+ @board[1][0].mark("x")
27
+ @board[1][1].mark("x")
28
+ @board[1][2].mark("x")
29
+ game = GameState.new(@board, 5, "x")
30
+
31
+ assert_equal "x", game.end_state?
32
+ end
33
+
34
+ def test_will_end_when_a_column_win_condition_is_met
35
+ @board[0][2].mark("x")
36
+ @board[1][2].mark("x")
37
+ @board[2][2].mark("x")
38
+ game = GameState.new(@board, 5, "x")
39
+
40
+ assert_equal "x", game.end_state?
41
+ end
42
+
43
+ def test_will_end_when_a_left_diagonal_win_condition_is_met
44
+ @board[0][0].mark("x")
45
+ @board[1][1].mark("x")
46
+ @board[2][2].mark("x")
47
+ game = GameState.new(@board, 5, "x")
48
+
49
+ assert_equal "x", game.end_state?
50
+ end
51
+
52
+ def test_will_end_when_a_right_diagonal_win_condition_is_met
53
+ @board[0][2].mark("x")
54
+ @board[1][1].mark("x")
55
+ @board[2][0].mark("x")
56
+ game = GameState.new(@board, 5, "x")
57
+
58
+ assert_equal "x", game.end_state?
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,22 @@
1
+ require 'player'
2
+
3
+ module JbcdenTtt
4
+ class PlayerTest < MiniTest::Test
5
+ def test_a_player_has_a_symbol
6
+ player = Player.new("x")
7
+
8
+ assert_equal "x", player.symbol
9
+ end
10
+
11
+ def test_a_player_has_a_turn_and_can_pick_a_move
12
+ board = Board.new(3,3)
13
+ player = Player.new("x")
14
+
15
+ assert_equal false, board[0][1].marked?
16
+
17
+ player.make_move(board, "a2")
18
+ assert_equal true, board[0][1].marked?
19
+ assert_equal player.symbol, board[0][1].symbol
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ $:.unshift File.expand_path('../../lib/jbcden_ttt', __FILE__)
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/pride'
data/test/tile_test.rb ADDED
@@ -0,0 +1,52 @@
1
+ require_relative './test_helper'
2
+ require 'tile'
3
+
4
+ module JbcdenTtt
5
+ class TileTest < MiniTest::Test
6
+ def test_it_has_a_character
7
+ actual = Tile.new('|', 0, 0)
8
+ assert_equal '|', actual.symbol
9
+ end
10
+
11
+ def test_it_initially_is_marked
12
+ actual = Tile.new('|', 0, 0)
13
+ assert_equal false, actual.marked?
14
+ end
15
+
16
+ def test_can_mark_a_tile
17
+ tile = Tile.new('_', 0, 0)
18
+ tile.mark("x")
19
+
20
+ assert_equal "x", tile.symbol
21
+ refute_equal '_', tile.symbol
22
+ end
23
+
24
+ def test_marking_a_tile_marks_it
25
+ tile = Tile.new('_', 0, 0)
26
+ assert_equal false, tile.marked?
27
+
28
+ tile.mark("x")
29
+ assert_equal true, tile.marked?
30
+ end
31
+
32
+ def test_cannot_mark_pipe_tiles
33
+ tile = Tile.new('|', 0, 0)
34
+
35
+ assert_raises(Tile::InvalidActionError) {
36
+ tile.mark("x")
37
+ }
38
+ end
39
+
40
+ def test_tile_knows_its_xcoordinate
41
+ tile = Tile.new('_', 5, 0)
42
+
43
+ assert_equal 5, tile.xval
44
+ end
45
+
46
+ def test_tile_knows_its_ycoordinate
47
+ tile = Tile.new('_', 5, 0)
48
+
49
+ assert_equal 0, tile.yval
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jbcden_ttt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Chae
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - jbcden@gmail.com
44
+ executables:
45
+ - ttt
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - bin/ttt
55
+ - jbcden_ttt.gemspec
56
+ - lib/jbcden_ttt.rb
57
+ - lib/jbcden_ttt/board.rb
58
+ - lib/jbcden_ttt/board_mapper.rb
59
+ - lib/jbcden_ttt/computer.rb
60
+ - lib/jbcden_ttt/display_board.rb
61
+ - lib/jbcden_ttt/game_state.rb
62
+ - lib/jbcden_ttt/player.rb
63
+ - lib/jbcden_ttt/tile.rb
64
+ - lib/jbcden_ttt/version.rb
65
+ - test/board_mapper_test.rb
66
+ - test/board_test.rb
67
+ - test/computer_test.rb
68
+ - test/display_board_test.rb
69
+ - test/game_state_test.rb
70
+ - test/player_test.rb
71
+ - test/test_helper.rb
72
+ - test/tile_test.rb
73
+ homepage: https://github.com/jbcden/tic-tac-toe
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: A basic command line based game of tic tac toe.
97
+ test_files:
98
+ - test/board_mapper_test.rb
99
+ - test/board_test.rb
100
+ - test/computer_test.rb
101
+ - test/display_board_test.rb
102
+ - test/game_state_test.rb
103
+ - test/player_test.rb
104
+ - test/test_helper.rb
105
+ - test/tile_test.rb