jbcden_ttt 0.0.1

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.
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