reachy 1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|