Class: Upwords::Game

Inherits:
Object
  • Object
show all
Defined in:
lib/upwords/game.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(display_on = true, max_players = 4) ⇒ Game

Returns a new instance of Game.



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/upwords/game.rb', line 5

def initialize(display_on = true, max_players = 4)
  @max_players = max_players
  @display_on = display_on
  @board = Board.new(10, 5)
  @letter_bank = LetterBank.new(ALL_LETTERS.dup)
  @cursor = Cursor.new(@board.num_rows,
                       @board.num_columns,
                       *@board.middle_square[0])
  @dict = Dictionary.import(OSPD_FILE)
  @moves = MoveManager.new(@board, @dict)
  @players = []
  @running = false
  @submitted = false
end

Instance Attribute Details

#boardObject (readonly)

Returns the value of attribute board.



3
4
5
# File 'lib/upwords/game.rb', line 3

def board
  @board
end

#cursorObject (readonly)

Returns the value of attribute cursor.



3
4
5
# File 'lib/upwords/game.rb', line 3

def cursor
  @cursor
end

#playersObject (readonly)

Returns the value of attribute players.



3
4
5
# File 'lib/upwords/game.rb', line 3

def players
  @players
end

Instance Method Details

#add_player(name = nil, cpu = false) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/upwords/game.rb', line 36

def add_player(name = nil, cpu = false)
  if player_count >= max_players
    raise StandardError, "No more players can join"
  else
    if name.nil? || name.size == 0
      name = "Player #{player_count + 1}" 
    end
    @players << Player.new(name, rack_capacity=7, cpu)
  end
  @players.each {|p| p.refill_rack(@letter_bank) }
end

#add_players(player_names = nil) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/upwords/game.rb', line 48

def add_players(player_names = nil)
  print "\n"
  num_players = 0

  # Select how many players will be in the game
  # TODO: Add a command-line flag to allow players to skip this step
  until (1..@max_players).include?(num_players) do
    print "How many players will play? (1-#{@max_players})\n"
    num_players = gets.chomp.to_i
    print "\n"
    if !(1..@max_players).include?(num_players)
      print "Invalid selection: #{num_players}\n\n"
    end
  end

  # Name each player and choose if they are humans or computers
  # TODO: Add a command-line flag to set this
  (1..num_players).each do |idx|
    print "What is Player #{idx}'s name?\n"
    name = gets.chomp
    print "Is Player #{idx} or a computer? (y/n)\n"
    cpu = gets.chomp
    add_player(name, cpu.upcase == "Y")
    print "\n"
  end
end

#clear_messageObject



106
107
108
# File 'lib/upwords/game.rb', line 106

def clear_message
  update_message standard_message
end

#confirm_action?(question_text) ⇒ Boolean

Returns:

  • (Boolean)


231
232
233
234
235
236
237
238
239
240
# File 'lib/upwords/game.rb', line 231

def confirm_action?(question_text)
  if display_on?
    update_message "#{question_text} (y/n)"
    inp = @win.getch
    clear_message
    inp == 'y' || inp == 'Y'
  else
    true # Never ask for confirm if the display_on is off
  end
end

#current_playerObject

Player Methods



24
25
26
# File 'lib/upwords/game.rb', line 24

def current_player
  @players.first
end

#display_on?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/upwords/game.rb', line 89

def display_on?
  @display_on
end

#exit_game(need_confirm = true) ⇒ Object



291
292
293
294
295
296
# File 'lib/upwords/game.rb', line 291

def exit_game(need_confirm=true)
  if !need_confirm || (confirm_action? "Are you sure you want to exit the game?")
    @running = false
    @win.close if display_on?
  end
end

#game_overObject



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/upwords/game.rb', line 302

def game_over
  update_message "The game is over. Press any key to continue to see who won..."
  @win.getch if display_on?

  # Subtract 5 points for each tile remaining
  @players.each do |p|
    p.score -= p.letters.size * 5
  end

  top_score = @players.map {|p| p.score}.max
  winners = @players.select{|p| p.score == top_score}.map{|p| p.name}

  if winners.size == 1 
    update_message "And the winner is... #{winners[0]} with #{top_score} points!"
  else
    update_message "We have a tie! #{winners.join(', ')} all win with #{top_score} points!"
  end

  @win.getch if display_on?
  exit_game(need_confirm=false)
end

#init_windowObject

Graphics Methods



79
80
81
82
83
84
85
86
87
# File 'lib/upwords/game.rb', line 79

def init_window
  Curses.noecho
  Curses.curs_set(0) 
  Curses.init_screen
  # Curses.start_color

  @win = Graphics.new(self)
  @win.keypad(true)
end

#max_playersObject



28
29
30
# File 'lib/upwords/game.rb', line 28

def max_players
  @max_players
end

#modify_letter_input(letter) ⇒ Object

Capitalize letters, and convert ‘Q’ and ‘q’ to ‘Qu’



223
224
225
226
227
228
229
# File 'lib/upwords/game.rb', line 223

def modify_letter_input(letter)
  if letter =~ /[Qq]/
    'Qu'
  else
    letter.capitalize
  end
end

