threesmodel 0.0.3 → 0.0.6
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 +4 -4
- data/.gitignore +4 -0
- data/README.md +48 -7
- data/features/basic_folding.feature +32 -10
- data/features/basic_summing.feature +13 -13
- data/features/scoring.feature +14 -14
- data/features/steps/basic_steps.rb +21 -5
- data/features/steps/game_over_steps.rb +1 -2
- data/features/steps/game_play_steps.rb +7 -5
- data/lib/solvers/highest_paying_next_move_player.rb +19 -0
- data/lib/solvers/i_fear_down_folds_player.rb +20 -0
- data/lib/solvers/sample_automated_player.rb +16 -0
- data/lib/solvers/up_right_left_down_order_player.rb +20 -0
- data/lib/threesmodel.rb +65 -8
- data/lib/{candidate_extractor.rb → threesmodel/candidate_extractor.rb} +1 -1
- data/lib/{candidate_translator.rb → threesmodel/candidate_translator.rb} +0 -0
- data/lib/{game_board_folder.rb → threesmodel/game_board_folder.rb} +0 -1
- data/lib/{game_creator.rb → threesmodel/game_creator.rb} +1 -3
- data/lib/{game_over_checker.rb → threesmodel/game_over_checker.rb} +0 -0
- data/lib/{line_folder.rb → threesmodel/line_folder.rb} +5 -36
- data/lib/{next_number_determinator.rb → threesmodel/next_number_determinator.rb} +0 -0
- data/lib/{next_number_positioner.rb → threesmodel/next_number_positioner.rb} +0 -0
- data/lib/{score_calculator.rb → threesmodel/score_calculator.rb} +4 -0
- data/lib/threesmodel/version.rb +1 -1
- data/spec/candidate_extraction_spec.rb +1 -2
- data/spec/candidate_translator_spec.rb +1 -1
- data/spec/game_board_folder_spec.rb +10 -2
- data/spec/game_creator_spec.rb +1 -1
- data/spec/line_folder_spec.rb +56 -1
- data/spec/next_number_determination_spec.rb +1 -1
- data/spec/next_number_positioner_spec.rb +1 -1
- data/spec/run_sample_client_spec.rb +27 -0
- data/threesmodel.gemspec +1 -1
- metadata +18 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 687648f2451c403616405c662e634c983a194032
|
4
|
+
data.tar.gz: 4cce10bc6b1fb25c34f81a90c8fb0fcc89851ad9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a128c77f04e5e651b197121244690d2df366954baf906297dcfa953051bcb61727c3a2f81a32d028f985051db0036fbd471856c973f7104b123ff9f04f935d6
|
7
|
+
data.tar.gz: c256bfed6fcfa62d84bf6eef538ae0471baa971f1be2aab03cca76240f003f2d70cd67292950a5459f5f4d0b19d93bda864537d18fd96d93346d7f1429c99a62
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -24,9 +24,11 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
Creating and playing a basic game works like this:
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
```ruby
|
28
|
+
require 'threesmodel'
|
29
|
+
game_controller = Threesmodel::GameController.new
|
30
|
+
game_state = game_controller.start_new_game
|
31
|
+
```
|
30
32
|
|
31
33
|
The game state object is a hash with the keys :id (the identifier of the game),
|
32
34
|
:game the layout of the current game board (as an instance of a Matrix class),
|
@@ -36,10 +38,49 @@ The game state object is a hash with the keys :id (the identifier of the game),
|
|
36
38
|
|
37
39
|
Play by issuing fold calls to the game controller identifying the game with the id like this:
|
38
40
|
|
39
|
-
|
40
|
-
game_state = game_controller.
|
41
|
-
game_state = game_controller.
|
42
|
-
game_state = game_controller.
|
41
|
+
```ruby
|
42
|
+
game_state = game_controller.fold_left(game_state[:id])
|
43
|
+
game_state = game_controller.fold_right(game_state[:id])
|
44
|
+
game_state = game_controller.fold_up(game_state[:id])
|
45
|
+
game_state = game_controller.fold_down(game_state[:id])
|
46
|
+
```
|
47
|
+
|
48
|
+
## Creating an automated solver
|
49
|
+
|
50
|
+
If you want to try to make an automated puzzel solver. You can do the following.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'threesmodel'
|
54
|
+
class MySmartPlayer < Threesmodel::BaseGameAutomation
|
55
|
+
|
56
|
+
|
57
|
+
def play()
|
58
|
+
# for your convenience you will have a few API classes set up for you.
|
59
|
+
# these two gives you the opportunity to try things out, before you decide
|
60
|
+
# which move you want to make
|
61
|
+
@game_board_folder
|
62
|
+
@score_calculator
|
63
|
+
|
64
|
+
# The game controller allows you to execute a move (or fold as I like to call it)
|
65
|
+
@game = @game_controller.fold_down(@game[:id])
|
66
|
+
end
|
67
|
+
```
|
68
|
+
Note that you should keep the state returned after the fold in order to be able
|
69
|
+
to progress with the game.
|
70
|
+
|
71
|
+
Then you should call the automated solver in this manner (I usually use a rspec test cases as a runner).
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
player = MySmartPlayer.new
|
75
|
+
player.play_many(number_of_games_to_play)
|
76
|
+
```
|
77
|
+
Where the "number_of_games_to_play" parameter is an integer stating the numnber of games the algorithm shall play.
|
78
|
+
The end result is a few stats about the runs. High score, a histogram of the value of the highest cell in the game board.
|
79
|
+
And a file (called by the solver class name dot txt) containing all the scores if you want to make your own statistical analysis.
|
80
|
+
|
81
|
+
Happy hacking!
|
82
|
+
|
83
|
+
Future ideas is to create some basic suppurt for setting up a solver algorithm competition.
|
43
84
|
|
44
85
|
|
45
86
|
## Contributing
|
@@ -1,66 +1,88 @@
|
|
1
1
|
Feature: The start situation is playable and 1's fold into 2's to create 3's.
|
2
2
|
Scenario: I have just started a game. The board is full of small numbers and empty cells.
|
3
3
|
|
4
|
-
Given a
|
4
|
+
Given a game board
|
5
5
|
|0|1|2|0|
|
6
6
|
|0|1|2|0|
|
7
7
|
|0|1|2|0|
|
8
8
|
|0|1|2|0|
|
9
9
|
When the game is folded right to left
|
10
|
-
Then the
|
10
|
+
Then the game board looks like
|
11
11
|
|1|2|0|0|
|
12
12
|
|1|2|0|0|
|
13
13
|
|1|2|0|0|
|
14
14
|
|1|2|0|0|
|
15
15
|
|
16
16
|
Scenario: I am folding ones and twos in a fairly empty board
|
17
|
-
Given a
|
17
|
+
Given a game board
|
18
18
|
|1|2|0|0|
|
19
19
|
|1|2|0|0|
|
20
20
|
|1|2|0|0|
|
21
21
|
|1|2|0|0|
|
22
22
|
When the game is folded right to left
|
23
|
-
Then the
|
23
|
+
Then the game board looks like
|
24
24
|
|3|0|0|0|
|
25
25
|
|3|0|0|0|
|
26
26
|
|3|0|0|0|
|
27
27
|
|3|0|0|0|
|
28
28
|
|
29
29
|
Scenario: I am folding right over zeros
|
30
|
-
Given a
|
30
|
+
Given a game board
|
31
31
|
|1|2|0|0|
|
32
32
|
|1|2|0|0|
|
33
33
|
|1|2|0|0|
|
34
34
|
|1|2|0|0|
|
35
35
|
When the game is folded left to right
|
36
|
-
Then the
|
36
|
+
Then the game board looks like
|
37
37
|
|0|1|2|0|
|
38
38
|
|0|1|2|0|
|
39
39
|
|0|1|2|0|
|
40
40
|
|0|1|2|0|
|
41
41
|
|
42
42
|
Scenario: I am folding up over zeros
|
43
|
-
Given a
|
43
|
+
Given a game board
|
44
44
|
|0|0|0|0|
|
45
45
|
|2|2|2|2|
|
46
46
|
|1|1|1|1|
|
47
47
|
|0|0|0|0|
|
48
48
|
When the game is folded upwards
|
49
|
-
Then the
|
49
|
+
Then the game board looks like
|
50
50
|
|2|2|2|2|
|
51
51
|
|1|1|1|1|
|
52
52
|
|0|0|0|0|
|
53
53
|
|0|0|0|0|
|
54
54
|
|
55
55
|
Scenario: I am folding down over zeros
|
56
|
-
Given a
|
56
|
+
Given a game board
|
57
57
|
|0|0|0|0|
|
58
58
|
|2|2|2|2|
|
59
59
|
|1|1|1|1|
|
60
60
|
|0|0|0|0|
|
61
61
|
When the game is folded downwards
|
62
|
-
Then the
|
62
|
+
Then the game board looks like
|
63
63
|
|0|0|0|0|
|
64
64
|
|0|0|0|0|
|
65
65
|
|2|2|2|2|
|
66
66
|
|1|1|1|1|
|
67
|
+
|
68
|
+
Scenario: Folding a specific game board
|
69
|
+
Given a game board
|
70
|
+
|2|24| 1|24|
|
71
|
+
|0| 3| 6|12|
|
72
|
+
|0| 6| 2| 2|
|
73
|
+
|0| 0| 0| 3|
|
74
|
+
Then the game board can fold left
|
75
|
+
And the game board can fold down
|
76
|
+
And the game board can not fold up
|
77
|
+
And the game board can not fold right
|
78
|
+
|
79
|
+
Scenario: Folding a specific game board
|
80
|
+
Given a game board
|
81
|
+
|6|12| 1| 6|
|
82
|
+
|2| 6| 1| 1|
|
83
|
+
|0| 0| 3|12|
|
84
|
+
|0| 0| 0| 2|
|
85
|
+
Then the game board can fold left
|
86
|
+
And the game board can fold down
|
87
|
+
And the game board can not fold up
|
88
|
+
And the game board can not fold right
|
@@ -1,81 +1,81 @@
|
|
1
1
|
Feature: The start situation is playable and equal numbers fold to create new, double numbers.
|
2
2
|
Scenario: Threes will fold into sixes.
|
3
|
-
Given a
|
3
|
+
Given a game board
|
4
4
|
|0| 0|0|0|
|
5
5
|
|0| 0|0|0|
|
6
6
|
|3| 6|0|0|
|
7
7
|
|3|12|2|0|
|
8
8
|
When the game is folded downwards
|
9
|
-
Then the
|
9
|
+
Then the game board looks like
|
10
10
|
|0| 0|0|0|
|
11
11
|
|0| 0|0|0|
|
12
12
|
|0| 6|0|0|
|
13
13
|
|6|12|2|0|
|
14
14
|
|
15
15
|
Scenario: Equal numbers in several lines
|
16
|
-
Given a
|
16
|
+
Given a game board
|
17
17
|
|1|2| 0| 0|
|
18
18
|
|1|2| 0| 0|
|
19
19
|
|3|6|12|24|
|
20
20
|
|3|6|12|24|
|
21
21
|
When the game is folded downwards
|
22
|
-
Then the
|
22
|
+
Then the game board looks like
|
23
23
|
|0| 0| 0| 0|
|
24
24
|
|1| 2| 0| 0|
|
25
25
|
|1| 2| 0| 0|
|
26
26
|
|6|12|24|48|
|
27
27
|
|
28
28
|
Scenario: Equal numbers in the middle of the line
|
29
|
-
Given a
|
29
|
+
Given a game board
|
30
30
|
|6|2| 3| 0|
|
31
31
|
|6|3|12|24|
|
32
32
|
|6|3|12|24|
|
33
33
|
|3|6| 3| 3|
|
34
34
|
When the game is folded downwards
|
35
|
-
Then the
|
35
|
+
Then the game board looks like
|
36
36
|
| 0|0| 0| 0|
|
37
37
|
| 6|2| 3| 0|
|
38
38
|
|12|6|24|48|
|
39
39
|
| 3|6| 3| 3|
|
40
40
|
|
41
41
|
Scenario: Fold an unanimous matrix completely
|
42
|
-
Given a
|
42
|
+
Given a game board
|
43
43
|
|12|12|12|12|
|
44
44
|
|12|12|12|12|
|
45
45
|
|12|12|12|12|
|
46
46
|
|12|12|12|12|
|
47
47
|
When the game is folded downwards
|
48
|
-
Then the
|
48
|
+
Then the game board looks like
|
49
49
|
| 0| 0| 0| 0|
|
50
50
|
|12|12|12|12|
|
51
51
|
|12|12|12|12|
|
52
52
|
|24|24|24|24|
|
53
53
|
When the game is folded downwards
|
54
|
-
Then the
|
54
|
+
Then the game board looks like
|
55
55
|
| 0| 0| 0| 0|
|
56
56
|
| 0| 0| 0| 0|
|
57
57
|
|24|24|24|24|
|
58
58
|
|24|24|24|24|
|
59
59
|
When the game is folded downwards
|
60
|
-
Then the
|
60
|
+
Then the game board looks like
|
61
61
|
| 0| 0| 0| 0|
|
62
62
|
| 0| 0| 0| 0|
|
63
63
|
| 0| 0| 0| 0|
|
64
64
|
|48|48|48|48|
|
65
65
|
When the game is folded right
|
66
|
-
Then the
|
66
|
+
Then the game board looks like
|
67
67
|
|0| 0| 0| 0|
|
68
68
|
|0| 0| 0| 0|
|
69
69
|
|0| 0| 0| 0|
|
70
70
|
|0|48|48|96|
|
71
71
|
When the game is folded right
|
72
|
-
Then the
|
72
|
+
Then the game board looks like
|
73
73
|
|0|0| 0| 0|
|
74
74
|
|0|0| 0| 0|
|
75
75
|
|0|0| 0| 0|
|
76
76
|
|0|0|96|96|
|
77
77
|
When the game is folded right
|
78
|
-
Then the
|
78
|
+
Then the game board looks like
|
79
79
|
|0|0|0| 0|
|
80
80
|
|0|0|0| 0|
|
81
81
|
|0|0|0| 0|
|
data/features/scoring.feature
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Feature: For every game state there is a score.
|
2
2
|
Scenario: Ones and twos give zero points.
|
3
|
-
Given a
|
3
|
+
Given a game board
|
4
4
|
|0|0|0|1|
|
5
5
|
|0|1|2|0|
|
6
6
|
|2|1|0|2|
|
@@ -8,7 +8,7 @@ Scenario: Ones and twos give zero points.
|
|
8
8
|
Then score is:"0"
|
9
9
|
|
10
10
|
Scenario: A three gives 3 points
|
11
|
-
Given a
|
11
|
+
Given a game board
|
12
12
|
|0|0|0|1|
|
13
13
|
|0|1|2|0|
|
14
14
|
|2|1|0|2|
|
@@ -16,7 +16,7 @@ Scenario: A three gives 3 points
|
|
16
16
|
Then score is:"3"
|
17
17
|
|
18
18
|
Scenario: A six gives 9 points
|
19
|
-
Given a
|
19
|
+
Given a game board
|
20
20
|
|0|0|0|1|
|
21
21
|
|0|1|2|0|
|
22
22
|
|2|1|0|2|
|
@@ -24,7 +24,7 @@ Scenario: A six gives 9 points
|
|
24
24
|
Then score is:"9"
|
25
25
|
|
26
26
|
Scenario: A 12 gives 27 points
|
27
|
-
Given a
|
27
|
+
Given a game board
|
28
28
|
|0|0|0| 1|
|
29
29
|
|0|1|2| 0|
|
30
30
|
|2|1|0| 2|
|
@@ -32,7 +32,7 @@ Scenario: A 12 gives 27 points
|
|
32
32
|
Then score is:"27"
|
33
33
|
|
34
34
|
Scenario: A 24 gives 81 points
|
35
|
-
Given a
|
35
|
+
Given a game board
|
36
36
|
|0|0|0| 1|
|
37
37
|
|0|1|2| 0|
|
38
38
|
|2|1|0| 2|
|
@@ -40,7 +40,7 @@ Scenario: A 24 gives 81 points
|
|
40
40
|
Then score is:"81"
|
41
41
|
|
42
42
|
Scenario: A 48 gives 243 points
|
43
|
-
Given a
|
43
|
+
Given a game board
|
44
44
|
|0|0|0| 1|
|
45
45
|
|0|1|2| 0|
|
46
46
|
|2|1|0| 2|
|
@@ -48,7 +48,7 @@ Scenario: A 48 gives 243 points
|
|
48
48
|
Then score is:"243"
|
49
49
|
|
50
50
|
Scenario: A 96 gives 729 points
|
51
|
-
Given a
|
51
|
+
Given a game board
|
52
52
|
|0|0|0| 1|
|
53
53
|
|0|1|2| 0|
|
54
54
|
|2|1|0| 2|
|
@@ -56,7 +56,7 @@ Scenario: A 96 gives 729 points
|
|
56
56
|
Then score is:"729"
|
57
57
|
|
58
58
|
Scenario: A 192 gives 2187 points
|
59
|
-
Given a
|
59
|
+
Given a game board
|
60
60
|
|0|0|0| 1|
|
61
61
|
|0|1|2| 0|
|
62
62
|
|2|1|0| 2|
|
@@ -64,7 +64,7 @@ Scenario: A 192 gives 2187 points
|
|
64
64
|
Then score is:"2187"
|
65
65
|
|
66
66
|
Scenario: A 384 gives 6561 points
|
67
|
-
Given a
|
67
|
+
Given a game board
|
68
68
|
|0|0|0| 1|
|
69
69
|
|0|1|2| 0|
|
70
70
|
|2|1|0| 2|
|
@@ -72,7 +72,7 @@ Scenario: A 384 gives 6561 points
|
|
72
72
|
Then score is:"6561"
|
73
73
|
|
74
74
|
Scenario: A 768 gives 19683 points
|
75
|
-
Given a
|
75
|
+
Given a game board
|
76
76
|
|0|0|0| 1|
|
77
77
|
|0|1|2| 0|
|
78
78
|
|2|1|0| 2|
|
@@ -80,7 +80,7 @@ Scenario: A 768 gives 19683 points
|
|
80
80
|
Then score is:"19683"
|
81
81
|
|
82
82
|
Scenario: A 1536 gives 59049 points
|
83
|
-
Given a
|
83
|
+
Given a game board
|
84
84
|
|0|0|0| 1|
|
85
85
|
|0|1|2| 0|
|
86
86
|
|2|1|0| 2|
|
@@ -88,7 +88,7 @@ Scenario: A 1536 gives 59049 points
|
|
88
88
|
Then score is:"59049"
|
89
89
|
|
90
90
|
Scenario: A 3072 gives 177147 points
|
91
|
-
Given a
|
91
|
+
Given a game board
|
92
92
|
|0|0|0| 1|
|
93
93
|
|0|1|2| 0|
|
94
94
|
|2|1|0| 2|
|
@@ -96,7 +96,7 @@ Scenario: A 3072 gives 177147 points
|
|
96
96
|
Then score is:"177147"
|
97
97
|
|
98
98
|
Scenario: A 6144 gives 531441 points
|
99
|
-
Given a
|
99
|
+
Given a game board
|
100
100
|
|0|0|0| 1|
|
101
101
|
|0|1|2| 0|
|
102
102
|
|2|1|0| 2|
|
@@ -104,7 +104,7 @@ Scenario: A 6144 gives 531441 points
|
|
104
104
|
Then score is:"531441"
|
105
105
|
|
106
106
|
Scenario: All points are summed up
|
107
|
-
Given a
|
107
|
+
Given a game board
|
108
108
|
| 1| 2| 3| 6|
|
109
109
|
| 12| 24| 48| 96|
|
110
110
|
| 192| 384|768|1536|
|
@@ -1,8 +1,7 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require_relative '../../lib/score_calculator'
|
1
|
+
require 'threesmodel/game_board_folder'
|
2
|
+
require 'threesmodel/score_calculator'
|
4
3
|
|
5
|
-
Given(/^a
|
4
|
+
Given(/^a game board$/) do |table|
|
6
5
|
@game_board = GameBoardFolder.new
|
7
6
|
matrix = table.raw
|
8
7
|
matrix = matrix.map{|l| l.map{|cell| cell.strip.to_i}}
|
@@ -29,7 +28,7 @@ When(/^the game is folded right$/) do
|
|
29
28
|
@state = @game_board.fold_right @state
|
30
29
|
end
|
31
30
|
|
32
|
-
Then(/^the
|
31
|
+
Then(/^the game board looks like$/) do |table|
|
33
32
|
matrix = table.raw
|
34
33
|
matrix = matrix.map{|l| l.map{|cell| cell.strip.to_i}}
|
35
34
|
expect(@state).to eq(Matrix.rows(matrix))
|
@@ -39,3 +38,20 @@ Then(/^score is:"(.*?)"$/) do |score|
|
|
39
38
|
actual_score = ScoreCalculator.score_for(@state)
|
40
39
|
expect(actual_score).to eq(score.to_i)
|
41
40
|
end
|
41
|
+
|
42
|
+
|
43
|
+
Then(/^the game board can fold left$/) do
|
44
|
+
expect(@game_board.can_fold_left?(@state)).to eq(true)
|
45
|
+
end
|
46
|
+
|
47
|
+
Then(/^the game board can fold down$/) do
|
48
|
+
expect(@game_board.can_fold_down?(@state)).to eq(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
Then(/^the game board can not fold up$/) do
|
52
|
+
expect(@game_board.can_fold_up?(@state)).to eq(false)
|
53
|
+
end
|
54
|
+
|
55
|
+
Then(/^the game board can not fold right$/) do
|
56
|
+
expect(@game_board.can_fold_right?(@state)).to eq(false)
|
57
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'threesmodel'
|
2
|
+
require 'threesmodel/game_board_folder'
|
3
3
|
Before do
|
4
4
|
end
|
5
5
|
|
@@ -16,6 +16,7 @@ Then(/^the game is not over$/) do
|
|
16
16
|
expect(@game_state[:game_over]).to eq(false)
|
17
17
|
end
|
18
18
|
|
19
|
+
|
19
20
|
Then(/^the game board has (\d+) cells filled$/) do |filled_cells|
|
20
21
|
c = []
|
21
22
|
values = @game_state[:game].row_vectors.each{|row|
|
@@ -28,12 +29,13 @@ end
|
|
28
29
|
|
29
30
|
Then(/^the board contains no other but the following numbers:$/) do |table|
|
30
31
|
data = table.raw[0]
|
32
|
+
data = data.map{|x| x.to_i}
|
31
33
|
c = []
|
32
|
-
|
34
|
+
@game_state[:game].row_vectors.each{|row|
|
33
35
|
c << row.to_a
|
34
36
|
}
|
35
37
|
c.flatten!.uniq!
|
36
|
-
|
37
|
-
expect(
|
38
|
+
c.each{|value|
|
39
|
+
expect(data.include?(value.to_i)).to eq(true)
|
38
40
|
}
|
39
41
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'threesmodel'
|
2
|
+
|
3
|
+
class HighestPayingNextMovePlayer < Threesmodel::BaseGameAutomation
|
4
|
+
def play
|
5
|
+
while (not @game[:game_over]) do
|
6
|
+
possible_moves = [:fold_right, :fold_left, :fold_up, :fold_down]
|
7
|
+
possible_moves.select! do |move|
|
8
|
+
@game_board_folder.send("can_#{move.to_s}?".to_sym, @game[:game])
|
9
|
+
end
|
10
|
+
|
11
|
+
scored_moves = possible_moves.map do |move|
|
12
|
+
[move, @score_calculator.score_for(@game_board_folder.send(move, @game[:game]))]
|
13
|
+
end
|
14
|
+
new_move = scored_moves.max{ |a, b| a[1] <=> b[1] }[0]
|
15
|
+
@game = @game_controller.send(new_move, @game[:id])
|
16
|
+
end
|
17
|
+
@game[:score]
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'threesmodel'
|
2
|
+
|
3
|
+
class IFearDownFoldsPlayer < Threesmodel::BaseGameAutomation
|
4
|
+
|
5
|
+
def play
|
6
|
+
while (not @game[:game_over]) do
|
7
|
+
|
8
|
+
if (@game_board_folder.can_fold_up?(@game[:game]) or @game_board_folder.can_fold_left?(@game[:game]) or @game_board_folder.can_fold_right?(@game[:game])) then
|
9
|
+
@game_controller.fold_up(@game[:id])
|
10
|
+
@game_controller.fold_right(@game[:id])
|
11
|
+
@game = @game_controller.fold_left(@game[:id])
|
12
|
+
else
|
13
|
+
@game = @game_controller.fold_down(@game[:id])
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
@game[:score]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'threesmodel'
|
2
|
+
|
3
|
+
|
4
|
+
class SampleAutomatedPlayer < Threesmodel::BaseGameAutomation
|
5
|
+
|
6
|
+
def play
|
7
|
+
while (not @game[:game_over]) do
|
8
|
+
@game_controller.fold_right(@game[:id])
|
9
|
+
@game_controller.fold_up(@game[:id])
|
10
|
+
@game_controller.fold_left(@game[:id])
|
11
|
+
@game = @game_controller.fold_down(@game[:id])
|
12
|
+
end
|
13
|
+
@game[:score]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'threesmodel'
|
2
|
+
|
3
|
+
class UpRightLeftDownOrderPlayer < Threesmodel::BaseGameAutomation
|
4
|
+
|
5
|
+
def play
|
6
|
+
while (not @game[:game_over]) do
|
7
|
+
if @game_board_folder.can_fold_up?(@game[:game])
|
8
|
+
@game = @game_controller.fold_up(@game[:id])
|
9
|
+
elsif @game_board_folder.can_fold_right?(@game[:game])
|
10
|
+
@game = @game_controller.fold_right(@game[:id])
|
11
|
+
elsif @game_board_folder.can_fold_left?(@game[:game])
|
12
|
+
@game = @game_controller.fold_left(@game[:id])
|
13
|
+
else
|
14
|
+
@game = @game_controller.fold_down(@game[:id])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@game[:score]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/threesmodel.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
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'
|
2
|
+
require 'threesmodel/candidate_translator'
|
3
|
+
require 'threesmodel/candidate_extractor'
|
4
|
+
require 'threesmodel/game_board_folder'
|
5
|
+
require 'threesmodel/next_number_positioner'
|
6
|
+
require 'threesmodel/next_number_determinator'
|
7
|
+
require 'threesmodel/game_over_checker'
|
8
|
+
require 'threesmodel/game_creator'
|
9
9
|
require 'securerandom'
|
10
|
+
require 'threesmodel/score_calculator'
|
10
11
|
require 'matrix'
|
11
|
-
require 'score_calculator'
|
12
12
|
|
13
13
|
module Threesmodel
|
14
14
|
class InternalAPI
|
@@ -16,6 +16,63 @@ module Threesmodel
|
|
16
16
|
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
class BaseGameAutomation
|
21
|
+
def initialize
|
22
|
+
@game_controller = Threesmodel::GameController.new
|
23
|
+
@game_board_folder = GameBoardFolder.new
|
24
|
+
@score_calculator = ScoreCalculator.new
|
25
|
+
@scores = []
|
26
|
+
@highest_cell_value_histogram = { c3: 0, c6: 0, c12: 0, c24: 0, c48: 0, c96: 0, c192: 0, c384: 0, c768: 0, c1536: 0, c3072: 0, c6144:0}
|
27
|
+
@best_game = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def play;
|
31
|
+
raise 'Method missing - please implement "play" method.';
|
32
|
+
end
|
33
|
+
|
34
|
+
def score_filename
|
35
|
+
self.class.name + ".txt"
|
36
|
+
end
|
37
|
+
|
38
|
+
def play_many(times=100)
|
39
|
+
max_score = 0
|
40
|
+
iteration = 1
|
41
|
+
times.times do
|
42
|
+
@game = @game_controller.start_new_game
|
43
|
+
score = play
|
44
|
+
if score > max_score then
|
45
|
+
max_score = score
|
46
|
+
@best_game = @game[:game]
|
47
|
+
end
|
48
|
+
@scores << score
|
49
|
+
print "." if iteration % 1000 == 0
|
50
|
+
update_highest_value_histogram(@game[:game])
|
51
|
+
end
|
52
|
+
puts "Done!"
|
53
|
+
puts max_score
|
54
|
+
puts @highest_cell_value_histogram
|
55
|
+
|
56
|
+
File.open(score_filename, "w+") do |f|
|
57
|
+
f.puts(@scores)
|
58
|
+
end
|
59
|
+
|
60
|
+
puts "Best game is:"
|
61
|
+
puts @best_game
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_highest_value_histogram(game_board)
|
65
|
+
highest_cell_value = 0
|
66
|
+
game_board.each { |val|
|
67
|
+
highest_cell_value = val if val > highest_cell_value
|
68
|
+
}
|
69
|
+
key = ("c" + highest_cell_value.to_s).to_sym
|
70
|
+
@highest_cell_value_histogram[key] += 1
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
19
76
|
class GameController
|
20
77
|
|
21
78
|
def initialize
|
File without changes
|
File without changes
|
@@ -18,12 +18,15 @@ class LineFolder
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def wall_index_of
|
21
|
+
return 3 if self == ([1,1,1,1] or [2,2,2,2])
|
22
|
+
return 3 if self[1..3] == [0,0,0]
|
21
23
|
return 0 if self[0] == 0
|
22
24
|
return 0 if self[0] == 1 and self[1] == 2
|
23
25
|
return 0 if self[0] == 2 and self[1] == 1
|
24
26
|
if self[0] > 2
|
25
27
|
return 0 if self[0] == self[1]
|
26
28
|
end
|
29
|
+
return 3 if self[2..3] == [0,0]
|
27
30
|
return 1 if self[1] == 0
|
28
31
|
return 1 if (self[0] != self[1]) and self[1] == 1 and self[2] == 2
|
29
32
|
return 1 if (self[0] != self[1]) and self[1] == 2 and self[2] == 1
|
@@ -38,6 +41,7 @@ class LineFolder
|
|
38
41
|
if self[2] > 2
|
39
42
|
return 2 if self[2] == self[3]
|
40
43
|
end
|
44
|
+
3
|
41
45
|
end
|
42
46
|
|
43
47
|
end
|
@@ -45,7 +49,6 @@ class LineFolder
|
|
45
49
|
|
46
50
|
def fold(line)
|
47
51
|
if can_fold?(line) then
|
48
|
-
if(line == [0,0,0,0]) then return line end
|
49
52
|
return line.squash
|
50
53
|
else
|
51
54
|
return line
|
@@ -53,41 +56,7 @@ class LineFolder
|
|
53
56
|
end
|
54
57
|
|
55
58
|
def can_fold?(line)
|
56
|
-
|
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
|
59
|
+
return line.wall_index_of < 3
|
91
60
|
end
|
92
61
|
|
93
62
|
end
|
File without changes
|
File without changes
|
data/lib/threesmodel/version.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require "Matrix"
|
1
|
+
require "threesmodel/game_board_folder"
|
3
2
|
|
4
3
|
describe GameBoardFolder do
|
5
4
|
|
@@ -47,4 +46,13 @@ describe GameBoardFolder do
|
|
47
46
|
expect(state.row(3).to_a).to eq([2,2,2,2])
|
48
47
|
end
|
49
48
|
|
49
|
+
it "can not fold up when no columns can be folded" do
|
50
|
+
state = @game_board_folder.can_fold_up?(
|
51
|
+
Matrix.rows([[3,0,3,3],
|
52
|
+
[2,0,2,1],
|
53
|
+
[3,0,3,3],
|
54
|
+
[2,0,2,1]]))
|
55
|
+
expect(state).to eq(false)
|
56
|
+
end
|
57
|
+
|
50
58
|
end
|
data/spec/game_creator_spec.rb
CHANGED
data/spec/line_folder_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require "threesmodel/line_folder"
|
2
2
|
|
3
3
|
describe LineFolder do
|
4
4
|
|
@@ -33,10 +33,18 @@ describe LineFolder do
|
|
33
33
|
expect(@line_folder.can_fold?([0,0,1,2])).to eq(true)
|
34
34
|
end
|
35
35
|
|
36
|
+
it "can not fold when line is all zeros" do
|
37
|
+
expect(@line_folder.can_fold?([0,0,0,0])).to eq(false)
|
38
|
+
end
|
39
|
+
|
36
40
|
it "can not fold when line is all ones" do
|
37
41
|
expect(@line_folder.can_fold?([1,1,1,1])).to eq(false)
|
38
42
|
end
|
39
43
|
|
44
|
+
it "can not fold when line is all twos" do
|
45
|
+
expect(@line_folder.can_fold?([2,2,2,2])).to eq(false)
|
46
|
+
end
|
47
|
+
|
40
48
|
it "can fold when line has a one and a two adjacent" do
|
41
49
|
expect(@line_folder.can_fold?([1,2,1,1])).to eq(true)
|
42
50
|
end
|
@@ -49,6 +57,20 @@ describe LineFolder do
|
|
49
57
|
expect(@line_folder.can_fold?([3,3,6,12])).to eq(true)
|
50
58
|
end
|
51
59
|
|
60
|
+
it "can not fold" do
|
61
|
+
expect(@line_folder.can_fold?([2,0,0,0])).to eq(false)
|
62
|
+
expect(@line_folder.can_fold?([24,3,6,0])).to eq(false)
|
63
|
+
expect(@line_folder.can_fold?([1,6,2,0])).to eq(false)
|
64
|
+
expect(@line_folder.can_fold?([24,12,2,3])).to eq(false)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "can not even fold" do
|
68
|
+
expect(@line_folder.can_fold?([6,2,0,0])).to eq(false)
|
69
|
+
expect(@line_folder.can_fold?([12,6,0,0])).to eq(false)
|
70
|
+
expect(@line_folder.can_fold?([1,1,3,0])).to eq(false)
|
71
|
+
expect(@line_folder.can_fold?([6,1,12,2])).to eq(false)
|
72
|
+
end
|
73
|
+
|
52
74
|
it "folds undetectably when all zero line" do
|
53
75
|
expect(@line_folder.fold([0,0,0,0])).to eq([0,0,0,0])
|
54
76
|
end
|
@@ -84,4 +106,37 @@ describe LineFolder do
|
|
84
106
|
expect(@line_folder.fold([1,2,0,3])).to eq([3,0,3,0])
|
85
107
|
end
|
86
108
|
|
109
|
+
it "considers to have the wall index at cell 3 for an all zero line" do
|
110
|
+
expect([0,0,0,0].wall_index_of).to eq(3)
|
111
|
+
end
|
112
|
+
it "considers to have the wall index at cell 3 for an all ones line" do
|
113
|
+
expect([1,1,1,1].wall_index_of).to eq(3)
|
114
|
+
end
|
115
|
+
it "considers to have the wall index at cell 3 for an all twos line" do
|
116
|
+
expect([2,2,2,2].wall_index_of).to eq(3)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "considers an line with trailing zeros to have wall index 3" do
|
120
|
+
expect([2,0,0,0].wall_index_of).to eq(3)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "considers an line with less trailing zeros to have wall index 3" do
|
124
|
+
expect([2,2,0,0].wall_index_of).to eq(3)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "considers an line with trailing ones to have wall index 3" do
|
128
|
+
expect([3,1,1,1].wall_index_of).to eq(3)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "considers an line with less trailing ones to have wall index 3" do
|
132
|
+
expect([3,6,1,1].wall_index_of).to eq(3)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "considers an line with trailing twos to have wall index 3" do
|
136
|
+
expect([3,2,2,2].wall_index_of).to eq(3)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "considers an line with less trailing twos to have wall index 3" do
|
140
|
+
expect([3,6,2,2].wall_index_of).to eq(3)
|
141
|
+
end
|
87
142
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "solvers/sample_automated_player"
|
2
|
+
require "solvers/i_fear_down_folds_player"
|
3
|
+
require "solvers/up_right_left_down_order_player"
|
4
|
+
require "solvers/highest_paying_next_move_player"
|
5
|
+
|
6
|
+
describe SampleAutomatedPlayer do
|
7
|
+
it "runs the sample automated player" do
|
8
|
+
player = SampleAutomatedPlayer.new
|
9
|
+
player.play_many(5)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "runs the player that tries to avoid down folds" do
|
13
|
+
player = IFearDownFoldsPlayer.new
|
14
|
+
player.play_many(5)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "runs the player that tries to fold up right" do
|
18
|
+
player = UpRightLeftDownOrderPlayer.new
|
19
|
+
player.play_many(5)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "runs the player that goes with the highest paying next move" do
|
23
|
+
player = HighestPayingNextMovePlayer.new
|
24
|
+
player.play_many(5)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/threesmodel.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["mans.sandstrom@gmail.com"]
|
11
11
|
spec.description = %q{A stab at implementing the game logic for the game threes, eventually making it scriptable for a bot.}
|
12
12
|
spec.summary = %q{An API for something that resembles the game threes}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/msa/threesmodel"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: threesmodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Måns Sandström
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -60,16 +60,20 @@ files:
|
|
60
60
|
- features/steps/basic_steps.rb
|
61
61
|
- features/steps/game_over_steps.rb
|
62
62
|
- features/steps/game_play_steps.rb
|
63
|
-
- lib/
|
64
|
-
- lib/
|
65
|
-
- lib/
|
66
|
-
- lib/
|
67
|
-
- lib/game_over_checker.rb
|
68
|
-
- lib/line_folder.rb
|
69
|
-
- lib/next_number_determinator.rb
|
70
|
-
- lib/next_number_positioner.rb
|
71
|
-
- lib/score_calculator.rb
|
63
|
+
- lib/solvers/highest_paying_next_move_player.rb
|
64
|
+
- lib/solvers/i_fear_down_folds_player.rb
|
65
|
+
- lib/solvers/sample_automated_player.rb
|
66
|
+
- lib/solvers/up_right_left_down_order_player.rb
|
72
67
|
- lib/threesmodel.rb
|
68
|
+
- lib/threesmodel/candidate_extractor.rb
|
69
|
+
- lib/threesmodel/candidate_translator.rb
|
70
|
+
- lib/threesmodel/game_board_folder.rb
|
71
|
+
- lib/threesmodel/game_creator.rb
|
72
|
+
- lib/threesmodel/game_over_checker.rb
|
73
|
+
- lib/threesmodel/line_folder.rb
|
74
|
+
- lib/threesmodel/next_number_determinator.rb
|
75
|
+
- lib/threesmodel/next_number_positioner.rb
|
76
|
+
- lib/threesmodel/score_calculator.rb
|
73
77
|
- lib/threesmodel/version.rb
|
74
78
|
- spec/candidate_extraction_spec.rb
|
75
79
|
- spec/candidate_translator_spec.rb
|
@@ -79,9 +83,10 @@ files:
|
|
79
83
|
- spec/line_folder_spec.rb
|
80
84
|
- spec/next_number_determination_spec.rb
|
81
85
|
- spec/next_number_positioner_spec.rb
|
86
|
+
- spec/run_sample_client_spec.rb
|
82
87
|
- spec/spec_helper.rb
|
83
88
|
- threesmodel.gemspec
|
84
|
-
homepage:
|
89
|
+
homepage: https://github.com/msa/threesmodel
|
85
90
|
licenses:
|
86
91
|
- MIT
|
87
92
|
metadata: {}
|
@@ -122,4 +127,5 @@ test_files:
|
|
122
127
|
- spec/line_folder_spec.rb
|
123
128
|
- spec/next_number_determination_spec.rb
|
124
129
|
- spec/next_number_positioner_spec.rb
|
130
|
+
- spec/run_sample_client_spec.rb
|
125
131
|
- spec/spec_helper.rb
|