Module: Reachy

Defined in:
lib/reachy.rb,
lib/reachy/game.rb,
lib/reachy/util.rb,
lib/reachy/round.rb,
lib/reachy/defines.rb,
lib/reachy/scoring.rb,
lib/reachy/game_menu.rb,
lib/reachy/main_menu.rb,
lib/reachy/scoretable.rb

Overview

Hash objects containing scoring tables

Defined Under Namespace

Modules: Scoring Classes: Game, Round

Constant Summary collapse

APP_NAME =

Path

"reachy"
ROOT_PATH =
Dir.home + "/." + APP_NAME + "/"
DATA_PATH =
ROOT_PATH + "data/"
TRASH_PATH =
DATA_PATH + "trash/"
COL_SPACING =

Scoreboard formatting

15
T_TSUMO =

Round result types

1
T_RON =
2
T_TENPAI =
3
T_NOTEN =
4
T_CHOMBO =
5
T_RESET =
6
L_HANDS =

List of named hand values

["mangan","haneman","baiman","sanbaiman","yakuman"]
L_NEWLINE =

Special characters

["\r","\n","\r\n"]
SIGINT_CH =
"\u0003"
EOF_CH =
"\u0004"
H_TSUMO =

Hash of tsumo scores

{
  "dealer" => {
    "han_1" => {
      "fu_20" => 400,
      "fu_30" => 500,
      "fu_40" => 700,
      "fu_50" => 800,
      "fu_60" => 1000,
      "fu_70" => 1200
    },
    "han_2" => {
      "fu_20" => 700,
      "fu_30" => 1000,
      "fu_40" => 1300,
      "fu_50" => 1600,
      "fu_60" => 2000,
      "fu_70" => 2300
    },
    "han_3" => {
      "fu_20" => 1300,
      "fu_25" => 1600,
      "fu_30" => 2000,
      "fu_40" => 2600,
      "fu_50" => 3200,
      "fu_60" => 3900
    },
    "han_4" => {
      "fu_20" => 2600,
      "fu_25" => 3200,
      "fu_30" => 3900
    },
    "mangan"          => 4000,
    "haneman"         => 6000,
    "baiman"          => 8000,
    "sanbaiman"       => 12000,
    "yakuman"         => 16000,
    #"double_yakuman"  => 32000
  },
  "nondealer" => {
    "han_1" => {
      "fu_20" => { "dealer" => 400, "nondealer" => 200 },
      "fu_30" => { "dealer" => 500, "nondealer" => 300 },
      "fu_40" => { "dealer" => 700, "nondealer" => 400 },
      "fu_50" => { "dealer" => 800, "nondealer" => 400 },
      "fu_60" => { "dealer" => 1000, "nondealer" => 500 },
      "fu_70" => { "dealer" => 1200, "nondealer" => 600 }
    },
    "han_2" => {
      "fu_20" => { "dealer" => 700, "nondealer" => 400 },
      "fu_30" => { "dealer" => 1000, "nondealer" => 500 },
      "fu_40" => { "dealer" => 1300, "nondealer" => 700 },
      "fu_50" => { "dealer" => 1600, "nondealer" => 800 },
      "fu_60" => { "dealer" => 2000, "nondealer" => 1000 },
      "fu_70" => { "dealer" => 2300, "nondealer" => 1200 }
    },
    "han_3" => {
      "fu_20" => { "dealer" => 1300, "nondealer" => 700 },
      "fu_25" => { "dealer" => 1600, "nondealer" => 800 },
      "fu_30" => { "dealer" => 2000, "nondealer" => 1000 },
      "fu_40" => { "dealer" => 2600, "nondealer" => 1300 },
      "fu_50" => { "dealer" => 3200, "nondealer" => 1600 },
      "fu_60" => { "dealer" => 3900, "nondealer" => 2000 }
    },
    "han_4" => {
      "fu_20" => { "dealer" => 2600, "nondealer" => 1300 },
      "fu_25" => { "dealer" => 3200, "nondealer" => 1600 },
      "fu_30" => { "dealer" => 3900, "nondealer" => 2000 }
    },
    "mangan"          => { "dealer" => 4000, "nondealer" => 2000 },
    "haneman"         => { "dealer" => 6000, "nondealer" => 3000 },
    "baiman"          => { "dealer" => 8000, "nondealer" => 4000 },
    "sanbaiman"       => { "dealer" => 12000, "nondealer" => 6000 },
    "yakuman"         => { "dealer" => 16000, "nondealer" => 8000 },
    #"double_yakuman"  => { "dealer" => 32000, "nondealer" => 16000 }
  }
}
H_RON =