#next_turnObject



211
212
213
214
215
216
# File 'lib/upwords/game.rb', line 211

def next_turn
  @players.rotate!
  @win.hide_rack if display_on?
  clear_message
  @submitted = false
end

#pending_resultObject



114
115
116
117
118
119
120
121
122
# File 'lib/upwords/game.rb', line 114

def pending_result
  new_words = @moves.pending_words

  unless new_words.empty?
    new_words.map do |w|
      "#{w} (#{w.score})"
    end.join(", ") + " (Total = #{@moves.pending_score(current_player)})"
  end
end

#player_countObject



32
33
34
# File 'lib/upwords/game.rb', line 32

def player_count
  player_count = @players.size
end

#read_input(key) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/upwords/game.rb', line 183

def read_input(key)
  case key
  when 'Q'
    exit_game
  when SPACE
    toggle_rack_visibility
  when DELETE
    undo_moves
  when ENTER
    submit_moves
  when Curses::KEY_UP
    @cursor.up
  when Curses::KEY_DOWN
    @cursor.down
  when Curses::KEY_LEFT
    @cursor.left
  when Curses::KEY_RIGHT
    @cursor.right
  when '+'
    swap_letter
  when '-'
    skip_turn
  when /[[:alpha:]]/
    @moves.add(current_player, modify_letter_input(key), @cursor.y, @cursor.x)
    clear_message
  end
end

#refresh_graphicsObject



93
94
95
96
97
# File 'lib/upwords/game.rb', line 93

def refresh_graphics
  if display_on? && running?
    @win.refresh
  end
end

#runObject



132
133
134
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/upwords/game.rb', line 132

def run
  @running = true
  
  # Add players
  add_players

  # Start graphics
  init_window if @display_on
  clear_message

  # Start main loop
  while running? do
    begin
      # ------ CPU MOVE --------
      if current_player.cpu?
        update_message "#{current_player.name} is thinking..."
        cpu_move = current_player.cpu_move(@board, @dict, 50, 10)
        
        if !cpu_move.nil?
          cpu_move.each do |posn, letter|
            @moves.add(current_player, letter, *posn)
          end
          submit_moves(need_confirm=false)
        else
          skip_turn(need_confirm=false)
        end
      else
        read_input(@win.getch)
      end

      if @submitted
        # TODO: remove magic string from last move message
        if @players.all? {|p| p.last_turn == "skipped turn"} || @letter_bank.empty? && current_player.rack_empty?
          game_over
          @running = false
        else    
          next_turn
        end
      end
      
      refresh_graphics
      
    rescue IllegalMove => exception
      update_message "#{exception.message} (press any key to continue...)"
      @win.getch if display_on?
      clear_message
    end
  end
  
end

#running?Boolean

Game Loops & Non-Input Procedures

Returns:

  • (Boolean)


128
129
130
# File 'lib/upwords/game.rb', line 128

def running?
  @running
end

#skip_turn(need_confirm = true) ⇒ Object



281
282
283
284
285
286
287
288
289
# File 'lib/upwords/game.rb', line 281

def skip_turn(need_confirm=true)
  if !need_confirm || (confirm_action? "Are you sure you want to skip your turn?")
    @moves.undo_all(current_player)
    @submitted = true

    # TODO: remove magic string from last move message
    current_player.last_turn = "skipped turn"
  end
end

#standard_messageObject



110
111
112
# File 'lib/upwords/game.rb', line 110

def standard_message
  "#{current_player.name}'s pending words: #{pending_result}"
end

#submit_moves(need_confirm = true) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
# File 'lib/upwords/game.rb', line 251

def submit_moves(need_confirm=true)
  if !need_confirm || (confirm_action? "Are you sure you want to submit?")
    @moves.submit(current_player)
    current_player.refill_rack(@letter_bank)
    @submitted = true
    clear_message

    # TODO: remove magic string from last move message
    current_player.last_turn = "played word"
  end
end

#swap_letter(need_confirm = true) ⇒ Object

TODO: Test this method…



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/upwords/game.rb', line 264

def swap_letter(need_confirm=true)
  update_message "Pick a letter to swap... "
  letter = @win.getch

  if letter =~ /[[:alpha:]]/
    letter = modify_letter_input(letter)
    if !need_confirm || (confirm_action? "Swap '#{letter}' for another?")
      @moves.undo_all(current_player)
      current_player.swap_letter(letter, @letter_bank)
      @submitted = true

      # TODO: remove magic string from last move message
      current_player.last_turn = "swapped letter"
    end
  end
end

#toggle_rack_visibilityObject



298
299
300
# File 'lib/upwords/game.rb', line 298

def toggle_rack_visibility 
  @win.toggle_rack_visibility
end

#undo_movesObject

Game Procedures Bound to some Key Input



246
247
248
249
# File 'lib/upwords/game.rb', line 246

def undo_moves
  @moves.undo_last(current_player)
  clear_message
end

#update_message(msg) ⇒ Object



99
100
101
102
103
104
# File 'lib/upwords/game.rb', line 99

def update_message msg
  if display_on?
    @win.message = msg
    refresh_graphics
  end
end