Class: Upwords::Player
- Inherits:
-
Object
- Object
- Upwords::Player
- Defined in:
- lib/upwords/player.rb
Instance Attribute Summary collapse
-
#last_turn ⇒ Object
Returns the value of attribute last_turn.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#score ⇒ Object
Returns the value of attribute score.
Instance Method Summary collapse
-
#cpu? ⇒ Boolean
————— AI move methods —————.
-
#cpu_move(board, dict, batch_size = 1000, min_score = 0) ⇒ Object
Execute a legal move based on a predefined strategy.
-
#initialize(name, rack_capacity = 7, cpu = false) ⇒ Player
constructor
A new instance of Player.
-
#legal_move_shapes(board) ⇒ Object
Return a list of legal move shapes that player could make on board.
-
#legal_move_shapes_letter_permutations(board) ⇒ Object
Return list of all possible letter permutations on legal move shapes that player could make on board Elements in the list will be in form of [(row, col), letter].
- #letters ⇒ Object
- #play_letter(board, letter, row, col) ⇒ Object
- #rack_capacity ⇒ Object
- #rack_empty? ⇒ Boolean
- #rack_full? ⇒ Boolean
- #refill_rack(letter_bank) ⇒ Object
- #show_rack(masked = false) ⇒ Object
- #swap_letter(letter, letter_bank) ⇒ Object
-
#take_from(board, row, col) ⇒ Object
——————————- Game object interaction methods ——————————-.
- #take_letter(letter) ⇒ Object
Constructor Details
#initialize(name, rack_capacity = 7, cpu = false) ⇒ Player
Returns a new instance of Player.
10 11 12 13 14 15 16 |
# File 'lib/upwords/player.rb', line 10 def initialize(name, rack_capacity=7, cpu=false) @name = name @rack = LetterRack.new(rack_capacity) @score = 0 @last_turn = nil @cpu = cpu end |
Instance Attribute Details
#last_turn ⇒ Object
Returns the value of attribute last_turn.
8 9 10 |
# File 'lib/upwords/player.rb', line 8 def last_turn @last_turn end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
7 8 9 |
# File 'lib/upwords/player.rb', line 7 def name @name end |
#score ⇒ Object
Returns the value of attribute score.
8 9 10 |
# File 'lib/upwords/player.rb', line 8 def score @score end |
Instance Method Details
#cpu? ⇒ Boolean
AI move methods
84 85 86 |
# File 'lib/upwords/player.rb', line 84 def cpu? @cpu end |
#cpu_move(board, dict, batch_size = 1000, min_score = 0) ⇒ Object
Execute a legal move based on a predefined strategy
Basic strategy:
-
Find all legal move shapes and all possible letter permutations across those shapes (this computation is relatively quick)
-
Retun the highest score from permutation that do not produce in any illegal new words (this computation is slow…)
-
To speed up the above computation: + Only check a batch of permutations at a time (specified in ‘batch_size’ argument) + After each batch, terminate the subroutine if it finds a score that is at least as high as the given ‘min_score’ + Decrement the ‘min_score’ after each batch that does not terminate the subroutine to prevent endless searches
TODO: refactor the the ‘strategy’ component out of this method, so different strategies can be swapped in and out
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/upwords/player.rb', line 135 def cpu_move(board, dict, batch_size = 1000, min_score = 0) possible_moves = self.legal_move_shapes_letter_permutations(board) possible_moves.shuffle! top_score = 0 top_score_move = nil while top_score_move.nil? || (top_score < min_score) do # Check if next batch contains any legal moves and save the top score ([batch_size, possible_moves.size].min).times do move_arr = possible_moves.pop move = Move.new(move_arr) if move.legal_words?(board, dict) move_score = move.score(board, self) if move_score >= top_score top_score = move_score top_score_move = move_arr end end end # Decrement minimum required score after each cycle to help prevent long searches min_score = [(min_score - 1), 0].max end top_score_move end |
#legal_move_shapes(board) ⇒ Object
Return a list of legal move shapes that player could make on board
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/upwords/player.rb', line 89 def legal_move_shapes(board) one_space_moves = board.coordinates.map {|posn| [posn]} # Collect board positions grouped by rows (0...board.num_rows).map do |row| (0...board.num_columns).map {|col| [row, col]} # Collect all positions of all possible horizontal multi-position moves that player could make end.flat_map do |posns| (2..(letters.size)).flat_map {|sz| posns.combination(sz).to_a} # Collect all positions of all possible vertical and horizontal moves that player could make end.reduce(one_space_moves) do |all_moves, move| all_moves << move # Horizontal moves all_moves << move.map {|posn| posn.rotate} # Vertical moves # Filter out illegal move shapes end.select do |move_posns| Shape.new(move_posns).legal?(board) end end |
#legal_move_shapes_letter_permutations(board) ⇒ Object
Return list of all possible letter permutations on legal move shapes that player could make on board Elements in the list will be in form of [(row, col), letter]
113 114 115 116 117 118 119 120 121 122 |
# File 'lib/upwords/player.rb', line 113 def legal_move_shapes_letter_permutations(board) # Cache result of letter permutation computation for each move size letter_perms = Hash.new {|perms, sz| perms[sz] = letters.permutation(sz).to_a} legal_move_shapes(board).reduce([]) do |all_moves, move| letter_perms[move.size].reduce(all_moves) do |move_perms, perm| move_perms << move.zip(perm) end end end |
#letters ⇒ Object
18 19 20 |
# File 'lib/upwords/player.rb', line 18 def letters @rack.letters.dup end |
#play_letter(board, letter, row, col) ⇒ Object
54 55 56 57 58 59 60 61 62 |
# File 'lib/upwords/player.rb', line 54 def play_letter(board, letter, row, col) rack_letter = @rack.remove(letter) begin board.play_letter(rack_letter, row, col) rescue IllegalMove => exn take_letter(rack_letter) raise IllegalMove, exn end end |
#rack_capacity ⇒ Object
34 35 36 |
# File 'lib/upwords/player.rb', line 34 def rack_capacity @rack.capacity end |
#rack_empty? ⇒ Boolean
30 31 32 |
# File 'lib/upwords/player.rb', line 30 def rack_empty? @rack.empty? end |
#rack_full? ⇒ Boolean
26 27 28 |
# File 'lib/upwords/player.rb', line 26 def rack_full? @rack.full? end |
#refill_rack(letter_bank) ⇒ Object
74 75 76 77 78 |
# File 'lib/upwords/player.rb', line 74 def refill_rack(letter_bank) until rack_full? || letter_bank.empty? do take_letter(letter_bank.draw) end end |
#show_rack(masked = false) ⇒ Object
22 23 24 |
# File 'lib/upwords/player.rb', line 22 def show_rack(masked = false) masked ? @rack.show_masked : @rack.show end |
#swap_letter(letter, letter_bank) ⇒ Object
64 65 66 67 68 69 70 71 72 |
# File 'lib/upwords/player.rb', line 64 def swap_letter(letter, letter_bank) if letter_bank.empty? raise IllegalMove, "Letter bank is empty!" else trade_letter = @rack.remove(letter) take_letter(letter_bank.draw) letter_bank.deposit(trade_letter) end end |
#take_from(board, row, col) ⇒ Object
Game object interaction methods
46 47 48 49 50 51 52 |
# File 'lib/upwords/player.rb', line 46 def take_from(board, row, col) if board.stack_height(row, col) == 0 raise IllegalMove, "No letters in #{row}, #{col}!" else take_letter(board.remove_top_letter(row, col)) end end |
#take_letter(letter) ⇒ Object
38 39 40 |
# File 'lib/upwords/player.rb', line 38 def take_letter(letter) @rack.add(letter) end |