Hash of ron scores

{
  "dealer" => {
    "han_1" => {
      "fu_20" => 1000,
      "fu_30" => 1500,
      "fu_40" => 2000,
      "fu_50" => 2400,
      "fu_60" => 2900,
      "fu_70" => 3400
    },
    "han_2" => {
      "fu_20" => 2000,
      "fu_25" => 2400,
      "fu_30" => 2900,
      "fu_40" => 3900,
      "fu_50" => 4800,
      "fu_60" => 5800,
      "fu_70" => 6800
    },
    "han_3" => {
      "fu_20" => 3900,
      "fu_25" => 4800,
      "fu_30" => 5800,
      "fu_40" => 7700,
      "fu_50" => 9600,
      "fu_60" => 11600
    },
    "han_4" => {
      "fu_20" => 7700,
      "fu_25" => 9600,
      "fu_30" => 11600
    },
    "mangan"          => 12000,
    "haneman"         => 18000,
    "baiman"          => 24000,
    "sanbaiman"       => 36000,
    "yakuman"         => 48000,
    #"double_yakuman"  => 96000
  },
  "nondealer" => {
    "han_1" => {
      "fu_20" => 700,
      "fu_30" => 1000,
      "fu_40" => 1300,
      "fu_50" => 1600,
      "fu_60" => 2000,
      "fu_70" => 2300
    },
    "han_2" => {
      "fu_20" => 1300,
      "fu_30" => 2000,
      "fu_25" => 1600,
      "fu_40" => 2600,
      "fu_50" => 3200,
      "fu_60" => 3900,
      "fu_70" => 4500
    },
    "han_3" => {
      "fu_20" => 2600,
      "fu_25" => 3200,
      "fu_30" => 3900,
      "fu_40" => 5200,
      "fu_50" => 6300,
      "fu_60" => 7700
    },
    "han_4" => {
      "fu_20" => 5200,
      "fu_25" => 6400,
      "fu_30" => 7700
    },
    "mangan"          => 8000,
    "haneman"         => 12000,
    "baiman"          => 16000,
    "sanbaiman"       => 24000,
    "yakuman"         => 32000,
    #"double_yakuman"  => 64000
  }
}
H_CHOMBO =

Hash of chombo scores

{
  "dealer" => 4000,
  "nondealer" => { "dealer" => 4000, "nondealer" => 2000 }
}

Class Method Summary collapse

Class Method Details

.add_gameObject

Add a game. Main menu option 2.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/reachy/main_menu.rb', line 95

def self.add_game
  puts "(Enter \"x\" to go back to previous menu.)"
  puts nil

  # Ask for unique game name.
  unique = false
  until unique do
    name = prompt("---> Game name: ", false)
    return false if name == "x"
    next if name.length == 0
    if not /\A\w+\z/.match(name)
      printf "Please enter only alphanumeric characters and underscores.\n"
      next
    end
    unique = true
    @games.each do |game|
      if game.filename == name
        unique = false
        printf "Already exists a game of name: %s!\n", name
        break
      end
    end
  end

  # Ask for number of players
  good = false
  until good do
    nump = prompt "---> Number of players (3 or 4): "
    return false if nump == "x"
    nump = nump.to_i
    if nump == 3 or nump == 4
      good = true
    else
      puts "Invalid number of players"
    end
  end

  # Ask for unique player handles
  good = false
  until good do
    players = prompt "---> Player names (separated by spaces, in ESWN order): "
    return false if nump == "x"
    players = players.split
    if players.length == nump and players.uniq.length == players.length
      good = true
    else
      printf "Must input %d unique player handles\n", nump
    end
  end

  newgame = Game.new(name, false, players)

  # Add to @games array and go to its menu.
  @games << newgame
  puts "\n*** New game created! Scoreboard:"
  puts nil
  newgame.print_scoreboard
  @selected_game_index = @games.length - 1 # last entry is the new game
  return true
