Skip to content

Commit 05a1a4c

Browse files
authored
perf(gb): allocate discard hands only on ting cache miss (#58)
1 parent 28300f7 commit 05a1a4c

2 files changed

Lines changed: 51 additions & 10 deletions

File tree

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,15 @@ private DiscardChoice bestDiscardChoice(
189189
EnumMap<MahjongTile, GbTingResponse> tingMemo = new EnumMap<>(MahjongTile.class);
190190
for (int i = 0; i < hand.size(); i++) {
191191
MahjongTile discarded = hand.get(i);
192-
List<MahjongTile> remaining = new ArrayList<>(hand);
193-
remaining.remove(i);
194-
GbTingResponse ting = tingMemo.computeIfAbsent(discarded, ignored -> tingEvaluator.evaluate(remaining, melds));
192+
GbTingResponse ting = tingMemo.get(discarded);
193+
if (ting == null) {
194+
List<MahjongTile> remaining = new ArrayList<>(hand);
195+
remaining.remove(i);
196+
ting = tingEvaluator.evaluate(remaining, melds);
197+
if (ting != null) {
198+
tingMemo.put(discarded, ting);
199+
}
200+
}
195201
DiscardChoice candidate = new DiscardChoice(i, readyScore(ting), discardPreference(hand, discarded));
196202
if (best == null || candidate.compareTo(best) > 0) {
197203
best = candidate;

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

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class GbBotDecisionServiceTest {
2424

2525
assertTrue(
2626
GbBotDecisionService.discardPreference(hand, MahjongTile.P9) >
27-
GbBotDecisionService.discardPreference(hand, MahjongTile.M2)
27+
GbBotDecisionService.discardPreference(hand, MahjongTile.M2),
2828
)
2929
}
3030

@@ -33,14 +33,49 @@ class GbBotDecisionServiceTest {
3333
val service = GbBotDecisionService(8)
3434
val hand = listOf(MahjongTile.M1, MahjongTile.M2, MahjongTile.M3, MahjongTile.EAST)
3535

36-
val suggestedIndex = service.suggestedDiscardIndex(hand, emptyList()) { remaining, _ ->
37-
if (MahjongTile.EAST !in remaining) {
38-
GbTingResponse(true, listOf(GbTingCandidate("W1", 8)), null)
39-
} else {
40-
GbTingResponse(true, emptyList(), null)
36+
val suggestedIndex =
37+
service.suggestedDiscardIndex(hand, emptyList()) { remaining, _ ->
38+
if (MahjongTile.EAST !in remaining) {
39+
GbTingResponse(true, listOf(GbTingCandidate("W1", 8)), null)
40+
} else {
41+
GbTingResponse(true, emptyList(), null)
42+
}
4143
}
42-
}
4344

4445
assertEquals(3, suggestedIndex)
4546
}
47+
48+
@Test
49+
fun `discard suggestion evaluates the first remaining hand once per duplicated tile`() {
50+
val service = GbBotDecisionService(8)
51+
val hand = listOf(MahjongTile.M1, MahjongTile.M2, MahjongTile.M1)
52+
val evaluatedHands = mutableListOf<List<MahjongTile>>()
53+
54+
service.suggestedDiscardIndex(hand, emptyList()) { remaining, _ ->
55+
evaluatedHands += remaining.toList()
56+
GbTingResponse(true, emptyList(), null)
57+
}
58+
59+
assertEquals(
60+
listOf(
61+
listOf(MahjongTile.M2, MahjongTile.M1),
62+
listOf(MahjongTile.M1, MahjongTile.M1),
63+
),
64+
evaluatedHands,
65+
)
66+
}
67+
68+
@Test
69+
fun `discard suggestion preserves null evaluator recomputation`() {
70+
val service = GbBotDecisionService(8)
71+
val hand = listOf(MahjongTile.M1, MahjongTile.M1, MahjongTile.M1)
72+
var evaluations = 0
73+
74+
service.suggestedDiscardIndex(hand, emptyList()) { _, _ ->
75+
evaluations++
76+
null
77+
}
78+
79+
assertEquals(hand.size, evaluations)
80+
}
4681
}

0 commit comments

Comments
 (0)