asterism-games 0.20.0 → 0.22.0
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 +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +67 -9
- data/VERSION +1 -1
- data/lib/asterism_games/commentate/assets/SOUNDS_LICENSE.txt +63 -0
- data/lib/asterism_games/commentate/assets/commentate.css +9 -4
- data/lib/asterism_games/commentate/assets/commentate.js +298 -7
- data/lib/asterism_games/commentate/assets/index.html +5 -3
- data/lib/asterism_games/commentate/assets/sounds/after_the_rain.mp3 +0 -0
- data/lib/asterism_games/commentate/assets/sounds/chi.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/discard.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/discard_other.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/draw.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/encounter.mp3 +0 -0
- data/lib/asterism_games/commentate/assets/sounds/fanfare1.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/fanfare2.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/fanfare3.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/fanfare4.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/kan.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/kyuushu.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/points.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/pon.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/riichi.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/ron.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/round.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/ryuukyoku.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/sounds.json +335 -0
- data/lib/asterism_games/commentate/assets/sounds/spring_thing.mp3 +0 -0
- data/lib/asterism_games/commentate/assets/sounds/tsumo.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/turn.wav +0 -0
- data/lib/asterism_games/commentate/assets/sounds/yaku.wav +0 -0
- data/lib/asterism_games/commentate/mahjong.rb +11 -4
- data/lib/asterism_games/commentate/mahjong_sounds.rb +118 -0
- data/lib/asterism_games/commentate/server.rb +14 -2
- data/lib/asterism_games/commentate.rb +8 -2
- data/lib/asterism_games/play/mahjong.rb +3 -1
- metadata +29 -8
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
{
|
|
2
|
+
"made_by": "fmruby-core tools/mahjong_sounds.rb (Family mruby's APU, components/apu_emu)",
|
|
3
|
+
"from": {
|
|
4
|
+
"flash/app/game/mahjong/mahjong_sound.rb": {
|
|
5
|
+
"SFX": "a8739fe35e6aa353a1bca456b26bd90f706ce1cbd0b43824f1531f890048e2bd"
|
|
6
|
+
},
|
|
7
|
+
"flash/usr/share/sounds/nsf/after_the_rain.nsf": "2d12f597da28207ae72d6ef82ceda6e7a0c32268d09e94328e499a9c6dff1023",
|
|
8
|
+
"flash/usr/share/sounds/nsf/spring_thing.nsf": "7541ff743d241752fa0b496b1f6ed7146526208c5a4b0d59cff8f8e8d55e45ac",
|
|
9
|
+
"flash/usr/share/sounds/nsf/encounter.nsf": "d11c34896b7a60a325e7296499e3b58bb4df04dec2d26bdd15bed178baf551a7"
|
|
10
|
+
},
|
|
11
|
+
"rate": 15720,
|
|
12
|
+
"bgm_levels": [
|
|
13
|
+
0.0,
|
|
14
|
+
0.3,
|
|
15
|
+
0.55,
|
|
16
|
+
1.0
|
|
17
|
+
],
|
|
18
|
+
"bgm_names": [
|
|
19
|
+
"切",
|
|
20
|
+
"小",
|
|
21
|
+
"中",
|
|
22
|
+
"大"
|
|
23
|
+
],
|
|
24
|
+
"bgm_default": 1,
|
|
25
|
+
"tense_left": 10,
|
|
26
|
+
"scenes": {
|
|
27
|
+
"lobby": "after_the_rain",
|
|
28
|
+
"play": "spring_thing",
|
|
29
|
+
"tense": "encounter"
|
|
30
|
+
},
|
|
31
|
+
"tunes": {
|
|
32
|
+
"after_the_rain": {
|
|
33
|
+
"file": "after_the_rain.mp3",
|
|
34
|
+
"nsf": "after_the_rain.nsf",
|
|
35
|
+
"track": 0,
|
|
36
|
+
"title": "8-bit Chiptune - After The Rain",
|
|
37
|
+
"author": "shiru8bit",
|
|
38
|
+
"license": "CC BY 3.0",
|
|
39
|
+
"intro_s": 0.0167,
|
|
40
|
+
"loop_s": 153.6,
|
|
41
|
+
"loop_start": 3.083333,
|
|
42
|
+
"loop_end": 156.683333,
|
|
43
|
+
"length_s": 157.6833
|
|
44
|
+
},
|
|
45
|
+
"spring_thing": {
|
|
46
|
+
"file": "spring_thing.mp3",
|
|
47
|
+
"nsf": "spring_thing.nsf",
|
|
48
|
+
"track": 0,
|
|
49
|
+
"title": "8-bit Chiptune - Spring Thing",
|
|
50
|
+
"author": "shiru8bit",
|
|
51
|
+
"license": "CC BY 3.0",
|
|
52
|
+
"intro_s": 1.9167,
|
|
53
|
+
"loop_s": 115.2,
|
|
54
|
+
"loop_start": 7.183333,
|
|
55
|
+
"loop_end": 122.383333,
|
|
56
|
+
"length_s": 123.3833
|
|
57
|
+
},
|
|
58
|
+
"encounter": {
|
|
59
|
+
"file": "encounter.mp3",
|
|
60
|
+
"nsf": "encounter.nsf",
|
|
61
|
+
"track": 0,
|
|
62
|
+
"title": "8-bit Chiptune - Encounter",
|
|
63
|
+
"author": "shiru8bit",
|
|
64
|
+
"license": "CC BY 3.0",
|
|
65
|
+
"intro_s": 0.0,
|
|
66
|
+
"loop_s": 128.0,
|
|
67
|
+
"loop_start": 1.116667,
|
|
68
|
+
"loop_end": 129.116667,
|
|
69
|
+
"length_s": 130.1167
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"effects": {
|
|
73
|
+
"discard": {
|
|
74
|
+
"file": "discard.wav",
|
|
75
|
+
"priority": 1,
|
|
76
|
+
"channels": [
|
|
77
|
+
3
|
|
78
|
+
],
|
|
79
|
+
"length_s": 0.083
|
|
80
|
+
},
|
|
81
|
+
"discard_other": {
|
|
82
|
+
"file": "discard_other.wav",
|
|
83
|
+
"priority": 1,
|
|
84
|
+
"channels": [
|
|
85
|
+
3
|
|
86
|
+
],
|
|
87
|
+
"length_s": 0.05
|
|
88
|
+
},
|
|
89
|
+
"draw": {
|
|
90
|
+
"file": "draw.wav",
|
|
91
|
+
"priority": 1,
|
|
92
|
+
"channels": [
|
|
93
|
+
1
|
|
94
|
+
],
|
|
95
|
+
"length_s": 0.083
|
|
96
|
+
},
|
|
97
|
+
"turn": {
|
|
98
|
+
"file": "turn.wav",
|
|
99
|
+
"priority": 2,
|
|
100
|
+
"channels": [
|
|
101
|
+
0
|
|
102
|
+
],
|
|
103
|
+
"length_s": 0.2
|
|
104
|
+
},
|
|
105
|
+
"round": {
|
|
106
|
+
"file": "round.wav",
|
|
107
|
+
"priority": 3,
|
|
108
|
+
"channels": [
|
|
109
|
+
0,
|
|
110
|
+
2
|
|
111
|
+
],
|
|
112
|
+
"length_s": 0.545
|
|
113
|
+
},
|
|
114
|
+
"yaku": {
|
|
115
|
+
"file": "yaku.wav",
|
|
116
|
+
"priority": 2,
|
|
117
|
+
"channels": [
|
|
118
|
+
0
|
|
119
|
+
],
|
|
120
|
+
"length_s": 0.117
|
|
121
|
+
},
|
|
122
|
+
"points": {
|
|
123
|
+
"file": "points.wav",
|
|
124
|
+
"priority": 3,
|
|
125
|
+
"channels": [
|
|
126
|
+
0,
|
|
127
|
+
1,
|
|
128
|
+
2
|
|
129
|
+
],
|
|
130
|
+
"length_s": 0.476
|
|
131
|
+
},
|
|
132
|
+
"riichi": {
|
|
133
|
+
"file": "riichi.wav",
|
|
134
|
+
"priority": 3,
|
|
135
|
+
"channels": [
|
|
136
|
+
0,
|
|
137
|
+
1
|
|
138
|
+
],
|
|
139
|
+
"length_s": 0.35
|
|
140
|
+
},
|
|
141
|
+
"pon": {
|
|
142
|
+
"file": "pon.wav",
|
|
143
|
+
"priority": 3,
|
|
144
|
+
"channels": [
|
|
145
|
+
0,
|
|
146
|
+
3
|
|
147
|
+
],
|
|
148
|
+
"length_s": 0.183
|
|
149
|
+
},
|
|
150
|
+
"chi": {
|
|
151
|
+
"file": "chi.wav",
|
|
152
|
+
"priority": 3,
|
|
153
|
+
"channels": [
|
|
154
|
+
0
|
|
155
|
+
],
|
|
156
|
+
"length_s": 0.167
|
|
157
|
+
},
|
|
158
|
+
"kan": {
|
|
159
|
+
"file": "kan.wav",
|
|
160
|
+
"priority": 3,
|
|
161
|
+
"channels": [
|
|
162
|
+
0,
|
|
163
|
+
2
|
|
164
|
+
],
|
|
165
|
+
"length_s": 0.374
|
|
166
|
+
},
|
|
167
|
+
"ron": {
|
|
168
|
+
"file": "ron.wav",
|
|
169
|
+
"priority": 3,
|
|
170
|
+
"channels": [
|
|
171
|
+
3,
|
|
172
|
+
0,
|
|
173
|
+
1,
|
|
174
|
+
2
|
|
175
|
+
],
|
|
176
|
+
"length_s": 0.599
|
|
177
|
+
},
|
|
178
|
+
"tsumo": {
|
|
179
|
+
"file": "tsumo.wav",
|
|
180
|
+
"priority": 3,
|
|
181
|
+
"channels": [
|
|
182
|
+
0,
|
|
183
|
+
1,
|
|
184
|
+
2
|
|
185
|
+
],
|
|
186
|
+
"length_s": 0.532
|
|
187
|
+
},
|
|
188
|
+
"kyuushu": {
|
|
189
|
+
"file": "kyuushu.wav",
|
|
190
|
+
"priority": 3,
|
|
191
|
+
"channels": [
|
|
192
|
+
0,
|
|
193
|
+
2
|
|
194
|
+
],
|
|
195
|
+
"length_s": 0.464
|
|
196
|
+
},
|
|
197
|
+
"ryuukyoku": {
|
|
198
|
+
"file": "ryuukyoku.wav",
|
|
199
|
+
"priority": 3,
|
|
200
|
+
"channels": [
|
|
201
|
+
0,
|
|
202
|
+
2
|
|
203
|
+
],
|
|
204
|
+
"length_s": 0.841
|
|
205
|
+
},
|
|
206
|
+
"fanfare1": {
|
|
207
|
+
"file": "fanfare1.wav",
|
|
208
|
+
"priority": 4,
|
|
209
|
+
"channels": [
|
|
210
|
+
0,
|
|
211
|
+
1,
|
|
212
|
+
2
|
|
213
|
+
],
|
|
214
|
+
"length_s": 1.188
|
|
215
|
+
},
|
|
216
|
+
"fanfare2": {
|
|
217
|
+
"file": "fanfare2.wav",
|
|
218
|
+
"priority": 4,
|
|
219
|
+
"channels": [
|
|
220
|
+
0,
|
|
221
|
+
1,
|
|
222
|
+
2
|
|
223
|
+
],
|
|
224
|
+
"length_s": 0.783
|
|
225
|
+
},
|
|
226
|
+
"fanfare3": {
|
|
227
|
+
"file": "fanfare3.wav",
|
|
228
|
+
"priority": 4,
|
|
229
|
+
"channels": [
|
|
230
|
+
0,
|
|
231
|
+
2
|
|
232
|
+
],
|
|
233
|
+
"length_s": 0.853
|
|
234
|
+
},
|
|
235
|
+
"fanfare4": {
|
|
236
|
+
"file": "fanfare4.wav",
|
|
237
|
+
"priority": 4,
|
|
238
|
+
"channels": [
|
|
239
|
+
0,
|
|
240
|
+
2
|
|
241
|
+
],
|
|
242
|
+
"length_s": 1.06
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"files": {
|
|
246
|
+
"after_the_rain.mp3": {
|
|
247
|
+
"sha256": "031f62d6d389cf972ca4d76d4f60eb39aaf81abfddb8cd9bfdadb6a9ee2d0c36",
|
|
248
|
+
"bytes": 1262208
|
|
249
|
+
},
|
|
250
|
+
"chi.wav": {
|
|
251
|
+
"sha256": "4dc17f9942b7b24c0516e63722edb0337b8a08878436c541a25b008afffd93db",
|
|
252
|
+
"bytes": 5286
|
|
253
|
+
},
|
|
254
|
+
"discard.wav": {
|
|
255
|
+
"sha256": "b7c7e6d9d85c095086bbb363878f2d43718852faa760ec9a80c22a278af25c3d",
|
|
256
|
+
"bytes": 2664
|
|
257
|
+
},
|
|
258
|
+
"discard_other.wav": {
|
|
259
|
+
"sha256": "827a02bcc0b4b562c0e5570fbdefa7380c1d93f08d891c5a25b582d78eebc19b",
|
|
260
|
+
"bytes": 1616
|
|
261
|
+
},
|
|
262
|
+
"draw.wav": {
|
|
263
|
+
"sha256": "109f99db18e74a70c658651fde27080ca3d10e56ad6af9c4923fa79b3cc55c5f",
|
|
264
|
+
"bytes": 2666
|
|
265
|
+
},
|
|
266
|
+
"encounter.mp3": {
|
|
267
|
+
"sha256": "d266523c8a39b9df416ba0e279b3d408ed53aa9cb9eb4dd91c234f3e81ab7e70",
|
|
268
|
+
"bytes": 1041600
|
|
269
|
+
},
|
|
270
|
+
"fanfare1.wav": {
|
|
271
|
+
"sha256": "1c033836febf9f7fd4453c33233fd23103f4ad0f86caf385faf86227e38d2a90",
|
|
272
|
+
"bytes": 37404
|
|
273
|
+
},
|
|
274
|
+
"fanfare2.wav": {
|
|
275
|
+
"sha256": "7c5cf3343d909d0fd0c0bc7218ef27b824d3ee3b217ed3b80ada4141813dd9b3",
|
|
276
|
+
"bytes": 24652
|
|
277
|
+
},
|
|
278
|
+
"fanfare3.wav": {
|
|
279
|
+
"sha256": "1bf38ce91857961b980d268adb8898a5242c159d1d4f00a5e8243fc919b94c57",
|
|
280
|
+
"bytes": 26872
|
|
281
|
+
},
|
|
282
|
+
"fanfare4.wav": {
|
|
283
|
+
"sha256": "f75997ee293281fa8b5c5dc0a33980def453f6a889cba16a36cb2449e3ef87c1",
|
|
284
|
+
"bytes": 33362
|
|
285
|
+
},
|
|
286
|
+
"kan.wav": {
|
|
287
|
+
"sha256": "50ac3498c781e1c7cfd1275c9625167650d1b1390719bc544dc6e90aa73e06f9",
|
|
288
|
+
"bytes": 11790
|
|
289
|
+
},
|
|
290
|
+
"kyuushu.wav": {
|
|
291
|
+
"sha256": "73d5c7a57ad5dd109df0da1cde6364943ac9b956233cac1336c5d5d517bcf7af",
|
|
292
|
+
"bytes": 14626
|
|
293
|
+
},
|
|
294
|
+
"points.wav": {
|
|
295
|
+
"sha256": "2b6167f3bc36339118e30b290a8a2fe9b5069da5cb2b6160b1be89db24546296",
|
|
296
|
+
"bytes": 15004
|
|
297
|
+
},
|
|
298
|
+
"pon.wav": {
|
|
299
|
+
"sha256": "cf904f8855444198c89a76d76acf1041efc42dd888b3ad0f1f964099ab1ee8d8",
|
|
300
|
+
"bytes": 5810
|
|
301
|
+
},
|
|
302
|
+
"riichi.wav": {
|
|
303
|
+
"sha256": "e8d8064abba1da685453bc0fb30d938a2da5ad69aef69e4993459676c1a75e65",
|
|
304
|
+
"bytes": 11050
|
|
305
|
+
},
|
|
306
|
+
"ron.wav": {
|
|
307
|
+
"sha256": "aaec44cfe51ffb0219e6936cd0642e0b8b972b0f4e21625fb54d1e36028e4029",
|
|
308
|
+
"bytes": 18892
|
|
309
|
+
},
|
|
310
|
+
"round.wav": {
|
|
311
|
+
"sha256": "f2857dd2e45e0a1e130e63285119e3072a35056f571954f95665cce81b7047d4",
|
|
312
|
+
"bytes": 17164
|
|
313
|
+
},
|
|
314
|
+
"ryuukyoku.wav": {
|
|
315
|
+
"sha256": "105f366cf26b8be5e8aa210886c05b91965e566ecf610a64b4b5b63db452bff7",
|
|
316
|
+
"bytes": 26500
|
|
317
|
+
},
|
|
318
|
+
"spring_thing.mp3": {
|
|
319
|
+
"sha256": "55f90585c6994293d33ece9145b9a7f58c457a86a9b466c52882997a12fb87db",
|
|
320
|
+
"bytes": 987648
|
|
321
|
+
},
|
|
322
|
+
"tsumo.wav": {
|
|
323
|
+
"sha256": "2f849a68fe071355714322e0e5693c529067621c7030252ad6efce69f7ef5750",
|
|
324
|
+
"bytes": 16766
|
|
325
|
+
},
|
|
326
|
+
"turn.wav": {
|
|
327
|
+
"sha256": "a2813659474f629ea8fc88e5dc4eae70ba662d8bbe569178089522c929edd0c4",
|
|
328
|
+
"bytes": 6334
|
|
329
|
+
},
|
|
330
|
+
"yaku.wav": {
|
|
331
|
+
"sha256": "b19fbb15ad4ee6125bfa9ebe31c3701d6a25551a2bdb7e490f68c1f26dd3d8a5",
|
|
332
|
+
"bytes": 3714
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "monitor"
|
|
5
5
|
require_relative "mahjong_events"
|
|
6
6
|
require_relative "mahjong_phrases"
|
|
7
|
+
require_relative "mahjong_sounds"
|
|
7
8
|
require_relative "server"
|
|
8
9
|
require_relative "voice"
|
|
9
10
|
require_relative "ai_comment"
|
|
@@ -68,6 +69,7 @@ module AsterismGames
|
|
|
68
69
|
t["gone"] = false
|
|
69
70
|
t["at"] = @clock.call
|
|
70
71
|
evs, t["memo"] = MahjongEvents.diff(t["view"], view, t["memo"])
|
|
72
|
+
sounds = MahjongSounds.effects(t["view"], view)
|
|
71
73
|
evs.each do |ev|
|
|
72
74
|
l = MahjongPhrases.line(ev, view, @rng, tone: @tone)
|
|
73
75
|
l = public_line(l) if @public
|
|
@@ -82,8 +84,7 @@ module AsterismGames
|
|
|
82
84
|
end
|
|
83
85
|
t["lines"].shift while t["lines"].size > KEEP_LINES
|
|
84
86
|
t["view"] = view
|
|
85
|
-
|
|
86
|
-
frames << [id, Server.frame("state", { "table" => id, "view" => out })]
|
|
87
|
+
frames << [id, Server.frame("state", state_frame(id, view).merge("sounds" => sounds))]
|
|
87
88
|
lines.each { |l| frames << [id, Server.frame("line", l)] }
|
|
88
89
|
broadcast_status if fresh || was_gone
|
|
89
90
|
frames.each { |tid, f| each_client(tid) { |c| c.push(f) } }
|
|
@@ -182,7 +183,7 @@ module AsterismGames
|
|
|
182
183
|
out = [["status", status]]
|
|
183
184
|
t = @tables[client.table]
|
|
184
185
|
if t
|
|
185
|
-
out << ["state",
|
|
186
|
+
out << ["state", state_frame(client.table, t["view"])]
|
|
186
187
|
t["lines"].each { |l| out << ["line", l.merge("old" => true)] }
|
|
187
188
|
end
|
|
188
189
|
out
|
|
@@ -226,7 +227,7 @@ module AsterismGames
|
|
|
226
227
|
if c.table.nil? && (d = default_table)
|
|
227
228
|
c.table = d
|
|
228
229
|
t = @tables[d]
|
|
229
|
-
c.push(Server.frame("state",
|
|
230
|
+
c.push(Server.frame("state", state_frame(d, t["view"]))) if t["view"]
|
|
230
231
|
end
|
|
231
232
|
c.push(f)
|
|
232
233
|
end
|
|
@@ -239,6 +240,12 @@ module AsterismGames
|
|
|
239
240
|
end
|
|
240
241
|
end
|
|
241
242
|
|
|
243
|
+
# A "state" event: the view as it leaves, and the BGM's scene for it
|
|
244
|
+
# (MahjongSounds; update adds the effects of the step).
|
|
245
|
+
def state_frame(id, view)
|
|
246
|
+
{ "table" => id, "view" => shown(view), "bgm" => MahjongSounds.scene(view) }
|
|
247
|
+
end
|
|
248
|
+
|
|
242
249
|
# The view as it leaves: without the hands with --public.
|
|
243
250
|
def shown(view)
|
|
244
251
|
return view unless @public && view.is_a?(Hash)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AsterismGames
|
|
4
|
+
module Commentate
|
|
5
|
+
# The sound of Family mruby's mahjong app at the commentary page
|
|
6
|
+
# (README "Commentary", "The sound"): which of the app's effects a new
|
|
7
|
+
# spectate view calls for, and the scene of the BGM. Pure functions, as
|
|
8
|
+
# MahjongEvents: the same two views always give the same sounds.
|
|
9
|
+
#
|
|
10
|
+
# sounds = MahjongSounds.effects(prev, cur) # [[name, delay_ms], ...]
|
|
11
|
+
# scene = MahjongSounds.scene(cur) # "lobby", "play", "tense" or "none"
|
|
12
|
+
#
|
|
13
|
+
# The names are the app's (mahjong_sound.rb's SFX, the files in
|
|
14
|
+
# assets/sounds/, sounds.json). The page hears the table as the app's
|
|
15
|
+
# spectator does: a discard is discard_other whoever made it, and the
|
|
16
|
+
# player's own sounds (a draw, the turn coming) are not played. In the
|
|
17
|
+
# order the app plays them, at the same moments:
|
|
18
|
+
# a new round round
|
|
19
|
+
# a call, a riichi chi, pon, kan, riichi (the jingle of the call)
|
|
20
|
+
# a discard discard_other
|
|
21
|
+
# a win ron or tsumo (the jingle), then points after
|
|
22
|
+
# the shout (1 s), the winner's points
|
|
23
|
+
# a draw of the round ryuukyoku (and the abortive draws: kyuushu for
|
|
24
|
+
# nine kinds, ryuukyoku for the others; three
|
|
25
|
+
# rons is the app's ryuukyoku jingle too)
|
|
26
|
+
# the end of the game fanfare1 (a spectator's: the winner's), after
|
|
27
|
+
# a win's shout when one is sounding
|
|
28
|
+
# The BGM's scenes are the app's: the lobby (waiting), the game and its
|
|
29
|
+
# results, the tense one (a riichi on the table, or TENSE_LEFT tiles or
|
|
30
|
+
# fewer left), and none at the end of a game (the fanfare plays alone).
|
|
31
|
+
module MahjongSounds
|
|
32
|
+
TENSE_LEFT = 10 # mahjong_sound.rb's TENSE_LEFT (sounds.json's tense_left)
|
|
33
|
+
SHOUT_MS = 1000 # how long the app shows a shout (a win's result waits for it)
|
|
34
|
+
CALLS = { "chi" => "chi", "pon" => "pon", "open_kan" => "kan", "closed_kan" => "kan", "added_kan" => "kan" }.freeze
|
|
35
|
+
DRAWS = %w[ryuukyoku suufon_renda suukaikan suucha_riichi sanchahou].freeze
|
|
36
|
+
# Every name these functions give (the files the page needs).
|
|
37
|
+
NAMES = %w[round chi pon kan riichi discard_other ron tsumo points ryuukyoku kyuushu fanfare1].freeze
|
|
38
|
+
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
# [[name, delay_ms], ...] for the step from prev to cur (nothing for the
|
|
42
|
+
# first view: that one is old news, as in the app).
|
|
43
|
+
def effects(prev, cur)
|
|
44
|
+
return [] unless prev.is_a?(Hash) && cur.is_a?(Hash)
|
|
45
|
+
out = []
|
|
46
|
+
st = cur["status"]
|
|
47
|
+
pst = prev["status"]
|
|
48
|
+
same = %w[play result].include?(pst) && round_key(prev) == round_key(cur)
|
|
49
|
+
if st == "play" && !same
|
|
50
|
+
out << ["round", 0]
|
|
51
|
+
return out
|
|
52
|
+
end
|
|
53
|
+
jingle = nil
|
|
54
|
+
if same && %w[play result over].include?(st)
|
|
55
|
+
ps = Array(prev["seats"])
|
|
56
|
+
Array(cur["seats"]).each_with_index do |s, i|
|
|
57
|
+
p = ps[i] || {}
|
|
58
|
+
call = new_call(Array(p["melds"]), Array(s["melds"]))
|
|
59
|
+
jingle = call if call
|
|
60
|
+
jingle = "riichi" if s["riichi"] && !p["riichi"]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
res = cur["result"]
|
|
64
|
+
win = false
|
|
65
|
+
if %w[result over].include?(st) && res.is_a?(Hash) && (pst == "play" || prev["result"] != res)
|
|
66
|
+
kind = res["kind"].to_s
|
|
67
|
+
if %w[ron_agari tsumo_agari].include?(kind)
|
|
68
|
+
jingle = kind == "ron_agari" ? "ron" : "tsumo"
|
|
69
|
+
win = true
|
|
70
|
+
elsif kind == "kyuushu_kyuuhai"
|
|
71
|
+
jingle = "kyuushu"
|
|
72
|
+
elsif DRAWS.include?(kind)
|
|
73
|
+
jingle = "ryuukyoku"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
out << [jingle, 0] if jingle
|
|
77
|
+
out << ["discard_other", 0] if st == "play" && pst == "play" && same && river_n(cur) > river_n(prev)
|
|
78
|
+
if st == "over" && pst != "over"
|
|
79
|
+
out << ["fanfare1", jingle ? SHOUT_MS : 0]
|
|
80
|
+
elsif win
|
|
81
|
+
out << ["points", SHOUT_MS]
|
|
82
|
+
end
|
|
83
|
+
out
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The BGM's scene for a view (nil: no table yet, the lobby's tune).
|
|
87
|
+
def scene(view)
|
|
88
|
+
return "lobby" unless view.is_a?(Hash)
|
|
89
|
+
st = view["status"]
|
|
90
|
+
return "lobby" if st == "wait"
|
|
91
|
+
return "none" if st == "over"
|
|
92
|
+
return "play" unless %w[play result].include?(st)
|
|
93
|
+
riichi = Array(view["seats"]).any? { |s| s.is_a?(Hash) && s["riichi"] }
|
|
94
|
+
left = view.dig("round", "wall_left")
|
|
95
|
+
riichi || (st == "play" && left.is_a?(Integer) && left <= TENSE_LEFT) ? "tense" : "play"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def round_key(v)
|
|
99
|
+
r = v["round"]
|
|
100
|
+
r.is_a?(Hash) ? [r["wind"], r["number"], r["honba"]] : nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def river_n(v)
|
|
104
|
+
Array(v["seats"]).sum { |s| s.is_a?(Hash) ? Array(s["river"]).size : 0 }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The jingle of a seat's new meld, or nil.
|
|
108
|
+
def new_call(old, cur)
|
|
109
|
+
return CALLS[cur.last["type"].to_s] if cur.size > old.size && cur.last.is_a?(Hash)
|
|
110
|
+
cur.each_with_index do |m, j|
|
|
111
|
+
o = old[j]
|
|
112
|
+
return "kan" if o.is_a?(Hash) && m.is_a?(Hash) && o["type"] != m["type"] && m["type"] == "added_kan"
|
|
113
|
+
end
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -15,7 +15,9 @@ module AsterismGames
|
|
|
15
15
|
# VOICEVOX voice (the hub's voice), also the fixed phrases'
|
|
16
16
|
# files (/clip/<digest>.wav, only those of the list) and the lines'
|
|
17
17
|
# sound (/voice/<digest>.wav, only the texts the hub registered:
|
|
18
|
-
# never a text a page sends).
|
|
18
|
+
# never a text a page sends). And the mahjong app's sound (README "The
|
|
19
|
+
# sound"): /sounds/<name>.wav|mp3 and their sounds.json, the files of
|
|
20
|
+
# assets/sounds/ only, with their note (/SOUNDS_LICENSE.txt).
|
|
19
21
|
class Server
|
|
20
22
|
ASSETS = File.expand_path("assets", __dir__)
|
|
21
23
|
FILES = {
|
|
@@ -25,8 +27,11 @@ module AsterismGames
|
|
|
25
27
|
"/commentate.css" => ["commentate.css", "text/css; charset=utf-8"],
|
|
26
28
|
"/tiles_hand.bmp" => ["tiles_hand.bmp", "image/bmp"],
|
|
27
29
|
"/tiles_river.bmp" => ["tiles_river.bmp", "image/bmp"],
|
|
28
|
-
"/TILES_LICENSE.txt" => ["TILES_LICENSE.txt", "text/plain; charset=utf-8"]
|
|
30
|
+
"/TILES_LICENSE.txt" => ["TILES_LICENSE.txt", "text/plain; charset=utf-8"],
|
|
31
|
+
"/SOUNDS_LICENSE.txt" => ["SOUNDS_LICENSE.txt", "text/plain; charset=utf-8"]
|
|
29
32
|
}.freeze
|
|
33
|
+
SOUNDS = %r{\A/sounds/([a-z0-9_]+\.(wav|mp3|json))\z}
|
|
34
|
+
SOUND_TYPES = { "wav" => "audio/wav", "mp3" => "audio/mpeg", "json" => "application/json; charset=utf-8" }.freeze
|
|
30
35
|
CLIP = %r{\A/clip/([0-9a-f]{16}\.wav)\z}
|
|
31
36
|
LIVE = %r{\A/voice/([0-9a-f]{32}\.wav)\z}
|
|
32
37
|
LOCAL_HOSTS = %w[127.0.0.1 localhost].freeze
|
|
@@ -98,6 +103,13 @@ module AsterismGames
|
|
|
98
103
|
return respond(sock, 404, "text/plain", "not found\n") unless body
|
|
99
104
|
respond(sock, 200, "audio/wav", method == "HEAD" ? "" : body, "Cache-Control" => "max-age=600",
|
|
100
105
|
"Content-Length" => body.bytesize.to_s)
|
|
106
|
+
elsif (m = path.match(SOUNDS))
|
|
107
|
+
file = File.join(ASSETS, "sounds", m[1])
|
|
108
|
+
return respond(sock, 404, "text/plain", "not found\n") unless File.file?(file)
|
|
109
|
+
body = File.binread(file)
|
|
110
|
+
cache = m[2] == "json" ? "no-cache" : "max-age=3600"
|
|
111
|
+
respond(sock, 200, SOUND_TYPES[m[2]], method == "HEAD" ? "" : body, "Cache-Control" => cache,
|
|
112
|
+
"Content-Length" => body.bytesize.to_s)
|
|
101
113
|
elsif (f = FILES[path])
|
|
102
114
|
body = File.binread(File.join(ASSETS, f[0]))
|
|
103
115
|
respond(sock, 200, f[1], method == "HEAD" ? "" : body, "Cache-Control" => "no-cache",
|
|
@@ -56,8 +56,14 @@ module AsterismGames
|
|
|
56
56
|
fixed commentary alone
|
|
57
57
|
-h, --help this text
|
|
58
58
|
|
|
59
|
-
The page: Space pauses, M turns the voice off and on,
|
|
60
|
-
|
|
59
|
+
The page: Space pauses, M turns the voice off and on, B steps the BGM
|
|
60
|
+
(off, low, mid, high), E turns the effects off and on, H hides the
|
|
61
|
+
hands. The browser speaks once the page has been clicked (its autoplay rule).
|
|
62
|
+
|
|
63
|
+
The sound: the BGM and the effects of Family mruby's mahjong app, at
|
|
64
|
+
the app's moments, the BGM low by default and lower while the voice
|
|
65
|
+
speaks. The BGM is shiru8bit's (CC BY 3.0): the page shows the credit
|
|
66
|
+
(BGM: shiru8bit (CC BY 3.0)); put it in a stream's description too.
|
|
61
67
|
|
|
62
68
|
The voice: VOICEVOX's character says the fixed phrases (リーチ, ポン,
|
|
63
69
|
ロン, the rounds ...) from WAV files made on this machine at the first
|
|
@@ -327,7 +327,9 @@ module AsterismGames
|
|
|
327
327
|
return "待ち合わせを待っています" unless v["st"] == "wait"
|
|
328
328
|
return nil unless OWNER_WORDS.include?(w)
|
|
329
329
|
unless MahjongText.owner?(v)
|
|
330
|
-
|
|
330
|
+
# Seated or not, the role is someone else's (since games 0.20.0
|
|
331
|
+
# an owner may be unseated): wait for it to come here.
|
|
332
|
+
return "卓の主ではありません。卓の主の役が回ってくるのを待っています"
|
|
331
333
|
end
|
|
332
334
|
return "4 席がそろうのを待っています" if %w[start watch].include?(w) && (v["sk"] || [0]).any?(&:zero?)
|
|
333
335
|
nil
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asterism-games
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Katsuhiko Kageyama
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: asterism
|
|
@@ -37,7 +36,6 @@ description: 'A framework for the server side of a game on an Asterism network:
|
|
|
37
36
|
rules and the methods it exposes. Comes with othello and a shared drawing board
|
|
38
37
|
as samples, terminal clients for othello and mahjong (asterism-games play), and
|
|
39
38
|
a mahjong commentator that draws a table in the browser (asterism-games commentate).'
|
|
40
|
-
email:
|
|
41
39
|
executables:
|
|
42
40
|
- asterism-games
|
|
43
41
|
extensions: []
|
|
@@ -56,15 +54,40 @@ files:
|
|
|
56
54
|
- lib/asterism_games/board/table.rb
|
|
57
55
|
- lib/asterism_games/commentate.rb
|
|
58
56
|
- lib/asterism_games/commentate/ai_comment.rb
|
|
57
|
+
- lib/asterism_games/commentate/assets/SOUNDS_LICENSE.txt
|
|
59
58
|
- lib/asterism_games/commentate/assets/TILES_LICENSE.txt
|
|
60
59
|
- lib/asterism_games/commentate/assets/commentate.css
|
|
61
60
|
- lib/asterism_games/commentate/assets/commentate.js
|
|
62
61
|
- lib/asterism_games/commentate/assets/index.html
|
|
62
|
+
- lib/asterism_games/commentate/assets/sounds/after_the_rain.mp3
|
|
63
|
+
- lib/asterism_games/commentate/assets/sounds/chi.wav
|
|
64
|
+
- lib/asterism_games/commentate/assets/sounds/discard.wav
|
|
65
|
+
- lib/asterism_games/commentate/assets/sounds/discard_other.wav
|
|
66
|
+
- lib/asterism_games/commentate/assets/sounds/draw.wav
|
|
67
|
+
- lib/asterism_games/commentate/assets/sounds/encounter.mp3
|
|
68
|
+
- lib/asterism_games/commentate/assets/sounds/fanfare1.wav
|
|
69
|
+
- lib/asterism_games/commentate/assets/sounds/fanfare2.wav
|
|
70
|
+
- lib/asterism_games/commentate/assets/sounds/fanfare3.wav
|
|
71
|
+
- lib/asterism_games/commentate/assets/sounds/fanfare4.wav
|
|
72
|
+
- lib/asterism_games/commentate/assets/sounds/kan.wav
|
|
73
|
+
- lib/asterism_games/commentate/assets/sounds/kyuushu.wav
|
|
74
|
+
- lib/asterism_games/commentate/assets/sounds/points.wav
|
|
75
|
+
- lib/asterism_games/commentate/assets/sounds/pon.wav
|
|
76
|
+
- lib/asterism_games/commentate/assets/sounds/riichi.wav
|
|
77
|
+
- lib/asterism_games/commentate/assets/sounds/ron.wav
|
|
78
|
+
- lib/asterism_games/commentate/assets/sounds/round.wav
|
|
79
|
+
- lib/asterism_games/commentate/assets/sounds/ryuukyoku.wav
|
|
80
|
+
- lib/asterism_games/commentate/assets/sounds/sounds.json
|
|
81
|
+
- lib/asterism_games/commentate/assets/sounds/spring_thing.mp3
|
|
82
|
+
- lib/asterism_games/commentate/assets/sounds/tsumo.wav
|
|
83
|
+
- lib/asterism_games/commentate/assets/sounds/turn.wav
|
|
84
|
+
- lib/asterism_games/commentate/assets/sounds/yaku.wav
|
|
63
85
|
- lib/asterism_games/commentate/assets/tiles_hand.bmp
|
|
64
86
|
- lib/asterism_games/commentate/assets/tiles_river.bmp
|
|
65
87
|
- lib/asterism_games/commentate/mahjong.rb
|
|
66
88
|
- lib/asterism_games/commentate/mahjong_events.rb
|
|
67
89
|
- lib/asterism_games/commentate/mahjong_phrases.rb
|
|
90
|
+
- lib/asterism_games/commentate/mahjong_sounds.rb
|
|
68
91
|
- lib/asterism_games/commentate/mahjong_voice.yml
|
|
69
92
|
- lib/asterism_games/commentate/server.rb
|
|
70
93
|
- lib/asterism_games/commentate/voice.rb
|
|
@@ -96,7 +119,6 @@ metadata:
|
|
|
96
119
|
changelog_uri: https://github.com/ruby-asterism/asterism-games/blob/main/CHANGELOG.md
|
|
97
120
|
bug_tracker_uri: https://github.com/ruby-asterism/asterism-games/issues
|
|
98
121
|
rubygems_mfa_required: 'true'
|
|
99
|
-
post_install_message:
|
|
100
122
|
rdoc_options: []
|
|
101
123
|
require_paths:
|
|
102
124
|
- lib
|
|
@@ -104,15 +126,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
104
126
|
requirements:
|
|
105
127
|
- - ">="
|
|
106
128
|
- !ruby/object:Gem::Version
|
|
107
|
-
version: '3.
|
|
129
|
+
version: '3.4'
|
|
108
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
131
|
requirements:
|
|
110
132
|
- - ">="
|
|
111
133
|
- !ruby/object:Gem::Version
|
|
112
134
|
version: '0'
|
|
113
135
|
requirements: []
|
|
114
|
-
rubygems_version: 3.
|
|
115
|
-
signing_key:
|
|
136
|
+
rubygems_version: 3.6.9
|
|
116
137
|
specification_version: 4
|
|
117
138
|
summary: 'Game hosts on Asterism: write the rules of a game, get tables people play
|
|
118
139
|
over Zenoh'
|