end

.add_round(game) ⇒ Object

Add a new round to the current game. Sub menu option 1.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/reachy/game_menu.rb', line 71

def self.add_round(game)
  puts "(Enter \"x\" to return to game options.)"
  puts nil
  dealer = prompt "---> Dealer's name: "
  return if dealer == "x"
  puts nil

  loop do
    printf "*** Round result type:\n" \
      "  1) Tsumo\n" \
      "  2) Ron\n" \
      "  3) Tenpai\n" \
      "  4) Noten\n" \
      "  5) Chombo\n" \
      "  6) Round reset\n"
    choice = prompt "---> Select round result: "
    case choice
    when "x"
      puts nil
      return
    when "1"
      # Tsumo
      type = T_TSUMO
      winner = prompt "---> Winner's name: "
      return if winner == "x"
      winner = winner.split
      if winner.length > 1
        puts "  Assuming \"%s\" is the winner, ignoring remaining players.",
          winner.first
        winner = [winner.first]
      end
      next if not game.validate_players(winner)

      hand = prompt "---> Hand value (e.g. \"2 30\" or \"mangan\"): "
      return if hand == "x"
      hand = validate_hand(hand)

      loser = []  # Round::update_round will set loser = all - winner
      game.add_round(type, dealer, winner, loser, hand)
      break
    when "2"
      # Ron
      type = T_RON
      puts nil
      winner = prompt "---> Winner(s) (first winner gets bonus and riichi sticks): "
      return if winner == "x"
      winner = winner.split
      next if not game.validate_players(winner)

      hand = prompt "---> Hand value(s) (e.g. \"2 30 yakuman\" or \"mangan\"): "
      puts nil
      return if hand == "x"
      hand = validate_hand(hand)
      if hand.length != winner.length
        printf "The number of winners and winning hands do not match. " \
               "Please try again.\n\n"
      end

      loser = prompt "---> Player who dealt into winning hand(s): "
      return if loser == "x"
      loser = loser.split
      if loser.length > 1
        puts "  Assuming \"%s\" is the player who dealt into winning hand.",
          loser.first
        loser = [loser.first]
      end
      next if not game.validate_players(loser)
      if winner.include? loser.first
        puts "Loser can't be a winner..."
        next
      end

      game.add_round(type, dealer, winner, loser, hand)
      break
    when "3"
      # Tenpai
      type = T_TENPAI
      winner = prompt "---> Player(s) in tenpai (separated by space): "
      return if winner == "x"
      winner = winner.split
      next if not game.validate_players(winner)

      loser = []  # Round::update_round will set losers = all - winners
      hand = []
      game.add_round(type, dealer, winner, loser, hand)
      break
    when "4"
      # Noten
      type = T_NOTEN
      winner = []
      loser = []
      hand = []
      game.add_round(type, dealer, winner, loser, hand)
      break
    when "5"
      # Chombo
      type = T_CHOMBO
      loser = prompt "---> Player who chombo'd: "
      return if loser == "x"
      loser = loser.split
      if loser.length > 1
        puts "  Assuming \"%s\" is the player who dealt into winning hand.",
          loser.first
        loser = [loser.first]
      end
      next if not game.validate_players(loser)

      winner = [] # Round::update_round will set winners = all - loser
      hand = []
      game.add_round(type, dealer, winner, loser, hand)
      break
    when "6"
      # Round reset
      type = T_RESET
      winner = []
      loser = []
      hand = []
      game.add_round(type, dealer, winner, loser, hand)
      puts "*** Round reset."
      puts nil
      break
    when ""
      puts "Enter a choice... >_>"
      puts nil
    else
      printf "Invalid choice: %s\n", choice
      puts nil
    end
  end

  puts "*** Game scoreboard updated."
  puts nil
  game.print_scoreboard
