Skip to content

Commit d6592b4

Browse files
committed
Add a version of card-free american mahjong that seems to work
1 parent 53b897a commit d6592b4

File tree

5 files changed

+121
-7
lines changed

5 files changed

+121
-7
lines changed

lib/riichi_advanced/game/game_state.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,12 @@ defmodule RiichiAdvanced.GameState do
13921392
GenServer.cast(self(), :calculate_playable_indices)
13931393
# async populate closest_american_hands for all players
13941394
if state.ruleset == "american" do
1395-
GenServer.cast(self(), :calculate_closest_american_hands)
1395+
win_definition = Map.get(state.rules, "win_definition", [])
1396+
# the am_card_free mod sets win_definition to the empty one, because it uses an alternate wincon
1397+
# in which case we don't calculate closest hands
1398+
if win_definition != [[]] do
1399+
GenServer.cast(self(), :calculate_closest_american_hands)
1400+
end
13961401
end
13971402
end
13981403
# IO.puts("broadcast_state_change called")

lib/riichi_advanced/game/game_state/scoring.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,8 +1095,8 @@ defmodule RiichiAdvanced.GameState.Scoring do
10951095
else {prev_hand, winning_tile} end
10961096

10971097
# score yaku
1098-
yaku = if highest_scoring_yaku_only do [Enum.max_by(yaku, fn {_name, value} -> value end)] else yaku end
1099-
yaku2 = if highest_scoring_yaku_only do [Enum.max_by(yaku2, fn {_name, value} -> value end)] else yaku2 end
1098+
yaku = if not Enum.empty?(yaku) and highest_scoring_yaku_only do [Enum.max_by(yaku, fn {_name, value} -> value end)] else yaku end
1099+
yaku2 = if not Enum.empty?(yaku2) and highest_scoring_yaku_only do [Enum.max_by(yaku2, fn {_name, value} -> value end)] else yaku2 end
11001100
{score, points, points2, score_name} = score_yaku(state, seat, yaku, yaku2, is_dealer, win_source == :draw, minipoints)
11011101
if Debug.print_wins() do
11021102
IO.puts("score: #{inspect(score)}, points: #{inspect(points)}, points2: #{inspect(points2)}, minipoints: #{inspect(minipoints)}, score_name: #{inspect(score_name)}")

