linotype 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/linotype/game.rb +39 -18
- data/lib/linotype/move.rb +28 -7
- data/lib/linotype/player.rb +9 -0
- data/lib/linotype/simulator.rb +29 -0
- data/lib/linotype/strategy.rb +31 -0
- data/lib/linotype/tile.rb +12 -0
- data/lib/linotype/version.rb +1 -1
- data/lib/linotype.rb +2 -3
- metadata +4 -2
data/lib/linotype/game.rb
CHANGED
@@ -2,11 +2,12 @@ module Linotype
|
|
2
2
|
class Game
|
3
3
|
|
4
4
|
attr_accessor :moves
|
5
|
-
attr_reader :current_player
|
5
|
+
attr_reader :current_player, :players
|
6
6
|
|
7
7
|
def initialize(args={})
|
8
|
+
args = { player_one: Player.new, player_two: Player.new }.merge(args)
|
8
9
|
@board = Board.new(self, tiles: create_tile_letter_array(args[:tiles]))
|
9
|
-
@players = [
|
10
|
+
@players = [args[:player_one], args[:player_two]]
|
10
11
|
@current_player = @players[0]
|
11
12
|
@moves = []
|
12
13
|
end
|
@@ -27,7 +28,7 @@ module Linotype
|
|
27
28
|
letter_array
|
28
29
|
end
|
29
30
|
end
|
30
|
-
|
31
|
+
|
31
32
|
def play(*tile_coordinates)
|
32
33
|
tiles = find_tiles(tile_coordinates)
|
33
34
|
Move.new(self, @current_player, tiles).valid?
|
@@ -64,28 +65,22 @@ module Linotype
|
|
64
65
|
every_play_for_word(word_to_test).each do |tiles|
|
65
66
|
move = Move.new(self, @current_player, tiles)
|
66
67
|
potential_moves << move
|
67
|
-
|
68
|
+
move.undo!
|
68
69
|
end
|
69
70
|
end
|
70
|
-
potential_moves
|
71
|
+
potential_moves
|
71
72
|
end
|
72
73
|
|
73
74
|
def valid_potential_plays
|
74
|
-
test_potential_plays.select { |potential_play| potential_play
|
75
|
+
test_potential_plays.select { |potential_play| potential_play.valid? }
|
75
76
|
end
|
76
77
|
|
77
78
|
def best_next_play
|
78
|
-
valid_potential_plays.sort
|
79
|
+
valid_potential_plays.sort do |a, b|
|
80
|
+
current_player.strategy.score(b) <=> current_player.strategy.score(a)
|
81
|
+
end.first
|
79
82
|
end
|
80
83
|
|
81
|
-
def undo_last_move!
|
82
|
-
if (last_move = moves.pop) && last_move.valid?
|
83
|
-
last_move.uncover_tiles!
|
84
|
-
@current_player = other_player if last_move.valid?
|
85
|
-
end
|
86
|
-
moves.last.cover_tiles! if moves.any?
|
87
|
-
end
|
88
|
-
|
89
84
|
def over?
|
90
85
|
uncovered_tiles.empty? || two_passes_in_a_row?
|
91
86
|
end
|
@@ -148,6 +143,10 @@ module Linotype
|
|
148
143
|
def tile_rows
|
149
144
|
@board.tiles
|
150
145
|
end
|
146
|
+
|
147
|
+
def all_tiles
|
148
|
+
tile_rows.flatten
|
149
|
+
end
|
151
150
|
|
152
151
|
def letters
|
153
152
|
@board.tiles.flatten.collect(&:letter)
|
@@ -172,16 +171,24 @@ module Linotype
|
|
172
171
|
end
|
173
172
|
|
174
173
|
def uncovered_tiles
|
175
|
-
|
174
|
+
all_tiles.select { |tile| !tile.covered_by }
|
176
175
|
end
|
177
176
|
private :uncovered_tiles
|
178
177
|
|
179
178
|
def defended_tiles(player)
|
180
|
-
tile_rows.flatten.select { |tile| tile.covered_by == player && tile.defended? }
|
179
|
+
tile_rows.flatten.select { |tile| player && tile.covered_by == player && tile.defended? }
|
181
180
|
end
|
182
181
|
|
183
182
|
def covered_tiles(player)
|
184
|
-
tile_rows.flatten.select { |tile| tile.covered_by == player }
|
183
|
+
tile_rows.flatten.select { |tile| player && tile.covered_by == player }
|
184
|
+
end
|
185
|
+
|
186
|
+
def edge_tiles(player)
|
187
|
+
tile_rows.flatten.select { |tile| player && tile.edge? && tile.covered_by == player }
|
188
|
+
end
|
189
|
+
|
190
|
+
def corner_tiles(player)
|
191
|
+
tile_rows.flatten.select { |tile| player && tile.corner? && tile.covered_by == player }
|
185
192
|
end
|
186
193
|
|
187
194
|
def two_passes_in_a_row?
|
@@ -189,5 +196,19 @@ module Linotype
|
|
189
196
|
end
|
190
197
|
private :two_passes_in_a_row?
|
191
198
|
|
199
|
+
def print_board
|
200
|
+
tile_rows.each do |row|
|
201
|
+
r = ""
|
202
|
+
row.each do |tile|
|
203
|
+
r << "#{tile.letter}#{tile.covered_by ? player_number(tile.covered_by) : ' '} "
|
204
|
+
end
|
205
|
+
puts r
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def print_scores
|
210
|
+
players.each { |player| puts "Player #{player_number(player)}: #{covered_tiles(player).count}" }
|
211
|
+
end
|
212
|
+
|
192
213
|
end
|
193
214
|
end
|
data/lib/linotype/move.rb
CHANGED
@@ -1,35 +1,56 @@
|
|
1
1
|
module Linotype
|
2
2
|
class Move
|
3
3
|
|
4
|
-
attr_reader :game, :
|
4
|
+
attr_reader :game, :invalid_reason, :score, :tiles
|
5
|
+
attr_accessor :player, :previous_tile_values
|
5
6
|
|
6
7
|
def initialize(game, player, tiles)
|
7
8
|
@game = game
|
8
|
-
|
9
|
+
self.player = player
|
9
10
|
@tiles = tiles
|
11
|
+
set_previous_tile_values
|
10
12
|
calculate_valid
|
11
13
|
if valid?
|
12
14
|
calculate_scores(:before)
|
15
|
+
@game.moves << self
|
13
16
|
cover_tiles!
|
14
17
|
calculate_scores(:after)
|
15
18
|
calculate_net_scores
|
16
19
|
@game.toggle_current_player
|
20
|
+
else
|
21
|
+
@game.moves << self
|
17
22
|
end
|
18
|
-
@game.moves << self
|
19
23
|
end
|
20
24
|
|
21
25
|
def score
|
22
26
|
@score ||= {}
|
23
27
|
end
|
28
|
+
|
29
|
+
def undo!
|
30
|
+
tiles.each { |tile| tile.covered_by = previous_tile_values[tile][:covered_by] }
|
31
|
+
game.moves.delete(self)
|
32
|
+
game.toggle_current_player if valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_previous_tile_values
|
36
|
+
self.previous_tile_values = {}
|
37
|
+
tiles.each do |tile|
|
38
|
+
self.previous_tile_values[tile] = { covered_by: tile.covered_by }
|
39
|
+
end
|
40
|
+
end
|
24
41
|
|
25
42
|
def calculate_scores(time)
|
26
|
-
score["defended_#{time}".to_sym] = @game.defended_tiles(player).count
|
27
|
-
score["covered_#{time}".to_sym] = @game.covered_tiles(player).count
|
43
|
+
self.score["defended_#{time}".to_sym] = @game.defended_tiles(player).count
|
44
|
+
self.score["covered_#{time}".to_sym] = @game.covered_tiles(player).count
|
45
|
+
self.score["edges_#{time}".to_sym] = @game.edge_tiles(player).count
|
46
|
+
self.score["corners_#{time}".to_sym] = @game.corner_tiles(player).count
|
28
47
|
end
|
29
48
|
|
30
49
|
def calculate_net_scores
|
31
|
-
score[:defended] = score[:defended_after] - score[:defended_before]
|
32
|
-
score[:covered] = score[:covered_after] - score[:covered_before]
|
50
|
+
self.score[:defended] = score[:defended_after] - score[:defended_before]
|
51
|
+
self.score[:covered] = score[:covered_after] - score[:covered_before]
|
52
|
+
self.score[:edges] = score[:edges_after] - score[:edges_before]
|
53
|
+
self.score[:corners] = score[:corners_after] - score[:corners_before]
|
33
54
|
end
|
34
55
|
|
35
56
|
def valid?
|
data/lib/linotype/player.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
module Linotype
|
2
2
|
class Player
|
3
3
|
|
4
|
+
DEFAULT_STRATEGY = ->(move) { (move.score[:defended] * 2) + move.score[:covered] }
|
5
|
+
|
6
|
+
attr_accessor :strategy
|
7
|
+
|
8
|
+
def initialize(args={})
|
9
|
+
args = { strategy: DEFAULT_STRATEGY }.merge(args)
|
10
|
+
self.strategy = args[:strategy]
|
11
|
+
end
|
12
|
+
|
4
13
|
end
|
5
14
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Linotype
|
2
|
+
class Simulator
|
3
|
+
|
4
|
+
attr_accessor :player_one, :player_two, :game
|
5
|
+
|
6
|
+
def initialize(strategy_one, strategy_two)
|
7
|
+
self.player_one = Player.new(strategy: strategy_one)
|
8
|
+
self.player_two = Player.new(strategy: strategy_two)
|
9
|
+
end
|
10
|
+
|
11
|
+
def simulate!
|
12
|
+
self.game = Game.new(player_one: player_one, player_two: player_two)
|
13
|
+
puts "Let's start the simulator"
|
14
|
+
while !game.over?
|
15
|
+
if best_next_play = game.best_next_play
|
16
|
+
puts "Player #{game.player_number(game.current_player)} will play: #{best_next_play.word}"
|
17
|
+
game.play(*best_next_play.to_hash[:coordinates])
|
18
|
+
else
|
19
|
+
puts "Player #{game.player_number(game.current_player)} will pass."
|
20
|
+
game.play
|
21
|
+
end
|
22
|
+
game.print_board
|
23
|
+
game.print_scores
|
24
|
+
end
|
25
|
+
game
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Linotype
|
2
|
+
class Strategy
|
3
|
+
|
4
|
+
LETTERPRESS_FAN_HEURISTIC = ->(move) do
|
5
|
+
move.score[:covered] + move.score[:defended] + move.score[:edges] + move.score[:corners]
|
6
|
+
end
|
7
|
+
|
8
|
+
MAX_SIX_LETTERS = ->(move) do
|
9
|
+
move.word.length <= 6 ? move.word.length + move.score[:covered] : 0
|
10
|
+
end
|
11
|
+
|
12
|
+
MAX_FIVE_LETTERS = ->(move) do
|
13
|
+
move.word.length <= 5 ? move.word.length + move.score[:covered] : 0
|
14
|
+
end
|
15
|
+
|
16
|
+
MAX_THREE_LETTERS = ->(move) do
|
17
|
+
move.word.length <= 3 ? move.word.length + move.score[:covered] : 0
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :scoring_algorithm
|
21
|
+
|
22
|
+
def initialize(scoring_algorithm)
|
23
|
+
self.scoring_algorithm = scoring_algorithm
|
24
|
+
end
|
25
|
+
|
26
|
+
def score(move)
|
27
|
+
scoring_algorithm.call move
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/linotype/tile.rb
CHANGED
@@ -19,6 +19,10 @@ module Linotype
|
|
19
19
|
}
|
20
20
|
end
|
21
21
|
|
22
|
+
def board
|
23
|
+
@board
|
24
|
+
end
|
25
|
+
|
22
26
|
def to_a
|
23
27
|
[row, column]
|
24
28
|
end
|
@@ -39,6 +43,14 @@ module Linotype
|
|
39
43
|
@column ||= @board.column(self)
|
40
44
|
end
|
41
45
|
|
46
|
+
def edge?
|
47
|
+
(row == 0 || row == board.row_count - 1) || (column == 0 || column == board.column_count - 1)
|
48
|
+
end
|
49
|
+
|
50
|
+
def corner?
|
51
|
+
(row == 0 || row == board.row_count - 1) && (column == 0 || column == board.column_count - 1)
|
52
|
+
end
|
53
|
+
|
42
54
|
def previous?(coordinate_type)
|
43
55
|
send(coordinate_type) > 0
|
44
56
|
end
|
data/lib/linotype/version.rb
CHANGED
data/lib/linotype.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linotype
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-31 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! ' linotype implements that game mechanic of Letterpress for iOS by
|
15
15
|
atebits software. The program was written to support the automation of letterpress
|
@@ -36,6 +36,8 @@ files:
|
|
36
36
|
- lib/linotype/game.rb
|
37
37
|
- lib/linotype/move.rb
|
38
38
|
- lib/linotype/player.rb
|
39
|
+
- lib/linotype/simulator.rb
|
40
|
+
- lib/linotype/strategy.rb
|
39
41
|
- lib/linotype/tile.rb
|
40
42
|
- lib/linotype/version.rb
|
41
43
|
- linotype.gemspec
|