package ai import ( "testing" "nl-game-api-gin/internal/gamecore/ddz" ) // card 按点数造一张牌(花色取 0-3 循环,保证同点数多张时编码不同) // 点数 r:3-15(15=2),16=小王,17=大王 func card(r, i int) int { if r == 16 { return 52 } if r == 17 { return 53 } return (r-3)*4 + i } // TestFarmerNotBeatPartner 平民不压队友:队友出的中等牌(J 以上)应选择过牌 func TestFarmerNotBeatPartner(t *testing.T) { a := &ruleDdzAI{difficulty: DiffMedium, name: "测试"} // 手牌里有 2(能压 J)但队友出的 J 不该压 hand := []int{card(15, 0), card(10, 0), card(5, 0), card(5, 1)} last := ddz.Parse([]int{card(11, 0)}) // 队友出单张 J pick, _ := a.DecidePlay(hand, last, PlayContext{ IsLandlord: false, LastIsPartner: true, PartnerCards: 8, LandlordCards: 10, OppMin: 10, }) if pick != nil { t.Fatalf("平民不应压队友的大牌,却出了 %v", pick) } } // TestFarmerBeatPartnerSmall 队友前期小牌可以用小牌接管,但不能动 2/王/炸弹 func TestFarmerBeatPartnerSmall(t *testing.T) { a := &ruleDdzAI{difficulty: DiffMedium, name: "测试"} hand := []int{card(15, 0), card(8, 0), card(6, 0), card(6, 1)} last := ddz.Parse([]int{card(4, 0)}) // 队友出单张 4(早期小牌) pick, _ := a.DecidePlay(hand, last, PlayContext{ IsLandlord: false, LastIsPartner: true, PartnerCards: 10, LandlordCards: 12, OppMin: 12, }) if pick == nil { t.Fatal("队友的小牌应该用小牌接管节奏") } if c := ddz.Parse(pick); c.MainRank > 12 { t.Fatalf("接队友的牌不应动大牌,却出了主点数 %d", c.MainRank) } } // TestFarmerFinishOverridesCoop 能一手出完时即使是队友的牌也直接出完取胜 func TestFarmerFinishOverridesCoop(t *testing.T) { a := &ruleDdzAI{difficulty: DiffMedium, name: "测试"} hand := []int{card(14, 0)} // 只剩一张 A last := ddz.Parse([]int{card(11, 0)}) // 队友出单张 J pick, _ := a.DecidePlay(hand, last, PlayContext{ IsLandlord: false, LastIsPartner: true, PartnerCards: 3, LandlordCards: 9, OppMin: 9, }) if len(pick) != 1 { t.Fatalf("能直接出完应立即取胜,实际出牌 %v", pick) } } // TestFarmerFeedsPartnerSingle 队友只剩 1 张时,平民自由出牌应喂最小单张 func TestFarmerFeedsPartnerSingle(t *testing.T) { a := &ruleDdzAI{difficulty: DiffMedium, name: "测试"} hand := []int{card(15, 0), card(12, 0), card(12, 1), card(3, 0)} pick, _ := a.DecidePlay(hand, nil, PlayContext{ IsLandlord: false, LastIsPartner: false, PartnerCards: 1, LandlordCards: 8, OppMin: 8, }) c := ddz.Parse(pick) if c == nil || c.Type != ddz.ComboSingle { t.Fatalf("队友剩 1 张应喂单张,实际 %v", pick) } if c.MainRank != 3 { t.Fatalf("应喂最小单张 3,实际主点数 %d", c.MainRank) } } // TestFarmerFeedsPartnerPair 队友只剩 2 张时优先喂最小对子 func TestFarmerFeedsPartnerPair(t *testing.T) { a := &ruleDdzAI{difficulty: DiffMedium, name: "测试"} hand := []int{card(15, 0), card(9, 0), card(9, 1), card(4, 0), card(4, 1)} pick, _ := a.DecidePlay(hand, nil, PlayContext{ IsLandlord: false, LastIsPartner: false, PartnerCards: 2, LandlordCards: 8, OppMin: 8, }) c := ddz.Parse(pick) if c == nil || c.Type != ddz.ComboPair { t.Fatalf("队友剩 2 张应喂对子,实际 %v", pick) } if c.MainRank != 4 { t.Fatalf("应喂最小对子 4,实际主点数 %d", c.MainRank) } } // TestFarmerBlocksLandlordEndgame 地主只剩 1 张时,平民领出应避免送小单牌 func TestFarmerBlocksLandlordEndgame(t *testing.T) { a := &ruleDdzAI{difficulty: DiffMedium, name: "测试"} // 手牌:小单 3、对 7、大单 A hand := []int{card(14, 0), card(7, 0), card(7, 1), card(3, 0)} pick, _ := a.DecidePlay(hand, nil, PlayContext{ IsLandlord: false, LastIsPartner: false, PartnerCards: 10, LandlordCards: 1, OppMin: 1, }) c := ddz.Parse(pick) if c == nil { t.Fatal("应有出牌") } // 不应送出最小单张 3(地主最后一张很可能压过) if c.Type == ddz.ComboSingle && c.MainRank == 3 { t.Fatal("地主只剩 1 张时不应送最小单牌") } } // TestLandlordUnchanged 地主视角:正常出能压住的最小牌,不受配合逻辑影响 func TestLandlordUnchanged(t *testing.T) { a := &ruleDdzAI{difficulty: DiffMedium, name: "测试"} hand := []int{card(15, 0), card(10, 0), card(6, 0)} last := ddz.Parse([]int{card(5, 0)}) pick, _ := a.DecidePlay(hand, last, PlayContext{ IsLandlord: true, LandlordCards: 3, OppMin: 10, }) c := ddz.Parse(pick) if c == nil || c.MainRank != 6 { t.Fatalf("地主应出能压住的最小牌 6,实际 %v", pick) } }