lib/riichi_advanced/majs/compiler.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,8 @@ defmodule RiichiAdvanced.Compiler do
295295
"merge" when is_map(value_val) -> {:ok, "#{path} += #{value}"}
296296
"merge" -> {:ok, "#{path} += #{Jason.encode!(Map.new(value_val))}"}
297297
"subtract" -> {:ok, "#{path} -= #{value}"}
298-
"delete" -> {:ok, "#{path} -= #{value}"}
298+
"delete" when is_list(value_val) -> {:ok, "#{path} |= map(select(#{value} | index(.) | not))"}
299+
"delete" -> {:ok, "#{path} |= map(select(. != #{value}))"}
299300
"multiply" -> {:ok, "#{path} *= #{value}"}
300301
"deep_merge" -> {:ok, "#{path} *= #{value}"}
301302
"divide" when is_number(value_val) -> {:ok, "#{path} /= #{value}"}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
define_set am_pung, ~s"0 0 0"
2+
define_set am_kong, ~s"0 0 0 0"
3+
define_set am_quint, ~s"0 0 0 0 0"
4+
define_set am_news_kong, ~s"1z 2z 3z 4z"
5+
define_set am_dragon_pung, ~s"0z 6z 7z"
6+
define_set am_dragons_wings, ~s"1s 1s 0z 0z 0z 0z 6z 6z 6z 6z 7z 7z 7z 7z"
7+
8+
# category
9+
define_match any_like_numbers_true, ~a"X0a X0b X0c"
10+
define_match any_like_numbers_false, ~a"NN|EE|WW|SS", ~a"X0a X1a|X2a|X3a|X4a|X5a|X6a|X7a|X8a"
11+
define_match winds_news_true, ~a"NN EE WW SS"
12+
define_match winds_news_false, ~a"X0a"
13+
define_match winds_ns_true, ~a"NN SS"
14+
define_match winds_ns_false, ~a"X0a", ~a"EE|WW"
15+
define_match winds_ew_true, ~a"EE WW"
16+
define_match winds_ew_false, ~a"X0a", ~a"NN|SS"
17+
define_match num_369_true, ~a"3a 6a 9a"
18+
define_match num_369_false, ~a"X0a X0b", ~a"1a|2a|4a|5a|7a|8a|NN|EE|WW|SS"
19+
define_match num_2468_true, ~a"2a 4a 6a", ~a"4a 6a 8a"
20+
define_match num_2468_false, ~a"X0a X0b", ~a"1a|3a|5a|7a|9a|NN|EE|WW|SS"
21+
define_match num_135_true, ~a"1a 3a 5a"
22+
define_match num_135_false, ~a"X0a X0b", ~a"2a|4a|6a|8a|9a|NN|EE|WW|SS"
23+
define_match num_579_true, ~a"5a 7a 9a"
24+
define_match num_579_false, ~a"X0a X0b", ~a"2a|4a|6a|8a|1a|NN|EE|WW|SS"
25+
define_match num_357_true, ~a"3a 5a 7a"
26+
define_match num_357_false, ~a"X0a X0b", ~a"2a|4a|6a|8a|1a|9a|NN|EE|WW|SS"
27+
define_match consecutive_run_34_true, ~a"X0a X1a X2a"
28+
define_match consecutive_run_34_false, ~a"X0a X0b", ~a"X0a X4a|X5a|X6a|X7a|X8a", ~a"NN|EE|WW|SS"
29+
define_match consecutive_run_56_true, ~a"X0a X1a X2a X3a X4a"
30+
define_match consecutive_run_56_false, ~a"X0a X0b", ~a"X0a X6a|X7a|X8a", ~a"NN|EE|WW|SS"
31+
32+
# block pattern
33+
# 3 unique blocks
34+
define_match blocks_7, ~m"(am_pung am_dragon_pung):2, am_quint:1"
35+
define_match blocks_8, ~m"(am_kong am_news_kong):1, am_quint:2"
36+
# 4 unique blocks
37+
define_match blocks_1, ~m"pair:1, (am_kong am_news_kong):3"
38+
define_match blocks_6, ~m"pair:2, am_quint:2"
39+
define_match blocks_9, ~m"(am_pung am_dragon_pung):2, (am_kong am_news_kong):2"
40+
# 5 unique blocks
41+
define_match blocks_2, ~m"pair:3, (am_kong am_news_kong):2"
42+
define_match blocks_4, ~m"pair:1, (am_pung am_dragon_pung):4"
43+
# 6 unique blocks
44+
define_match blocks_3, ~m"pair:5, (am_kong am_news_kong):1"
45+
define_match blocks_5, ~m"pair:4, (am_pung am_dragon_pung):2"
46+
define_match blocks, "blocks_1", "blocks_2", "blocks_3", "blocks_4", "blocks_5", "blocks_6", "blocks_7", "blocks_8", "blocks_9"
47+
48+
define_match am_dragons_love, ~a"DDDDa DDDDb FFF FFF"
49+
define_match am_dragons_wings, ~m"am_dragons_wings:1"
50+
define_match am_dragons_breath, ~a"NN EE WW SS RR GG 00"
51+
define_match dragon_hand_win, "am_dragons_love", "am_dragons_wings", "am_dragons_breath"
52+
53+
# unique tiles
54+
on after_start do
55+
tag_tiles("flower", ["1f", "2f", "3f", "4f", "1g", "2g", "3g", "4g"])
56+
add_attr_tagged("flower", ["_flower"])
57+
end
58+
define_set unique_tile, ~s"""
59+
1m | 2m | 3m | 4m | 5m | 6m | 7m | 8m | 9m
60+
| 1p | 2p | 3p | 4p | 5p | 6p | 7p | 8p | 9p
61+
| 1s | 2s | 3s | 4s | 5s | 6s | 7s | 8s | 9s
62+
| 1z | 2z | 3z | 4z | 0z | 6z | 7z | any@flower
63+
"""
64+
define_match unique_3, ~m"(unique unique_tile):3"
65+
define_match unique_4, ~m"(unique unique_tile):4"
66+
define_match unique_5, ~m"(unique unique_tile):5"
67+
define_match unique_6, ~m"(unique unique_tile):6"
68+
69+
define_yaku yaku, "Base Value", 25, not_match(["hand", "call_tiles", "winning_tile"], ["dragon_hand_win"])
70+
define_yaku yaku, "Quints", 5, match(["hand", "call_tiles", "winning_tile"], ~m"am_quint:1")
71+
define_yaku yaku, "Quints", 5, match(["hand", "call_tiles", "winning_tile"], ~m"am_quint:2")
72+
define_yaku yaku, "Pure", 5, not_match(["hand", "call_tiles", "winning_tile"], ~m"(1z 2z 3z 4z 0z 6z 7z any@flower):1")
73+
define_yaku yaku, "Suit", 5,
74+
winning_hand_consists_of("1m","2m","3m","4m","5m","6m","7m","8m","9m","1z","2z","3z","4z","7z")
75+
or winning_hand_consists_of("1p","2p","3p","4p","5p","6p","7p","8p","9p","1z","2z","3z","4z","0z")
76+
or winning_hand_consists_of("1s","2s","3s","4s","5s","6s","7s","8s","9s","1z","2z","3z","4z","6z")
77+
define_yaku yaku, "Concealed", 10, has_no_call_named("am_pung", "am_kong", "am_quint")
78+
79+
apply delete, "buttons.mahjong_heavenly.show_when", "is_tenpai_american"
80+
apply delete, "buttons.mahjong_draw.show_when", "is_tenpai_american"
81+
apply delete, "buttons.mahjong_discard.show_when", "is_tenpai_american"
82+
83+
set open_win_definition, [[]]
84+
set win_definition, [[]]
85+
apply set, "score_calculation.arrange_american_yaku", false
86+
apply set, "score_calculation.highest_scoring_yaku_only", false
87+
88+
define_const win_con, match(["hand", "call_tiles", "draw"], ["dragon_hand_win"])
89+
or (match(["hand", "call_tiles", "draw"], ["blocks"])
90+
and (
91+
(match(["hand", "call_tiles", "draw"], ["any_like_numbers_true"]) and not_match(["hand", "call_tiles", "draw"], ["any_like_numbers_false"]))
92+
or (match(["hand", "call_tiles", "draw"], ["winds_news_true"]) and not_match(["hand", "call_tiles", "draw"], ["winds_news_false"]))
93+
or (match(["hand", "call_tiles", "draw"], ["winds_ns_true"]) and not_match(["hand", "call_tiles", "draw"], ["winds_ns_false"]))
94+
or (match(["hand", "call_tiles", "draw"], ["winds_ew_true"]) and not_match(["hand", "call_tiles", "draw"], ["winds_ew_false"]))
95+
or (match(["hand", "call_tiles", "draw"], ["num_369_true"]) and not_match(["hand", "call_tiles", "draw"], ["num_369_false"]))
96+
or (match(["hand", "call_tiles", "draw"], ["num_2468_true"]) and not_match(["hand", "call_tiles", "draw"], ["num_2468_false"]))
97+
or (match(["hand", "call_tiles", "draw"], ["num_135_true"]) and not_match(["hand", "call_tiles", "draw"], ["num_135_false"]))
98+
or (match(["hand", "call_tiles", "draw"], ["num_579_true"]) and not_match(["hand", "call_tiles", "draw"], ["num_579_false"]))
99+
or (match(["hand", "call_tiles", "draw"], ["num_357_true"]) and not_match(["hand", "call_tiles", "draw"], ["num_357_false"]))
100+
or (match(["hand", "call_tiles", "draw"], ["consecutive_run_34_true"]) and not_match(["hand", "call_tiles", "draw"], ["consecutive_run_34_false"]))
101+
or (match(["hand", "call_tiles", "draw"], ["consecutive_run_56_true"]) and not_match(["hand", "call_tiles", "draw"], ["consecutive_run_56_false"]))
102+
))
103+
104+
apply append, "buttons.mahjong_heavenly.show_when", [[@win_con]]
105+
apply append, "buttons.mahjong_draw.show_when", [[@win_con]]
106+
apply append, "buttons.mahjong_discard.show_when", [[@win_con]]
107+
replace all, "buttons.mahjong_discard.show_when", ["hand", "call_tiles", "draw"], ["hand", "call_tiles", "last_discard"]