end

.choice_matchObject

Find number of prefix-match game choices



49
50
51
52
53
54
# File 'lib/reachy/main_menu.rb', line 49

def self.choice_match
  return (@games[@choice_buf.to_i] ? 1 : 0) if L_NEWLINE.include?(@choice_buf[-1])
  l = (1..@games.length).map{|i| i if /\A#{@choice_buf}\d*\z/.match(i.to_s)}.compact
  ret = l.length
  return ret
end

.confirm_delete(chosen_game) ⇒ Object

Confirm game deletion



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/reachy/util.rb', line 67

def self.confirm_delete(chosen_game)
  printf "---> Deleting game \"%s\". This action cannot be undone.\n", chosen_game.filename
  conf = prompt "  Are you sure? (y/N) "
  if conf == "y"
    # Move associated json file to trash.
    chosen_game.delete_from_disk
    # Delete from @games array
    @games.delete(chosen_game)
    printf "*** Game \"%s\" deleted from database.\n\n", chosen_game.filename
    return true
  else
    puts "You changed your mind? Fine.\n\n"
    return false
  end
end

.cowsayObject

Call system cowsay if available



144
145
146
# File 'lib/reachy/util.rb', line 144

def self.cowsay
  system("cowsay Bye!") if system("which cowsay >/dev/null 2>&1")
end

.declare_riichi(game) ⇒ Object

Update riichi sticks. Sub menu option 2.



207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/reachy/game_menu.rb', line 207

def self.declare_riichi(game)
  puts "(Enter \"x\" to return to game options.)"
  puts nil
  player = prompt "---> Player(s) who declared riichi: "
  player = player.split
  return if not game.validate_players(player)

  player.each do |p|
    if game.add_riichi(p)
      printf "\n*** Riichi stick added by %s.\n", p
      game.print_current_sticks
    end
  end
end

.delete_gameObject

Delete a game. Main menu option 3.



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
182
183
# File 'lib/reachy/main_menu.rb', line 157

def self.delete_game
  loop do
    puts "(Enter \"x\" to go back to main menu.)"
    puts nil
    puts "*** Choose existing game to delete:"
    return if not display_all_games
    choice = prompt "---> Enter your choice: "
    case choice
    when "x"
      return # to main menu
    when ""
      puts "\nEnter a choice... >_>"
    else
      # Check that choice consists only of digits and within @games bounds
      if /\A\d+\z/.match(choice) and choice.to_i <= @games.length and choice.to_i > 0
        # Ask for confirmation
        chosen_game = @games[choice.to_i - 1]
        puts nil
        confirm_delete(chosen_game)
        return # to main menu
      else
        printf "Invalid choice: %s\n", choice
        puts nil
      end
    end
  end
end

.display_all_gamesObject

Print out all games in database



53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/reachy/util.rb', line 53

def self.display_all_games
  if @games.empty?
    puts "  No game currently in database. Please add a new game."
    puts nil
    return false
  end
  @games.each_with_index do |game, index|
    printf "  %d) ", index + 1
    game.print_title
  end
  return true
end

.display_all_scoreboardsObject

Display all scoreboards. Main menu option 4.



186
187
188
189
190
# File 'lib/reachy/main_menu.rb', line 186

def self.display_all_scoreboards
  @games.each do |game|
    game.print_scoreboard
  end
end

.display_all_winnersObject

Display winners of every game



132
133
134
135
136
137
138
139
140
141
# File 'lib/reachy/util.rb', line 132

def self.display_all_winners
  puts " Current winners:"
  puts " ----------------"
  @games.each do |game|
    high_score = game.scoreboard.last.scores.values.max
    winners = game.scoreboard.last.scores.select{ |player, score| score == high_score}
    printf "  * %s: %s - %d points\n", game.filename, winners.keys.join(", "), high_score
  end
  puts nil
end

.ensure_data_dirObject

Ensure that the data and data/trash directory exist, if not create them



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/reachy/util.rb', line 84

def self.ensure_data_dir
  if not File.directory?(ROOT_PATH)
    Dir.mkdir ROOT_PATH
  end
  if not File.directory?(DATA_PATH)
    Dir.mkdir DATA_PATH
  end
  if not File.directory?(TRASH_PATH)
    Dir.mkdir TRASH_PATH
  end
end

.game_menuObject

Game menu for a particular game



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/reachy/game_menu.rb', line 11

def self.game_menu
  loop do
    game = @games[@selected_game_index]
    puts "(Enter \"x\" to go back to main menu.)\n"
    puts nil
    printf "*** Game \"%s\" Options:\n" \
      "  1) Add next round result\n" \
      "  2) Declare riichi\n" \
      "  3) View current scoreboard\n" \
      "  4) Remove last round entry\n" \
      "  5) Delete current game\n" \
      "  6) Choose a different game\n" \
      "  7) Add new game\n", game.filename
    choice = prompt_ch "---> Enter your choice: "
    puts nil
    case choice
    when "x"
      puts nil
      return # to main menu
    when "1"
      puts "\n[Add next round result]"
      puts nil
      add_round(game)
    when "2"
      puts "\n[Declare riichi]"
      puts nil
      declare_riichi(game)
    when "3"
      puts "\n[View current scoreboard]"
      puts nil
      game.print_scoreboard
      puts "(Press any key to continue)"
      STDIN.getch
    when "4"
      puts "\n[Remove last round entry]"
      puts nil
      remove_last_round(game)
    when "5"
      puts "\n[Delete current game]"
      puts nil
      return if confirm_delete(game) # main menu if current game deleted
    when "6"
      puts "\n[Choose a different game]"
      puts nil
      view_game
    when "7"
      puts "\n[Add new game]"
      puts nil
      add_game
    when ""
      puts "\nEnter a choice... >_>"
      puts nil
    else
      printf "\nInvalid choice: %s\n", choice
      puts nil
    end
  end
end

.goodbyeObject

Message to print when quitting program



149
150
151
152
153
154
155
156
157
# File 'lib/reachy/util.rb', line 149

def self.goodbye
  puts "\n\n"
  printf "  -------------------------------\n" \
    "  |  Thanks for flying reachy!  |\n" \
    "  -------------------------------\n\n"
  display_all_winners
  cowsay
  exit 0
end

Main menu



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/reachy/main_menu.rb', line 12

def self.main_menu
  loop do
    puts "*** Main menu:\n" \
      "  1) View or update existing game scoreboard\n" \
      "  2) Add new game\n" \
      "  3) Delete existing game\n" \
      "  4) Display all scoreboards"
    choice = prompt_ch "---> Enter your choice: "
    puts nil
    case choice
    when "1"
      puts "\n[View or update existing game scoreboard]"
      puts nil
      if view_game then game_menu else puts nil end
    when "2"
      puts "\n[Add new game]"
      puts nil
      if add_game then game_menu else puts nil end
    when "3"
      puts "\n[Delete existing game]"
      puts nil
      delete_game
    when "4"
      puts "\n[Display all scoreboards]"
      puts nil
      display_all_scoreboards
    when ""
      puts "\nEnter a choice... >_>"
      puts nil
    else
      printf "\nInvalid choice: %s\n", choice
      puts nil
    end
  end
end

.prompt(message, downcase = true) ⇒ Object

Prompt for user input with a message. If EOF is entered, aborts program. Param: message - string to display

downcase - whether to downcase input

Return: User input Note: always strips input!



16
17
18
19
20
21
22
23
24
25
# File 'lib/reachy/util.rb', line 16

def self.prompt(message, downcase=true)
  print message
  input = $stdin.gets
  goodbye if !input
  if downcase
    return input.strip.downcase
  else
    return input.strip
  end
end

.prompt_ch(message) ⇒ Object

Prompt for one character, downcased. If EOF is entered, aborts program. Param: message - string to display Return: User input character, downcased



31
32
33
34
35
36
37
# File 'lib/reachy/util.rb', line 31

def self.prompt_ch(message)
  print message
  input = STDIN.getch
  goodbye if input == SIGINT_CH || input == EOF_CH
  print input
  return input.downcase
end

.read_all_gamesObject

Read all games in data dir, and store in @games array



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/reachy/util.rb', line 40

def self.read_all_games
  @games = []
  Dir.foreach(DATA_PATH) do |filename|
    # Skip . and .. dir entries, and trash dir
    next if filename == '.' or filename == '..' or filename == "trash"

    # Create game objects
    game = Game.new(filename)
    @games << game
  end
end

.remove_last_round(game) ⇒ Object

Remove last round from scoreboard. Sub menu option 3.



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/reachy/game_menu.rb', line 223

def self.remove_last_round(game)
  printf "---> Removing last round entry:\n"
  game.print_last_round
  conf = prompt "  Are you sure? (y/N) "
  if conf == "y"
    game.remove_last_round
    puts nil
    puts "*** Game scoreboard updated."
    puts nil
    game.print_scoreboard
    return true
  else
    puts "You changed your mind? Fine.\n\n"
    return false
  end
end

.start_screenObject

Display initial screen (complete with banner, list of games)



17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/reachy.rb', line 17

def self.start_screen
  # Display banner
  File.open(File.expand_path("../banner", __FILE__), "r"){ |file| puts file.read }
  puts nil

  # Display all games in db
  ensure_data_dir
  read_all_games
  puts "*** Current existing game(s) in database:"
  puts nil if display_all_games

  # Display main menu options
  main_menu
end

.validate_hand(hand) ⇒ Object

Validate hand input Param: hand - string of hand value input Return: reformated hand value or empty list if input invalid



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/reachy/util.rb', line 99

def self.validate_hand(hand)
  split_hand = hand.split
  hand = []
  i = 0
  flag = true
  while i < split_hand.length   # Did this C-style AKA imperatively.. how to ruby
    if split_hand[i].match(/^\d+$/)
      han = split_hand[i]
      fu = split_hand[i+1]
      if fu.match(/^\d+$/) && (fu.to_i==25 || fu.to_i%10 == 0)
        hand << [han.to_i, fu.to_i]
        i += 2
      else
        flag = false
        hand = []
        break
      end
    elsif L_HANDS.include?(split_hand[i])
      hand << [split_hand[i]]
      i += 1
    else
      flag = false
      hand = []
      break
    end
  end
  if not flag
    printf "Hand value malformed: \"%s\"\n", hand
  end
  return hand
end

.view_gameObject

View/update an existing game. Main menu option 1.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/reachy/main_menu.rb', line 57

def self.view_game
  loop do
    puts "(Enter \"x\" to go back to main menu.)"
    puts nil
    puts "*** Choose existing game:"
    return if not display_all_games

    @choice_buf = prompt_ch "---> Enter your choice: "
    if @choice_buf == "x"
      puts nil
      return false # to main menu
    elsif L_NEWLINE.include?(@choice_buf)
      puts "\n\nEnter a choice... >_>"
      puts nil
    else
      # Check if current input buffer represents a unique game
      matches = choice_match
      while matches > 1
        @choice_buf += prompt_ch ""
        matches = choice_match
      end
      puts nil
      puts nil
      if choice_match == 0
        printf "Invalid choice: %s\n", @choice_buf
        puts nil
      else
        c = @choice_buf.to_i
        # Print scoreboard for this game
        @games[c - 1].print_scoreboard
        @selected_game_index = c - 1
        return true # to main menu
      end
    end
  end
end