linotype 0.0.3 → 0.0.4

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.
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 = [Player.new, Player.new]
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
- undo_last_move!
68
+ move.undo!
68
69
  end
69
70
  end
70
- potential_moves.collect(&:to_hash)
71
+ potential_moves
71
72
  end
72
73
 
73
74
  def valid_potential_plays
74
- test_potential_plays.select { |potential_play| potential_play[:valid] }
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 { |a, b| b[:score][:defended] <=> a[:score][:defended] }.first
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
- covered_tiles(nil)
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, :player, :invalid_reason, :score
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
- @player = player
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?
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Linotype
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/linotype.rb CHANGED
@@ -4,8 +4,7 @@ require "linotype/board"
4
4
  require "linotype/move"
5
5
  require "linotype/player"
6
6
  require "linotype/tile"
7
+ require "linotype/strategy"
8
+ require "linotype/simulator"
7
9
  require "linotype/dictionary/dictionary"
8
10
 
9
- module Linotype
10
-
11
- end
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.3
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-30 00:00:00.000000000 Z
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