threesmodel 0.0.3
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 +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +28 -0
- data/features/basic_folding.feature +66 -0
- data/features/basic_summing.feature +82 -0
- data/features/game_over.feature +18 -0
- data/features/scoring.feature +112 -0
- data/features/start_game.feature +14 -0
- data/features/steps/basic_steps.rb +41 -0
- data/features/steps/game_over_steps.rb +44 -0
- data/features/steps/game_play_steps.rb +39 -0
- data/lib/candidate_extractor.rb +47 -0
- data/lib/candidate_translator.rb +26 -0
- data/lib/game_board_folder.rb +86 -0
- data/lib/game_creator.rb +30 -0
- data/lib/game_over_checker.rb +18 -0
- data/lib/line_folder.rb +93 -0
- data/lib/next_number_determinator.rb +21 -0
- data/lib/next_number_positioner.rb +10 -0
- data/lib/score_calculator.rb +27 -0
- data/lib/threesmodel/version.rb +3 -0
- data/lib/threesmodel.rb +113 -0
- data/spec/candidate_extraction_spec.rb +34 -0
- data/spec/candidate_translator_spec.rb +36 -0
- data/spec/custom_matchers.rb +27 -0
- data/spec/game_board_folder_spec.rb +50 -0
- data/spec/game_creator_spec.rb +36 -0
- data/spec/line_folder_spec.rb +87 -0
- data/spec/next_number_determination_spec.rb +38 -0
- data/spec/next_number_positioner_spec.rb +28 -0
- data/spec/spec_helper.rb +17 -0
- data/threesmodel.gemspec +23 -0
- metadata +125 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'line_folder'
|
2
|
+
require "Matrix"
|
3
|
+
# A game board keeps and manipulates the state of the game.
|
4
|
+
class GameBoardFolder
|
5
|
+
|
6
|
+
def initialize(line_folder = LineFolder.new )
|
7
|
+
@line_folder = line_folder
|
8
|
+
end
|
9
|
+
|
10
|
+
# Folds the game board from right to left.
|
11
|
+
def fold_left(state)
|
12
|
+
new_state = []
|
13
|
+
state.row_vectors.each_index {|i|
|
14
|
+
values = state.row_vectors[i].to_a
|
15
|
+
values = @line_folder.fold(values)
|
16
|
+
new_state << values
|
17
|
+
}
|
18
|
+
Matrix.rows(new_state)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Folds the game board from left to right.
|
22
|
+
def fold_right(state)
|
23
|
+
new_state = []
|
24
|
+
state.row_vectors.each {|row|
|
25
|
+
values = row.to_a.reverse
|
26
|
+
values = @line_folder.fold(values)
|
27
|
+
new_state << values.reverse
|
28
|
+
}
|
29
|
+
Matrix.rows(new_state)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Folds the game board upwards.
|
33
|
+
def fold_up(state)
|
34
|
+
new_state = []
|
35
|
+
state.column_vectors.each {|column|
|
36
|
+
values = column.to_a
|
37
|
+
values = @line_folder.fold(values)
|
38
|
+
new_state << values
|
39
|
+
}
|
40
|
+
Matrix.columns(new_state)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Folds the game board downwards.
|
44
|
+
def fold_down(state)
|
45
|
+
new_state = []
|
46
|
+
state.column_vectors.each {|column|
|
47
|
+
values = column.to_a.reverse
|
48
|
+
values = @line_folder.fold(values)
|
49
|
+
new_state << values.reverse
|
50
|
+
}
|
51
|
+
Matrix.columns(new_state)
|
52
|
+
end
|
53
|
+
|
54
|
+
def can_fold_left?(state)
|
55
|
+
foldable = false
|
56
|
+
state.row_vectors.each{|line|
|
57
|
+
foldable = (foldable or @line_folder.can_fold?(line.to_a))
|
58
|
+
}
|
59
|
+
foldable
|
60
|
+
end
|
61
|
+
|
62
|
+
def can_fold_right?(state)
|
63
|
+
foldable = false
|
64
|
+
state.row_vectors.each{|line|
|
65
|
+
foldable = (foldable or @line_folder.can_fold?(line.to_a.reverse))
|
66
|
+
}
|
67
|
+
foldable
|
68
|
+
end
|
69
|
+
|
70
|
+
def can_fold_up?(state)
|
71
|
+
foldable = false
|
72
|
+
state.column_vectors.each{|line|
|
73
|
+
foldable = (foldable or @line_folder.can_fold?(line.to_a))
|
74
|
+
}
|
75
|
+
foldable
|
76
|
+
end
|
77
|
+
|
78
|
+
def can_fold_down?(state)
|
79
|
+
foldable = false
|
80
|
+
state.column_vectors.each{|line|
|
81
|
+
foldable = (foldable or @line_folder.can_fold?(line.to_a.reverse))
|
82
|
+
}
|
83
|
+
foldable
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/lib/game_creator.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'Matrix'
|
2
|
+
|
3
|
+
class GameCreator
|
4
|
+
def self.create_new_game
|
5
|
+
positions = random_positions(9)
|
6
|
+
create_matrix(positions)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.create_matrix(positions)
|
10
|
+
rows = []
|
11
|
+
4.times {
|
12
|
+
rows << [0,0,0,0]
|
13
|
+
}
|
14
|
+
positions.each {|position|
|
15
|
+
rows[position[0]][position[1]] = rand(1..3)
|
16
|
+
}
|
17
|
+
Matrix.rows(rows)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.random_positions(number_of_positions)
|
21
|
+
positions = []
|
22
|
+
while (positions.length < number_of_positions)
|
23
|
+
candidate_position = [rand(0..3), rand(0..3)]
|
24
|
+
unless positions.include?(candidate_position)
|
25
|
+
positions << candidate_position
|
26
|
+
end
|
27
|
+
end
|
28
|
+
positions
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'line_folder'
|
2
|
+
class GameOverChecker
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@line_folder = LineFolder.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def game_over?(state)
|
9
|
+
state.row_vectors.each {|row|
|
10
|
+
if ((@line_folder.can_fold?(row.to_a)) or (@line_folder.can_fold?(row.to_a.reverse))) then return false end
|
11
|
+
}
|
12
|
+
state.column_vectors.each{|column|
|
13
|
+
if ((@line_folder.can_fold?(column.to_a)) or (@line_folder.can_fold?(column.to_a.reverse))) then return false end
|
14
|
+
}
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/line_folder.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# The LineFolder can fold an array with four cells according to the rules of
|
2
|
+
# threes.
|
3
|
+
class LineFolder
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
Array.class_eval do
|
7
|
+
# Determines the "wall" position and the collapses the first to collapsible cells inserting a zero at the end. Assumes a foldable line.
|
8
|
+
def squash
|
9
|
+
array = Array.new(self)
|
10
|
+
wall_index = wall_index_of
|
11
|
+
array[wall_index] = array[wall_index] + array[wall_index + 1]
|
12
|
+
while wall_index < 2
|
13
|
+
array[wall_index + 1] = array[wall_index + 2]
|
14
|
+
wall_index += 1
|
15
|
+
end
|
16
|
+
array[3] = 0
|
17
|
+
return array
|
18
|
+
end
|
19
|
+
|
20
|
+
def wall_index_of
|
21
|
+
return 0 if self[0] == 0
|
22
|
+
return 0 if self[0] == 1 and self[1] == 2
|
23
|
+
return 0 if self[0] == 2 and self[1] == 1
|
24
|
+
if self[0] > 2
|
25
|
+
return 0 if self[0] == self[1]
|
26
|
+
end
|
27
|
+
return 1 if self[1] == 0
|
28
|
+
return 1 if (self[0] != self[1]) and self[1] == 1 and self[2] == 2
|
29
|
+
return 1 if (self[0] != self[1]) and self[1] == 2 and self[2] == 1
|
30
|
+
return 1 if (self[0] == self[1]) and self[1] == 1 and self[2] == 2
|
31
|
+
return 1 if (self[0] == self[1]) and self[1] == 2 and self[2] == 1
|
32
|
+
if self[1] > 2
|
33
|
+
return 1 if self[1] == self[2]
|
34
|
+
end
|
35
|
+
return 2 if self[2] == 0
|
36
|
+
return 2 if self[2] == 1 and self[3] == 2
|
37
|
+
return 2 if self[2] == 2 and self[3] == 1
|
38
|
+
if self[2] > 2
|
39
|
+
return 2 if self[2] == self[3]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def fold(line)
|
47
|
+
if can_fold?(line) then
|
48
|
+
if(line == [0,0,0,0]) then return line end
|
49
|
+
return line.squash
|
50
|
+
else
|
51
|
+
return line
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def can_fold?(line)
|
56
|
+
foldable = has_adjacent_one_and_two?(line)
|
57
|
+
foldable = (foldable or has_adjacent_equals?(line))
|
58
|
+
foldable = (foldable or has_non_trailing_zeros?(line))
|
59
|
+
return foldable
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_non_trailing_zeros?(line)
|
63
|
+
first_non_zero_from_end = index_of_first_non_trailing_zero(line)
|
64
|
+
if (first_non_zero_from_end == nil) then return true end
|
65
|
+
if (line.index(0) == nil) then return false end
|
66
|
+
return (line.index(0) < first_non_zero_from_end)
|
67
|
+
end
|
68
|
+
|
69
|
+
def index_of_first_non_trailing_zero(line)
|
70
|
+
first_non_zero_from_end = line.rindex {|element| element != 0}
|
71
|
+
end
|
72
|
+
|
73
|
+
def has_adjacent_one_and_two?(line)
|
74
|
+
pairs = line.each_cons(2).to_a
|
75
|
+
index = pairs.each_index {|i|
|
76
|
+
if (pairs[i].sort == [1, 2])
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
}
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
def has_adjacent_equals?(line)
|
84
|
+
pairs = line.each_cons(2).to_a
|
85
|
+
index = pairs.each {|pair|
|
86
|
+
if(pair[0] == pair[1] and (pair[0] > 2))
|
87
|
+
return true
|
88
|
+
end
|
89
|
+
}
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class NextNumberDeterminator
|
2
|
+
def initialize
|
3
|
+
@score_table = [0,1,2,3,6,12,24,48,96,192,384,768,1536,3072,6144]
|
4
|
+
end
|
5
|
+
|
6
|
+
def highest_number(game_board)
|
7
|
+
highest = 0
|
8
|
+
game_board.row_vectors.each {|row|
|
9
|
+
row.to_a.each {|value|
|
10
|
+
if (value > highest) then
|
11
|
+
highest = value
|
12
|
+
end
|
13
|
+
}
|
14
|
+
}
|
15
|
+
return highest
|
16
|
+
end
|
17
|
+
|
18
|
+
def select_number(game_board)
|
19
|
+
@score_table[rand(1..([3, @score_table.index(highest_number(game_board))-2].max))]
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# This class is responsible for selecting the number to add to a certain game
|
2
|
+
# board state and to select the cell where the number should go
|
3
|
+
class NextNumberPositioner
|
4
|
+
|
5
|
+
# Selects one of the candidate positions to add the next number
|
6
|
+
def select_position(candidates)
|
7
|
+
return candidates[rand(0..candidates.size - 1)]
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# This class is responsible for calculating the score for a given game board.
|
2
|
+
class ScoreCalculator
|
3
|
+
|
4
|
+
@@score_table = {0 => 0, 1 => 0, 2 => 0,
|
5
|
+
3 => 3, 6 => 9,
|
6
|
+
12 => 27,
|
7
|
+
24 => 81,
|
8
|
+
48 => 243,
|
9
|
+
96 => 729,
|
10
|
+
192 => 2187,
|
11
|
+
384 => 6561,
|
12
|
+
768 => 19683,
|
13
|
+
1536 => 59049,
|
14
|
+
3072 => 177147,
|
15
|
+
6144 => 531441}
|
16
|
+
|
17
|
+
def self.score_for(game_board)
|
18
|
+
score = 0
|
19
|
+
game_board.row_vectors.each {|vector|
|
20
|
+
vector.to_a.each {|val|
|
21
|
+
score = score + @@score_table[val]
|
22
|
+
}
|
23
|
+
}
|
24
|
+
score
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/threesmodel.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require "threesmodel/version"
|
2
|
+
require 'candidate_translator'
|
3
|
+
require 'candidate_extractor'
|
4
|
+
require 'game_board_folder'
|
5
|
+
require 'next_number_positioner'
|
6
|
+
require 'next_number_determinator'
|
7
|
+
require 'game_over_checker'
|
8
|
+
require 'game_creator'
|
9
|
+
require 'securerandom'
|
10
|
+
require 'matrix'
|
11
|
+
require 'score_calculator'
|
12
|
+
|
13
|
+
module Threesmodel
|
14
|
+
class InternalAPI
|
15
|
+
def initialize
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
class GameController
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@games = {}
|
23
|
+
@next_number = {}
|
24
|
+
@candidate_extractor = CandidateExtractor.new
|
25
|
+
@candidate_translator = CandidateTranslator.new
|
26
|
+
@game_over_checker = GameOverChecker.new
|
27
|
+
@next_number_determinator = NextNumberDeterminator.new
|
28
|
+
@number_positioner = NextNumberPositioner.new
|
29
|
+
@game_board_folder = GameBoardFolder.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_new_game
|
33
|
+
id = SecureRandom.uuid
|
34
|
+
@games[id] = GameCreator.create_new_game
|
35
|
+
@next_number[id] = getNextNumber(@games[id])
|
36
|
+
respond_to_player(id, @next_number[id])
|
37
|
+
end
|
38
|
+
|
39
|
+
def fold_right(id)
|
40
|
+
game = @games[id]
|
41
|
+
unless (@game_board_folder.can_fold_right?(game))
|
42
|
+
return did_not_fold(game, id)
|
43
|
+
end
|
44
|
+
candidates = @candidate_translator.translate_right_fold(@candidate_extractor.fold_right_candidates(game))
|
45
|
+
game = @game_board_folder.fold_right(game)
|
46
|
+
prepare_for_next_fold(candidates, game, id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def fold_left(id)
|
50
|
+
game = @games[id]
|
51
|
+
unless (@game_board_folder.can_fold_left?(game))
|
52
|
+
return did_not_fold(game, id)
|
53
|
+
end
|
54
|
+
candidates = @candidate_translator.translate_left_fold(@candidate_extractor.fold_left_candidates(game))
|
55
|
+
game = @game_board_folder.fold_left(game)
|
56
|
+
prepare_for_next_fold(candidates, game, id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def fold_down(id)
|
60
|
+
game = @games[id]
|
61
|
+
unless (@game_board_folder.can_fold_down?(game))
|
62
|
+
return did_not_fold(game, id)
|
63
|
+
end
|
64
|
+
candidates = @candidate_translator.translate_down_fold(@candidate_extractor.fold_down_candidates(game))
|
65
|
+
game = @game_board_folder.fold_down(game)
|
66
|
+
prepare_for_next_fold(candidates, game, id)
|
67
|
+
end
|
68
|
+
|
69
|
+
def fold_up(id)
|
70
|
+
game = @games[id]
|
71
|
+
unless (@game_board_folder.can_fold_up?(game))
|
72
|
+
return did_not_fold(game, id)
|
73
|
+
end
|
74
|
+
candidates = @candidate_translator.translate_up_fold(@candidate_extractor.fold_up_candidates(game))
|
75
|
+
game = @game_board_folder.fold_up(game)
|
76
|
+
prepare_for_next_fold(candidates, game, id)
|
77
|
+
end
|
78
|
+
|
79
|
+
def did_not_fold(game, id)
|
80
|
+
respond_to_player(id, @next_number[id])
|
81
|
+
end
|
82
|
+
|
83
|
+
def prepare_for_next_fold(candidates, game, id)
|
84
|
+
game = add_next_number(id, candidates, game)
|
85
|
+
@next_number[id] = getNextNumber(game)
|
86
|
+
@games[id] = game
|
87
|
+
respond_to_player(id, @next_number[id])
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_next_number(id, candidates, game)
|
91
|
+
coordinates = @number_positioner.select_position(candidates)
|
92
|
+
new_state = []
|
93
|
+
game.row_vectors.each_index {|i|
|
94
|
+
values = game.row_vectors[i].to_a
|
95
|
+
if (coordinates[0] == i)
|
96
|
+
values[coordinates[1]] = @next_number[id]
|
97
|
+
end
|
98
|
+
new_state << values
|
99
|
+
}
|
100
|
+
Matrix.rows(new_state)
|
101
|
+
end
|
102
|
+
|
103
|
+
def getNextNumber(game)
|
104
|
+
@next_number_determinator.select_number(game)
|
105
|
+
end
|
106
|
+
|
107
|
+
def respond_to_player(id, next_number)
|
108
|
+
{:id => id, :game => @games[id], :next_number => next_number, :game_over => @game_over_checker.game_over?(@games[id]), :score => ScoreCalculator.score_for(@games[id]) }
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative "../lib/candidate_extractor"
|
2
|
+
require "Matrix"
|
3
|
+
|
4
|
+
describe CandidateExtractor do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@candidate_extractor = CandidateExtractor.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "extracts no candidates form an unfoldable board" do
|
11
|
+
game = Matrix.rows([[3, 1, 1, 3],
|
12
|
+
[6, 1, 1, 6],
|
13
|
+
[12, 1, 3, 12],
|
14
|
+
[24, 1, 6, 24]])
|
15
|
+
expect(@candidate_extractor.fold_left_candidates(game)).to eq([])
|
16
|
+
expect(@candidate_extractor.fold_right_candidates(game)).to eq([])
|
17
|
+
expect(@candidate_extractor.fold_up_candidates(game)).to eq([])
|
18
|
+
expect(@candidate_extractor.fold_down_candidates(game)).to eq([])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "extracts a single candidate form a board with one foldable line" do
|
22
|
+
expect(@candidate_extractor.fold_left_candidates(
|
23
|
+
Matrix.rows([[3,1,1,3],[6,1,1,6],[12,1,2,12],[24,1,6,24]]))).to eq([[2,3]])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "extracts all four candidates form a board where all lines fold" do
|
27
|
+
expect(@candidate_extractor.fold_left_candidates(
|
28
|
+
Matrix.rows([[3,3,1,3],
|
29
|
+
[6,3,3,6],
|
30
|
+
[12,1,12,12],
|
31
|
+
[3,3,3,3]]))).to eq([[0,3], [1,3], [2,3], [3,3]])
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../lib/candidate_translator'
|
2
|
+
describe CandidateTranslator do
|
3
|
+
|
4
|
+
it "translates left fold candidates with same candidates" do
|
5
|
+
translator = CandidateTranslator.new
|
6
|
+
expect(translator.translate_left_fold([[0,3]])).to eq [[0,3]]
|
7
|
+
expect(translator.translate_left_fold([[0,3], [1,3]])).to eq [[0,3], [1,3]]
|
8
|
+
expect(translator.translate_left_fold([[0,3], [1,3], [2,3]])).to eq [[0,3], [1,3], [2,3]]
|
9
|
+
expect(translator.translate_left_fold([[0,3], [1,3], [2,3], [3,3]])).to eq [[0,3], [1,3], [2,3], [3,3]]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "translates right fold candidates with mirrored candidates" do
|
13
|
+
translator = CandidateTranslator.new
|
14
|
+
expect(translator.translate_right_fold([[0,3]])).to eq [[3,0]]
|
15
|
+
expect(translator.translate_right_fold([[1,3]])).to eq [[2,0]]
|
16
|
+
expect(translator.translate_right_fold([[2,3]])).to eq [[1,0]]
|
17
|
+
expect(translator.translate_right_fold([[3,3]])).to eq [[0,0]]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "translates down fold candidates" do
|
21
|
+
translator = CandidateTranslator.new
|
22
|
+
expect(translator.translate_down_fold([[0,3]])).to eq [[0,0]]
|
23
|
+
expect(translator.translate_down_fold([[1,3]])).to eq [[0,1]]
|
24
|
+
expect(translator.translate_down_fold([[2,3]])).to eq [[0,2]]
|
25
|
+
expect(translator.translate_down_fold([[3,3]])).to eq [[0,3]]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "translates up fold candidates" do
|
29
|
+
translator = CandidateTranslator.new
|
30
|
+
expect(translator.translate_up_fold([[0,3]])).to eq [[3,3]]
|
31
|
+
expect(translator.translate_up_fold([[1,3]])).to eq [[3,2]]
|
32
|
+
expect(translator.translate_up_fold([[2,3]])).to eq [[3,1]]
|
33
|
+
expect(translator.translate_up_fold([[3,3]])).to eq [[3,0]]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CustomMatchers
|
2
|
+
class BeOneOf
|
3
|
+
def initialize(expected)
|
4
|
+
@expected = expected
|
5
|
+
end
|
6
|
+
|
7
|
+
def matches?(target)
|
8
|
+
@target = target
|
9
|
+
if(@target.respond_to? :keys) then
|
10
|
+
@expected.include?(@target.keys[0])
|
11
|
+
else
|
12
|
+
@expected.include?(@target)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message
|
17
|
+
"expected #{@target} to be one of the elements of #{@expected}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message_when_negated
|
21
|
+
"expected #{@target} not to be one of the elements of #{@expected}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def be_one_of(expected)
|
25
|
+
BeOneOf.new(expected)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative "../lib/game_board_folder"
|
2
|
+
require "Matrix"
|
3
|
+
|
4
|
+
describe GameBoardFolder do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@game_board_folder = GameBoardFolder.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "can create game board" do
|
11
|
+
expect(@game_board_folder).to_not eq(nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can fold all rows to the left" do
|
15
|
+
expect(@game_board_folder.fold_left(
|
16
|
+
Matrix.rows([[0,1,2,0],
|
17
|
+
[0,1,2,0],
|
18
|
+
[0,1,2,0],
|
19
|
+
[0,1,2,0]])).row(0).to_a).to eq([1,2,0,0])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can fold all rows to the right" do
|
23
|
+
expect(@game_board_folder.fold_right(
|
24
|
+
Matrix.rows([[0,1,2,0],
|
25
|
+
[0,1,2,0],
|
26
|
+
[0,1,2,0],
|
27
|
+
[0,1,2,0]])).row(0).to_a).to eq([0,0,1,2])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can fold all rows up" do
|
31
|
+
state = @game_board_folder.fold_up(
|
32
|
+
Matrix.rows([[0,0,0,0],
|
33
|
+
[1,1,1,1],
|
34
|
+
[2,2,2,2],
|
35
|
+
[0,0,0,0]]))
|
36
|
+
expect(state.row(0).to_a).to eq([1,1,1,1])
|
37
|
+
expect(state.row(1).to_a).to eq([2,2,2,2])
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can fold all rows down" do
|
41
|
+
state = @game_board_folder.fold_down(
|
42
|
+
Matrix.rows([[0,0,0,0],
|
43
|
+
[1,1,1,1],
|
44
|
+
[2,2,2,2],
|
45
|
+
[0,0,0,0]]))
|
46
|
+
expect(state.row(2).to_a).to eq([1,1,1,1])
|
47
|
+
expect(state.row(3).to_a).to eq([2,2,2,2])
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative '../lib/game_creator'
|
3
|
+
require 'custom_matchers'
|
4
|
+
|
5
|
+
describe 'Create new Game' do
|
6
|
+
|
7
|
+
include CustomMatchers
|
8
|
+
|
9
|
+
it 'should randomly position nine cells' do
|
10
|
+
game_state = GameCreator.create_new_game()
|
11
|
+
expect(cells_in_game(game_state)).to eq(9)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should determine nine coordinates' do
|
15
|
+
expect(GameCreator.random_positions(9).size).to eq(9)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should only use 1s 2s and 3s when initiating game board' do
|
19
|
+
game_state = GameCreator.create_new_game()
|
20
|
+
game_state.row_vectors.each{|row|
|
21
|
+
row.to_a.each{|value|
|
22
|
+
expect(value).to be_one_of([0,1,2,3])
|
23
|
+
}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def cells_in_game(game_state)
|
29
|
+
cells = 0
|
30
|
+
game_state.row_vectors.each{|row|
|
31
|
+
row.to_a.each{|val|
|
32
|
+
if val > 0 then cells = cells + 1 end
|
33
|
+
}
|
34
|
+
}
|
35
|
+
cells
|
36
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative "../lib/line_folder"
|
2
|
+
|
3
|
+
describe LineFolder do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@line_folder = LineFolder.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "can find wall in position zero" do
|
10
|
+
expect([0,0,1,2].wall_index_of).to eq(0)
|
11
|
+
expect([1,2,0,0].wall_index_of).to eq(0)
|
12
|
+
expect([2,1,0,0].wall_index_of).to eq(0)
|
13
|
+
expect([0,1,0,0].wall_index_of).to eq(0)
|
14
|
+
expect([3,3,0,0].wall_index_of).to eq(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can find wall in position one" do
|
18
|
+
expect([1,1,2,2].wall_index_of).to eq(1)
|
19
|
+
expect([2,2,1,2].wall_index_of).to eq(1)
|
20
|
+
expect([3,2,1,2].wall_index_of).to eq(1)
|
21
|
+
expect([2,3,3,2].wall_index_of).to eq(1)
|
22
|
+
expect([1,0,2,2].wall_index_of).to eq(1)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can find wall in position two" do
|
26
|
+
expect([1,1,1,2].wall_index_of).to eq(2)
|
27
|
+
expect([2,2,2,1].wall_index_of).to eq(2)
|
28
|
+
expect([2,2,3,3].wall_index_of).to eq(2)
|
29
|
+
expect([2,2,0,3].wall_index_of).to eq(2)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can fold when line has leading zeros" do
|
33
|
+
expect(@line_folder.can_fold?([0,0,1,2])).to eq(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can not fold when line is all ones" do
|
37
|
+
expect(@line_folder.can_fold?([1,1,1,1])).to eq(false)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can fold when line has a one and a two adjacent" do
|
41
|
+
expect(@line_folder.can_fold?([1,2,1,1])).to eq(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "can fold when line has a two and a one adjacent" do
|
45
|
+
expect(@line_folder.can_fold?([2,1,1,1])).to eq(true)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can fold over two equal numbers larger than 2" do
|
49
|
+
expect(@line_folder.can_fold?([3,3,6,12])).to eq(true)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "folds undetectably when all zero line" do
|
53
|
+
expect(@line_folder.fold([0,0,0,0])).to eq([0,0,0,0])
|
54
|
+
end
|
55
|
+
it "folds by shifting onto leading zeros" do
|
56
|
+
expect(@line_folder.fold([0,0,1,2])).to eq([0,1,2,0])
|
57
|
+
end
|
58
|
+
|
59
|
+
it "folds by shifting onto zeros" do
|
60
|
+
expect(@line_folder.fold([1,0,1,2])).to eq([1,1,2,0])
|
61
|
+
end
|
62
|
+
|
63
|
+
it "folds by adding one and two at a wall" do
|
64
|
+
expect(@line_folder.fold([1,2,0,0])).to eq([3,0,0,0])
|
65
|
+
end
|
66
|
+
|
67
|
+
it "folds by adding one and two at a non foldable cell" do
|
68
|
+
expect(@line_folder.fold([3,2,1,0])).to eq([3,3,0,0])
|
69
|
+
end
|
70
|
+
|
71
|
+
it "folds by adding equal numbers at a wall" do
|
72
|
+
expect(@line_folder.fold([3,3,0,0])).to eq([6,0,0,0])
|
73
|
+
end
|
74
|
+
|
75
|
+
it "folds by adding equal numbers at a non foldable cell" do
|
76
|
+
expect(@line_folder.fold([12,6,6,3])).to eq([12,12,3,0])
|
77
|
+
end
|
78
|
+
|
79
|
+
it "only folds first pair of equal numbers " do
|
80
|
+
expect(@line_folder.fold([12,12,3,3])).to eq([24,3,3,0])
|
81
|
+
end
|
82
|
+
|
83
|
+
it "prioritizes leftwards folds" do
|
84
|
+
expect(@line_folder.fold([1,2,0,3])).to eq([3,0,3,0])
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|