bindbox-game/internal/api/activity/matching_game_verify_test.go
邹方成 e2782a69d3 feat: 添加对对碰游戏功能与Redis支持
refactor: 重构抽奖逻辑以支持可验证凭据
feat(redis): 集成Redis客户端并添加配置支持
fix: 修复订单取消时的优惠券和库存处理逻辑
docs: 添加对对碰游戏前端对接指南和示例JSON
test: 添加对对碰游戏模拟测试和验证逻辑
2025-12-21 17:31:32 +08:00

138 lines
3.5 KiB
Go

package app
import (
"fmt"
"testing"
)
// SimulateClientPlay mimics the frontend logic: Match -> Eliminate -> Fill -> Reshuffle
func SimulateClientPlay(initialCards []*MatchingCard) int {
// Deep copy to avoid modifying original test data
deck := make([]*MatchingCard, len(initialCards))
copy(deck, initialCards)
board := make([]*MatchingCard, 9)
// Initial fill
for i := 0; i < 9; i++ {
board[i] = deck[0]
deck = deck[1:]
}
pairsFound := 0
for {
// 1. Check for matches on board
counts := make(map[string][]int) // type -> list of indices
for i, c := range board {
if c != nil {
// Cast CardType to string for map key
counts[string(c.Type)] = append(counts[string(c.Type)], i)
}
}
matchedType := ""
for t, indices := range counts {
if len(indices) >= 2 {
matchedType = t
break
}
}
if matchedType != "" {
// Elimination
pairsFound++
indices := counts[matchedType]
// Remove first 2
idx1, idx2 := indices[0], indices[1]
board[idx1] = nil
board[idx2] = nil
// Filling: Fill empty slots from deck
for i := 0; i < 9; i++ {
if board[i] == nil && len(deck) > 0 {
board[i] = deck[0]
deck = deck[1:]
}
}
} else {
// Deadlock (No matches on board)
// User requirement: "Stop when no pairs can be generated" (i.e., No Reshuffle)
// If we are stuck, we stop.
break
}
}
return pairsFound
}
// TestVerification_DataIntegrity simulates the PreOrder logic 10000 times
func TestVerification_DataIntegrity(t *testing.T) {
fmt.Println("=== Starting Full Game Simulation (10000 Runs) ===")
// Using 10k runs to keep test time reasonable
configs := []CardTypeConfig{
{Code: "A", Name: "TypeA", Quantity: 9},
{Code: "B", Name: "TypeB", Quantity: 9},
{Code: "C", Name: "TypeC", Quantity: 9},
{Code: "D", Name: "TypeD", Quantity: 9},
{Code: "E", Name: "TypeE", Quantity: 9},
{Code: "F", Name: "TypeF", Quantity: 9},
{Code: "G", Name: "TypeG", Quantity: 9},
{Code: "H", Name: "TypeH", Quantity: 9},
{Code: "I", Name: "TypeI", Quantity: 9},
{Code: "J", Name: "TypeJ", Quantity: 9},
{Code: "K", Name: "TypeK", Quantity: 9},
}
scoreDist := make(map[int]int)
for i := 0; i < 10000; i++ {
// 1. Simulate PreOrder generation
game := NewMatchingGameWithConfig(configs, fmt.Sprintf("pos_%d", i))
// 2. Reconstruct "all_cards"
allCards := make([]*MatchingCard, 0, 99)
for _, c := range game.Board {
if c != nil {
allCards = append(allCards, c)
}
}
allCards = append(allCards, game.Deck...)
// 3. Play the game!
score := SimulateClientPlay(allCards)
scoreDist[score]++
// Note: Without reshuffle, score < 44 is expected.
}
// Calculate Stats
totalScore := 0
var allScores []int
for s := 0; s <= 44; s++ {
count := scoreDist[s]
for c := 0; c < count; c++ {
allScores = append(allScores, s)
totalScore += s
}
}
mean := float64(totalScore) / float64(len(allScores))
median := allScores[len(allScores)/2]
fmt.Println("\n=== No-Reshuffle Statistical Analysis (10000 Runs) ===")
fmt.Printf("Mean Score: %.2f / 44\n", mean)
fmt.Printf("Median Score: %d / 44\n", median)
fmt.Printf("Pass Rate: %.2f%%\n", float64(scoreDist[44])/100.0)
fmt.Println("------------------------------------------------")
// Output Distribution Segments
fmt.Println("Detailed Distribution:")
cumulative := 0
for s := 0; s <= 44; s++ {
count := scoreDist[s]
if count > 0 {
cumulative += count
fmt.Printf("Score %d: %d times (%.2f%%) [Cum: %.2f%%]\n", s, count, float64(count)/100.0, float64(cumulative)/100.0)
}
}
}