reachy 1.1
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.
- checksums.yaml +7 -0
- data/bin/reachy +5 -0
- data/lib/banner +9 -0
- data/lib/reachy/defines.rb +23 -0
- data/lib/reachy/game.rb +222 -0
- data/lib/reachy/game_menu.rb +228 -0
- data/lib/reachy/main_menu.rb +192 -0
- data/lib/reachy/round.rb +242 -0
- data/lib/reachy/scoretable.rb +169 -0
- data/lib/reachy/scoring.rb +86 -0
- data/lib/reachy/util.rb +156 -0
- data/lib/reachy.rb +32 -0
- data/tests/test_reachy.rb +21 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 08b598e9df22e627af1198a45436f0883644347b
|
4
|
+
data.tar.gz: c2005872367ba2345d1462c0a3bba7c046003cee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b7cd060809d5ef86258ca046328fd40cfe3ea75dbb08da932aa38eb1a842192f3e3f615b74a061ad80e613fe9169b33869c37abc79b62308381691ec2f4044ac
|
7
|
+
data.tar.gz: f8dc5d0ee3560fb31ee171e4954dfb9af7901f83164190e48228835b17df858b66ca1a815027d3de3e66907f67320645855ae615d6c474a4dc5518cd21c86944
|
data/bin/reachy
ADDED
data/lib/banner
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
############################################
|
2
|
+
### RIICHI SCORE TRACKER ###
|
3
|
+
############################################
|
4
|
+
By: Thao Truong (Kainu) & Joshua Tang
|
5
|
+
Last Updated: 15 Jul 2016
|
6
|
+
|
7
|
+
A demo program that helps you keep track of
|
8
|
+
Japanese mahjong (Riichi) score as you play.
|
9
|
+
____________________________________________
|
@@ -0,0 +1,23 @@
|
|
1
|
+
##############################################
|
2
|
+
# Reachy constants
|
3
|
+
##############################################
|
4
|
+
module Reachy
|
5
|
+
|
6
|
+
# Scoreboard formatting
|
7
|
+
COL_SPACING = 15
|
8
|
+
|
9
|
+
# Round result types
|
10
|
+
T_TSUMO = 1
|
11
|
+
T_RON = 2
|
12
|
+
T_TENPAI = 3
|
13
|
+
T_NOTEN = 4
|
14
|
+
T_CHOMBO = 5
|
15
|
+
|
16
|
+
# List of named hand values
|
17
|
+
L_HANDS = ["mangan","haneman","baiman","sanbaiman","yakuman"]
|
18
|
+
|
19
|
+
# Special characters
|
20
|
+
L_NEWLINE = ["\r","\n","\r\n"]
|
21
|
+
SIGINT_CH = "\u0003"
|
22
|
+
EOF_CH = "\u0004"
|
23
|
+
end
|
data/lib/reachy/game.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'date'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
require_relative 'round'
|
6
|
+
|
7
|
+
##############################################
|
8
|
+
# Scoreboard record class
|
9
|
+
##############################################
|
10
|
+
module Reachy
|
11
|
+
class Game
|
12
|
+
|
13
|
+
attr_reader :filename # Name of JSON file
|
14
|
+
attr_reader :created_at # Timestamp at creation
|
15
|
+
attr_accessor :last_updated # Timestamp at last updated
|
16
|
+
attr_reader :mode # Game mode
|
17
|
+
attr_reader :players # List of player names
|
18
|
+
attr_accessor :scoreboard # List of round records
|
19
|
+
|
20
|
+
# Initialize game from given hash
|
21
|
+
# Param: filename - name of json file
|
22
|
+
# ondisk - whether Game object exists on disk
|
23
|
+
# players - array of player names associatied with this game
|
24
|
+
def initialize(filename, ondisk=true, players=[])
|
25
|
+
if ondisk
|
26
|
+
# Game object exists on disk. Read it from file.
|
27
|
+
self.read_data(filename)
|
28
|
+
else
|
29
|
+
# Create new Game object with starting values.
|
30
|
+
@filename = filename
|
31
|
+
@created_at = DateTime.now
|
32
|
+
@last_updated = DateTime.now
|
33
|
+
@mode = players.length
|
34
|
+
@players = players
|
35
|
+
self.initialize_scoreboard
|
36
|
+
self.write_data
|
37
|
+
end
|
38
|
+
@plist = @players.map {|x| x.downcase}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Populate @scoreboard with starting Round objects
|
42
|
+
def initialize_scoreboard
|
43
|
+
# Make initial scores e.g. { "joshua" => 35000, "kenta" => 35000, "thao" => 35000 }
|
44
|
+
start_score = mode == 3 ? Scoring::P_START_3 : Scoring::P_START_4
|
45
|
+
init_scores = Hash[ @players.map{ |p| [p.downcase, start_score] } ]
|
46
|
+
init_round = {"name" => "",
|
47
|
+
"wind" => nil,
|
48
|
+
"number" => 0,
|
49
|
+
"bonus" => 0,
|
50
|
+
"riichi" => 0,
|
51
|
+
"scores" => init_scores}
|
52
|
+
@scoreboard = [Round.new(init_round)]
|
53
|
+
self.clone_last_round(true)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Populate Game object with info from hash
|
57
|
+
# Param: db - hash of game data
|
58
|
+
def populate(db)
|
59
|
+
@filename = db["filename"]
|
60
|
+
@created_at = DateTime.parse(db["created_at"])
|
61
|
+
@last_updated = DateTime.parse(db["last_updated"])
|
62
|
+
@mode = db["mode"]
|
63
|
+
@players = db["players"]
|
64
|
+
@scoreboard = []
|
65
|
+
db["scoreboard"].each do |round|
|
66
|
+
@scoreboard << Round.new(round)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return Hash object representing Game object
|
71
|
+
def to_h
|
72
|
+
hash = {}
|
73
|
+
self.instance_variables.each do |var|
|
74
|
+
hash[var.to_s[1..-1]] = self.instance_variable_get(var)
|
75
|
+
end
|
76
|
+
hash["scoreboard"] = []
|
77
|
+
@scoreboard.each do |r|
|
78
|
+
hash["scoreboard"] << r.to_h
|
79
|
+
end
|
80
|
+
hash.delete("plist")
|
81
|
+
return hash
|
82
|
+
end
|
83
|
+
|
84
|
+
# Read JSON database file and repopulate object
|
85
|
+
def read_data(filename)
|
86
|
+
filepath = File.expand_path("../../../data/" + filename, __FILE__)
|
87
|
+
file = File.read(filepath)
|
88
|
+
db = JSON.parse(file)
|
89
|
+
self.populate(db)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Write Game object to JSON database file
|
93
|
+
def write_data
|
94
|
+
@last_updated = DateTime.now
|
95
|
+
hash = self.to_h
|
96
|
+
filepath = File.expand_path("../../../data/" + @filename, __FILE__)
|
97
|
+
File.open(filepath, "w") do |f|
|
98
|
+
f.write(JSON.pretty_generate(hash))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add new round result
|
103
|
+
def add_round(type, dealer, winner, loser, hand)
|
104
|
+
if not @scoreboard.last.update_round(type, dealer, winner, loser, hand)
|
105
|
+
printf " An error occurred while updating round score.\n" \
|
106
|
+
" Please check your input (winner, hand) and try again.\n\n"
|
107
|
+
return
|
108
|
+
end
|
109
|
+
self.clone_last_round
|
110
|
+
self.write_data
|
111
|
+
end
|
112
|
+
|
113
|
+
# Clone last round as next round and add to scoreboard
|
114
|
+
# Param: to_next - bool indicating whether to move to next round (optional)
|
115
|
+
def clone_last_round(to_next=false)
|
116
|
+
new_round = @scoreboard.last.clone
|
117
|
+
if (to_next || new_round.name == "") then new_round.next_round end
|
118
|
+
new_round.update_name
|
119
|
+
@scoreboard << new_round
|
120
|
+
end
|
121
|
+
|
122
|
+
# Remove latest round from scoreboard
|
123
|
+
def remove_last_round
|
124
|
+
if @scoreboard.length > 2
|
125
|
+
@scoreboard.pop
|
126
|
+
@scoreboard.pop
|
127
|
+
self.clone_last_round
|
128
|
+
self.write_data
|
129
|
+
else
|
130
|
+
printf "Error: Current game already in initial state. " \
|
131
|
+
"No round deleted.\n"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Move data file of this game to trash
|
136
|
+
def delete_from_disk
|
137
|
+
FileUtils.mv(File.expand_path("../../../data/" + @filename, __FILE__),
|
138
|
+
File.expand_path("../../../data/trash/" + @filename, __FILE__))
|
139
|
+
end
|
140
|
+
|
141
|
+
# Validate players input
|
142
|
+
# Param: players - list of players to check
|
143
|
+
# Return: true if all players in list are in this game, else false
|
144
|
+
def validate_players(players)
|
145
|
+
flag = true
|
146
|
+
players.each do |p|
|
147
|
+
if not @plist.include?(p)
|
148
|
+
printf "Error: Player \"%s\" not in current list of players\n", p
|
149
|
+
flag = false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
return flag
|
153
|
+
end
|
154
|
+
|
155
|
+
# Add riichi stick declared by player
|
156
|
+
# Param: player - string of player's name
|
157
|
+
# Return: true if successful, else false
|
158
|
+
def add_riichi(player)
|
159
|
+
if @players.map(&:downcase).include? player
|
160
|
+
ret = @scoreboard.last.add_riichi(player)
|
161
|
+
if ret then self.write_data end
|
162
|
+
return ret
|
163
|
+
else
|
164
|
+
printf "Error: \"%s\" not in current game's players list\n", player
|
165
|
+
return false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Print scoreboard header
|
170
|
+
# Round Joshua Kenta Thao
|
171
|
+
def print_header
|
172
|
+
#maxlen = [@players.max_by(&:length).length, 5].max
|
173
|
+
printf "%-#{COL_SPACING}s", "Round"
|
174
|
+
@players.each do |p|
|
175
|
+
printf "%-#{COL_SPACING}s", p
|
176
|
+
end
|
177
|
+
puts nil
|
178
|
+
end
|
179
|
+
|
180
|
+
# Print entire scoreboard
|
181
|
+
def print_scoreboard
|
182
|
+
self.print_title
|
183
|
+
(COL_SPACING * (@mode+1)).times { printf "-" }
|
184
|
+
puts nil
|
185
|
+
self.print_header
|
186
|
+
@scoreboard[0].print_scores(nil) # print init scores
|
187
|
+
@scoreboard[1..-2].each.with_index(1) do |curr,i|
|
188
|
+
delta = {}
|
189
|
+
prev = @scoreboard[i-1]
|
190
|
+
prev.scores.each do |k,v|
|
191
|
+
delta[k] = (curr.scores[k]-prev.scores[k]).to_f/1000
|
192
|
+
end
|
193
|
+
curr.print_scores(delta)
|
194
|
+
end
|
195
|
+
# print ongoing round
|
196
|
+
@scoreboard.last.print_scores(nil,true)
|
197
|
+
puts nil
|
198
|
+
end
|
199
|
+
|
200
|
+
# Print last round scores
|
201
|
+
# Round Joshua Kenta Thao
|
202
|
+
# E1B1R2 33400 39800 31800
|
203
|
+
def print_last_round
|
204
|
+
self.print_header
|
205
|
+
@scoreboard[-2].print_scores(nil)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Print 1-line game title
|
209
|
+
# game1: 3P (E1B1R1) ~ Joshua, Kenta, Thao ~ 2016-11-05T14:05:00-07:00
|
210
|
+
def print_title
|
211
|
+
printf "%s: %dP (%s) ~ %s ~ %s", @filename, @mode, @scoreboard[-2].name,
|
212
|
+
@players.join(", "), @last_updated
|
213
|
+
puts nil
|
214
|
+
end
|
215
|
+
|
216
|
+
# Print current round sticks
|
217
|
+
def print_current_sticks
|
218
|
+
@scoreboard.last.print_sticks
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require_relative 'game'
|
2
|
+
require_relative 'round'
|
3
|
+
require_relative 'util'
|
4
|
+
|
5
|
+
##############################################
|
6
|
+
# Game menu and specific game interactions
|
7
|
+
##############################################
|
8
|
+
module Reachy
|
9
|
+
|
10
|
+
# Game menu for a particular game
|
11
|
+
def self.game_menu
|
12
|
+
loop do
|
13
|
+
game = @games[@selected_game_index]
|
14
|
+
puts "(Enter \"x\" to go back to main menu.)\n"
|
15
|
+
puts nil
|
16
|
+
printf "*** Game \"%s\" Options:\n" \
|
17
|
+
" 1) Add next round result\n" \
|
18
|
+
" 2) Declare riichi\n" \
|
19
|
+
" 3) View current scoreboard\n" \
|
20
|
+
" 4) Remove last round entry\n" \
|
21
|
+
" 5) Delete current game\n" \
|
22
|
+
" 6) Choose a different game\n" \
|
23
|
+
" 7) Add new game\n", game.filename
|
24
|
+
choice = prompt_ch "---> Enter your choice: "
|
25
|
+
puts nil
|
26
|
+
case choice
|
27
|
+
when "x"
|
28
|
+
puts nil
|
29
|
+
return # to main menu
|
30
|
+
when "1"
|
31
|
+
puts "\n[Add next round result]"
|
32
|
+
puts nil
|
33
|
+
add_round(game)
|
34
|
+
when "2"
|
35
|
+
puts "\n[Declare riichi]"
|
36
|
+
puts nil
|
37
|
+
declare_riichi(game)
|
38
|
+
when "3"
|
39
|
+
puts "\n[View current scoreboard]"
|
40
|
+
puts nil
|
41
|
+
game.print_scoreboard
|
42
|
+
puts "(Press any key to continue)"
|
43
|
+
STDIN.getch
|
44
|
+
when "4"
|
45
|
+
puts "\n[Remove last round entry]"
|
46
|
+
puts nil
|
47
|
+
remove_last_round(game)
|
48
|
+
when "5"
|
49
|
+
puts "\n[Delete current game]"
|
50
|
+
puts nil
|
51
|
+
return if confirm_delete(game) # main menu if current game deleted
|
52
|
+
when "6"
|
53
|
+
puts "\n[Choose a different game]"
|
54
|
+
puts nil
|
55
|
+
view_game
|
56
|
+
when "7"
|
57
|
+
puts "\n[Add new game]"
|
58
|
+
puts nil
|
59
|
+
add_game
|
60
|
+
when ""
|
61
|
+
puts "\nEnter a choice... >_>"
|
62
|
+
puts nil
|
63
|
+
else
|
64
|
+
printf "\nInvalid choice: %s\n", choice
|
65
|
+
puts nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Add a new round to the current game. Sub menu option 1.
|
71
|
+
def self.add_round(game)
|
72
|
+
puts "(Enter \"x\" to return to game options.)"
|
73
|
+
puts nil
|
74
|
+
dealer = prompt "---> Dealer's name: "
|
75
|
+
return if dealer == "x"
|
76
|
+
puts nil
|
77
|
+
|
78
|
+
loop do
|
79
|
+
printf "*** Round result type:\n" \
|
80
|
+
" 1) Tsumo\n" \
|
81
|
+
" 2) Ron\n" \
|
82
|
+
" 3) Tenpai\n" \
|
83
|
+
" 4) Noten\n" \
|
84
|
+
" 5) Chombo\n"
|
85
|
+
choice = prompt "---> Select round result: "
|
86
|
+
case choice
|
87
|
+
when "x"
|
88
|
+
puts nil
|
89
|
+
return
|
90
|
+
when "1"
|
91
|
+
# Tsumo
|
92
|
+
type = T_TSUMO
|
93
|
+
winner = prompt "---> Winner's name: "
|
94
|
+
return if winner == "x"
|
95
|
+
winner = winner.split
|
96
|
+
if winner.length > 1
|
97
|
+
puts " Assuming \"%s\" is the winner, ignoring remaining players.",
|
98
|
+
winner.first
|
99
|
+
winner = [winner.first]
|
100
|
+
end
|
101
|
+
next if not game.validate_players(winner)
|
102
|
+
|
103
|
+
hand = prompt "---> Hand value(s) (e.g. \"2 30\" or \"mangan\"): "
|
104
|
+
return if hand == "x"
|
105
|
+
hand = validate_hand(hand)
|
106
|
+
|
107
|
+
loser = [] # Round::update_round will set loser = all - winner
|
108
|
+
game.add_round(type, dealer, winner, loser, hand)
|
109
|
+
break
|
110
|
+
when "2"
|
111
|
+
# Ron
|
112
|
+
type = T_RON
|
113
|
+
puts nil
|
114
|
+
winner = prompt "---> Winner(s) (first winner gets bonus and riichi sticks): "
|
115
|
+
return if winner == "x"
|
116
|
+
winner = winner.split
|
117
|
+
next if not game.validate_players(winner)
|
118
|
+
|
119
|
+
loser = prompt "---> Player who dealt into winning hand(s): "
|
120
|
+
return if loser == "x"
|
121
|
+
loser = loser.split
|
122
|
+
if loser.length > 1
|
123
|
+
puts " Assuming \"%s\" is the player who dealt into winning hand.",
|
124
|
+
loser.first
|
125
|
+
loser = [loser.first]
|
126
|
+
end
|
127
|
+
next if not game.validate_players(loser)
|
128
|
+
if winner.include? loser.first
|
129
|
+
puts "Loser can't be a winner..."
|
130
|
+
next
|
131
|
+
end
|
132
|
+
|
133
|
+
hand = prompt "---> Hand value(s) (e.g. \"2 30 yakuman\" or \"mangan\"): "
|
134
|
+
puts nil
|
135
|
+
return if hand == "x"
|
136
|
+
hand = validate_hand(hand)
|
137
|
+
|
138
|
+
if hand.length != winner.length
|
139
|
+
printf "The number of winners and winning hands do not match. " \
|
140
|
+
"Please try again.\n\n"
|
141
|
+
end
|
142
|
+
game.add_round(type, dealer, winner, loser, hand)
|
143
|
+
break
|
144
|
+
when "3"
|
145
|
+
# Tenpai
|
146
|
+
type = T_TENPAI
|
147
|
+
winner = prompt "---> Player(s) in tenpai (separated by space): "
|
148
|
+
return if winner == "x"
|
149
|
+
winner = winner.split
|
150
|
+
next if not game.validate_players(winner)
|
151
|
+
|
152
|
+
loser = [] # Round::update_round will set losers = all - winners
|
153
|
+
hand = []
|
154
|
+
game.add_round(type, dealer, winner, loser, hand)
|
155
|
+
break
|
156
|
+
when "4"
|
157
|
+
# Noten
|
158
|
+
type = T_NOTEN
|
159
|
+
winner = []
|
160
|
+
loser = []
|
161
|
+
hand = []
|
162
|
+
game.add_round(type, dealer, winner, loser, hand)
|
163
|
+
break
|
164
|
+
when "5"
|
165
|
+
# Chombo
|
166
|
+
type = T_CHOMBO
|
167
|
+
loser = prompt "---> Player who chombo'd: "
|
168
|
+
return if loser == "x"
|
169
|
+
loser = loser.split
|
170
|
+
if loser.length > 1
|
171
|
+
puts " Assuming \"%s\" is the player who dealt into winning hand.",
|
172
|
+
loser.first
|
173
|
+
loser = [loser.first]
|
174
|
+
end
|
175
|
+
next if not game.validate_players(loser)
|
176
|
+
|
177
|
+
winner = [] # Round::update_round will set winners = all - loser
|
178
|
+
hand = []
|
179
|
+
game.add_round(type, dealer, winner, loser, hand)
|
180
|
+
break
|
181
|
+
when ""
|
182
|
+
puts "Enter a choice... >_>"
|
183
|
+
puts nil
|
184
|
+
else
|
185
|
+
printf "Invalid choice: %s\n", choice
|
186
|
+
puts nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
puts "*** Game scoreboard updated."
|
191
|
+
puts nil
|
192
|
+
game.print_scoreboard
|
193
|
+
end
|
194
|
+
|
195
|
+
# Update riichi sticks. Sub menu option 2.
|
196
|
+
def self.declare_riichi(game)
|
197
|
+
puts "(Enter \"x\" to return to game options.)"
|
198
|
+
puts nil
|
199
|
+
player = prompt "---> Player(s) who declared riichi: "
|
200
|
+
player = player.split
|
201
|
+
return if not game.validate_players(player)
|
202
|
+
|
203
|
+
player.each do |p|
|
204
|
+
if game.add_riichi(p)
|
205
|
+
printf "\n*** Riichi stick added by %s.\n", p
|
206
|
+
game.print_current_sticks
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Remove last round from scoreboard. Sub menu option 3.
|
212
|
+
def self.remove_last_round(game)
|
213
|
+
printf "---> Removing last round entry:\n"
|
214
|
+
game.print_last_round
|
215
|
+
conf = prompt " Are you sure? (y/N) "
|
216
|
+
if conf == "y"
|
217
|
+
game.remove_last_round
|
218
|
+
puts nil
|
219
|
+
puts "*** Game scoreboard updated."
|
220
|
+
puts nil
|
221
|
+
game.print_scoreboard
|
222
|
+
return true
|
223
|
+
else
|
224
|
+
puts "You changed your mind? Fine.\n\n"
|
225
|
+
return false
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require_relative 'game'
|
2
|
+
require_relative 'round'
|
3
|
+
require_relative 'util'
|
4
|
+
require_relative 'game_menu'
|
5
|
+
|
6
|
+
##############################################
|
7
|
+
# Main menu and top level interactions
|
8
|
+
##############################################
|
9
|
+
module Reachy
|
10
|
+
|
11
|
+
# Main menu
|
12
|
+
def self.main_menu
|
13
|
+
loop do
|
14
|
+
puts "*** Main menu:\n" \
|
15
|
+
" 1) View or update existing game scoreboard\n" \
|
16
|
+
" 2) Add new game\n" \
|
17
|
+
" 3) Delete existing game\n" \
|
18
|
+
" 4) Display all scoreboards"
|
19
|
+
choice = prompt_ch "---> Enter your choice: "
|
20
|
+
puts nil
|
21
|
+
case choice
|
22
|
+
when "1"
|
23
|
+
puts "\n[View or update existing game scoreboard]"
|
24
|
+
puts nil
|
25
|
+
if view_game then game_menu else puts nil end
|
26
|
+
when "2"
|
27
|
+
puts "\n[Add new game]"
|
28
|
+
puts nil
|
29
|
+
if add_game then game_menu else puts nil end
|
30
|
+
when "3"
|
31
|
+
puts "\n[Delete existing game]"
|
32
|
+
puts nil
|
33
|
+
delete_game
|
34
|
+
when "4"
|
35
|
+
puts "\n[Display all scoreboards]"
|
36
|
+
puts nil
|
37
|
+
display_all_scoreboards
|
38
|
+
when ""
|
39
|
+
puts "\nEnter a choice... >_>"
|
40
|
+
puts nil
|
41
|
+
else
|
42
|
+
printf "\nInvalid choice: %s\n", choice
|
43
|
+
puts nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Find number of prefix-match game choices
|
49
|
+
def self.choice_match
|
50
|
+
return (@games[@choice_buf.to_i] ? 1 : 0) if L_NEWLINE.include?(@choice_buf[-1])
|
51
|
+
l = (1..@games.length).map{|i| i if /\A#{@choice_buf}\d*\z/.match(i.to_s)}.compact
|
52
|
+
ret = l.length
|
53
|
+
return ret
|
54
|
+
end
|
55
|
+
|
56
|
+
# View/update an existing game. Main menu option 1.
|
57
|
+
def self.view_game
|
58
|
+
loop do
|
59
|
+
puts "(Enter \"x\" to go back to main menu.)"
|
60
|
+
puts nil
|
61
|
+
puts "*** Choose existing game:"
|
62
|
+
return if not display_all_games
|
63
|
+
|
64
|
+
@choice_buf = prompt_ch "---> Enter your choice: "
|
65
|
+
if @choice_buf == "x"
|
66
|
+
puts nil
|
67
|
+
return false # to main menu
|
68
|
+
elsif L_NEWLINE.include?(@choice_buf)
|
69
|
+
puts "\n\nEnter a choice... >_>"
|
70
|
+
puts nil
|
71
|
+
else
|
72
|
+
# Check if current input buffer represents a unique game
|
73
|
+
matches = choice_match
|
74
|
+
while matches > 1
|
75
|
+
@choice_buf += prompt_ch ""
|
76
|
+
matches = choice_match
|
77
|
+
end
|
78
|
+
puts nil
|
79
|
+
puts nil
|
80
|
+
if choice_match == 0
|
81
|
+
printf "Invalid choice: %s\n", @choice_buf
|
82
|
+
puts nil
|
83
|
+
else
|
84
|
+
c = @choice_buf.to_i
|
85
|
+
# Print scoreboard for this game
|
86
|
+
@games[c - 1].print_scoreboard
|
87
|
+
@selected_game_index = c - 1
|
88
|
+
return true # to main menu
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Add a game. Main menu option 2.
|
95
|
+
def self.add_game
|
96
|
+
puts "(Enter \"x\" to go back to previous menu.)"
|
97
|
+
puts nil
|
98
|
+
|
99
|
+
# Ask for unique game name.
|
100
|
+
unique = false
|
101
|
+
until unique do
|
102
|
+
name = prompt("---> Game name: ", false)
|
103
|
+
return false if name == "x"
|
104
|
+
next if name.length == 0
|
105
|
+
if not /\A\w+\z/.match(name)
|
106
|
+
printf "Please enter only alphanumeric characters and underscores.\n"
|
107
|
+
next
|
108
|
+
end
|
109
|
+
unique = true
|
110
|
+
@games.each do |game|
|
111
|
+
if game.filename == name
|
112
|
+
unique = false
|
113
|
+
printf "Already exists a game of name: %s!\n", name
|
114
|
+
break
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Ask for number of players
|
120
|
+
good = false
|
121
|
+
until good do
|
122
|
+
nump = prompt "---> Number of players (3 or 4): "
|
123
|
+
return false if nump == "x"
|
124
|
+
nump = nump.to_i
|
125
|
+
if nump == 3 or nump == 4
|
126
|
+
good = true
|
127
|
+
else
|
128
|
+
puts "Invalid number of players"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Ask for unique player handles
|
133
|
+
good = false
|
134
|
+
until good do
|
135
|
+
players = prompt "---> Player names (separated by spaces, in ESWN order): "
|
136
|
+
return false if nump == "x"
|
137
|
+
players = players.split
|
138
|
+
if players.length == nump and players.uniq.length == players.length
|
139
|
+
good = true
|
140
|
+
else
|
141
|
+
printf "Must input %d unique player handles\n", nump
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
newgame = Game.new(name, false, players)
|
146
|
+
|
147
|
+
# Add to @games array and go to its menu.
|
148
|
+
@games << newgame
|
149
|
+
puts "\n*** New game created! Scoreboard:"
|
150
|
+
puts nil
|
151
|
+
newgame.print_scoreboard
|
152
|
+
@selected_game_index = @games.length - 1 # last entry is the new game
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
# Delete a game. Main menu option 3.
|
157
|
+
def self.delete_game
|
158
|
+
loop do
|
159
|
+
puts "(Enter \"x\" to go back to main menu.)"
|
160
|
+
puts nil
|
161
|
+
puts "*** Choose existing game to delete:"
|
162
|
+
return if not display_all_games
|
163
|
+
choice = prompt "---> Enter your choice: "
|
164
|
+
case choice
|
165
|
+
when "x"
|
166
|
+
return # to main menu
|
167
|
+
when ""
|
168
|
+
puts "\nEnter a choice... >_>"
|
169
|
+
else
|
170
|
+
# Check that choice consists only of digits and within @games bounds
|
171
|
+
if /\A\d+\z/.match(choice) and choice.to_i <= @games.length and choice.to_i > 0
|
172
|
+
# Ask for confirmation
|
173
|
+
chosen_game = @games[choice.to_i - 1]
|
174
|
+
puts nil
|
175
|
+
confirm_delete(chosen_game)
|
176
|
+
return # to main menu
|
177
|
+
else
|
178
|
+
printf "Invalid choice: %s\n", choice
|
179
|
+
puts nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Display all scoreboards. Main menu option 4.
|
186
|
+
def self.display_all_scoreboards
|
187
|
+
@games.each do |game|
|
188
|
+
game.print_scoreboard
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|