Skip to content

Commit 23ff27c

Browse files
committed
perf(gb): reuse adjacent discard preference on memo hit
1 parent f60e9bf commit 23ff27c

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

src/main/java/top/ellan/mahjong/table/core/round/GbBotDecisionService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,12 @@ private DiscardChoice bestDiscardChoice(
187187
}
188188
DiscardChoice best = null;
189189
EnumMap<MahjongTile, GbTingResponse> tingMemo = new EnumMap<>(MahjongTile.class);
190+
MahjongTile previousDiscarded = null;
191+
int previousDiscardPreference = 0;
190192
for (int i = 0; i < hand.size(); i++) {
191193
MahjongTile discarded = hand.get(i);
192194
GbTingResponse ting = tingMemo.get(discarded);
195+
boolean tingMemoHit = ting != null;
193196
if (ting == null) {
194197
List<MahjongTile> remaining = new ArrayList<>(hand);
195198
remaining.remove(i);
@@ -198,7 +201,13 @@ private DiscardChoice bestDiscardChoice(
198201
tingMemo.put(discarded, ting);
199202
}
200203
}
201-
DiscardChoice candidate = new DiscardChoice(i, readyScore(ting), discardPreference(hand, discarded));
204+
long candidateReadyScore = readyScore(ting);
205+
int candidateDiscardPreference = tingMemoHit && discarded == previousDiscarded
206+
? previousDiscardPreference
207+
: discardPreference(hand, discarded);
208+
DiscardChoice candidate = new DiscardChoice(i, candidateReadyScore, candidateDiscardPreference);
209+
previousDiscarded = discarded;
210+
previousDiscardPreference = candidateDiscardPreference;
202211
if (best == null || candidate.compareTo(best) > 0) {
203212
best = candidate;
204213
}

src/test/kotlin/top/ellan/mahjong/table/core/round/GbBotDecisionServiceTest.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,56 @@ class GbBotDecisionServiceTest {
6565
)
6666
}
6767

68+
@Test
69+
fun `discard suggestion keeps the earliest index for equal duplicate candidates`() {
70+
val service = GbBotDecisionService(8)
71+
val hand = listOf(MahjongTile.M1, MahjongTile.M1, MahjongTile.M1)
72+
var evaluations = 0
73+
74+
val suggestedIndex =
75+
service.suggestedDiscardIndex(hand, emptyList()) { _, _ ->
76+
evaluations++
77+
GbTingResponse(true, emptyList(), null)
78+
}
79+
80+
assertEquals(0, suggestedIndex)
81+
assertEquals(1, evaluations)
82+
}
83+
84+
@Test
85+
fun `discard suggestion recomputes null before memoizing a later duplicate response`() {
86+
val service = GbBotDecisionService(8)
87+
val hand = listOf(MahjongTile.M1, MahjongTile.M1, MahjongTile.M1)
88+
val ready = GbTingResponse(true, listOf(GbTingCandidate("W1", 8)), null)
89+
var evaluations = 0
90+
91+
val suggestedIndex =
92+
service.suggestedDiscardIndex(hand, emptyList()) { _, _ ->
93+
evaluations++
94+
if (evaluations == 1) null else ready
95+
}
96+
97+
assertEquals(1, suggestedIndex)
98+
assertEquals(2, evaluations)
99+
}
100+
101+
@Test
102+
fun `discard suggestion does not reuse red five preference for the exact regular tile`() {
103+
val service = GbBotDecisionService(8)
104+
val hand = listOf(MahjongTile.M5, MahjongTile.M5_RED, MahjongTile.M5)
105+
val ready = GbTingResponse(true, listOf(GbTingCandidate("W1", 8)), null)
106+
var evaluations = 0
107+
108+
val suggestedIndex =
109+
service.suggestedDiscardIndex(hand, emptyList()) { remaining, _ ->
110+
evaluations++
111+
if (MahjongTile.M5_RED in remaining) ready else GbTingResponse(true, emptyList(), null)
112+
}
113+
114+
assertEquals(0, suggestedIndex)
115+
assertEquals(2, evaluations)
116+
}
117+
68118
@Test
69119
fun `discard suggestion preserves null evaluator recomputation`() {
70120
val service = GbBotDecisionService(8)

0 commit comments

Comments
 (0)