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) } } }