priv/static/rulesets/american.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
{"id": "american/am_card_ILoveMahj", "conflicts": ["american/am_card_ATeacherFirst", "american/am_card_MahjLife", "american/BETA_am_card_AMJForEveryone"], "name": "ILM Card", "desc": "Play with the free ILoveMahj Card."},
1212
{"id": "american/am_card_ATeacherFirst", "conflicts": ["american/am_card_ILoveMahj", "american/am_card_MahjLife", "american/BETA_am_card_AMJForEveryone"], "name": "ATF Card", "desc": "Play with the free ATeacherFirst Card."},
1313
{"id": "american/am_card_MahjLife", "conflicts": ["american/am_card_ILoveMahj", "american/am_card_ATeacherFirst", "american/BETA_am_card_AMJForEveryone"], "name": "MahjLife Card", "desc": "Play with the free MahjLife Card."},
14-
{"id": "american/BETA_am_card_AMJForEveryone", "conflicts": ["american/am_card_ILoveMahj", "american/am_card_ATeacherFirst", "american/am_card_MahjLife"], "name": "AMJFE Card (beta)", "desc": "Play with the free American Mah Jongg for Everyone Card. The 75-point hand is not yet implemented."}
14+
{"id": "american/BETA_am_card_AMJForEveryone", "conflicts": ["american/am_card_ILoveMahj", "american/am_card_ATeacherFirst", "american/am_card_MahjLife"], "name": "AMJFE Card (beta)", "desc": "Play with the free American Mah Jongg for Everyone Card. The 75-point hand is not yet implemented."},
15+
{"id": "american/am_card_free", "name": "Card-Free (beta)", "desc": "Play with card-free rules: your hand must consist of two types of melds only, and these melds must match one of the following patterns: Any Like Numbers, NEWS, NS, EW, 369, 2468, 135, 357, 579, or Consecutive Run."}
1516
],
1617
"display_name": "American",
1718
"tutorial_link": "https://github.com/EpicOrange/riichi_advanced/blob/main/documentation/american.md",
@@ -347,15 +348,15 @@
347348
]]
348349
]
349350
},
350-
"before_win": {
351+
"after_scoring": {
351352
"actions": [
352353
// jokerless bonus
353354
["when", [
354355
{"name": "not_match", "opts": [["hand", "call_tiles", "winning_tile"], [[[["1j"], 1]]]]},
355356
{"name": "not_match", "opts": [["hand", "call_tiles", "winning_tile"], ["singles_win"]]}
356357
], [
357358
["push_message", "gets double score for a jokerless hand"],
358-
["set_counter_all", "delta_score_multiplier", 2]
359+
["modify_payout", "all", 2, "multiply"]
359360
]]
360361
]
361362
},

0 commit comments

Comments
 (0)