c4caed6569
Build and Push Docker Image / build (push) Successful in 3m50s
Update the Round 2 matches in the football tool defaults to reflect actual final scores, marking all 24 matches as completed with correct results.
212 lines
9.3 KiB
Go
212 lines
9.3 KiB
Go
package football
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"toolbox/pkg/base"
|
|
"toolbox/pkg/football/logic"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type footballTool struct{}
|
|
|
|
func init() {
|
|
base.Register(&footballTool{})
|
|
}
|
|
|
|
func (t *footballTool) ID() string { return "football" }
|
|
func (t *footballTool) Name() string { return "足球概率模拟" }
|
|
func (t *footballTool) Description() string {
|
|
return "使用蒙特卡洛算法模拟2026年美加墨世界杯小组赛阶段的出线概率"
|
|
}
|
|
func (t *footballTool) Emoji() string { return "⚽" }
|
|
|
|
func (t *footballTool) Init() error {
|
|
fmt.Println("Initializing Football Tool...")
|
|
return nil
|
|
}
|
|
|
|
func (t *footballTool) RegisterRoutes(r *gin.RouterGroup) {
|
|
r.GET("/defaults", t.handleDefaults)
|
|
r.POST("/simulate", t.handleSimulate)
|
|
}
|
|
|
|
type SimulateRequest struct {
|
|
Groups map[string][]string `json:"groups"`
|
|
CompletedMatches []logic.Match `json:"completed_matches"`
|
|
ScoreOutcomes []logic.ScoreOutcome `json:"score_outcomes"`
|
|
Simulations int `json:"simulations"`
|
|
}
|
|
|
|
func (t *footballTool) handleDefaults(c *gin.Context) {
|
|
defaultGroups := map[string][]string{
|
|
"A": {"MEX", "RSA", "KOR", "CZE"},
|
|
"B": {"CAN", "QAT", "SUI", "BIH"},
|
|
"C": {"BRA", "MAR", "HAI", "SCO"},
|
|
"D": {"USA", "PAR", "AUS", "TUR"},
|
|
"E": {"GER", "CUW", "CIV", "ECU"},
|
|
"F": {"NED", "JPN", "TUN", "SWE"},
|
|
"G": {"BEL", "EGY", "IRN", "NZL"},
|
|
"H": {"ESP", "KSA", "URU", "CPV"},
|
|
"I": {"FRA", "SEN", "IRQ", "NOR"},
|
|
"J": {"ARG", "ALG", "AUT", "JOR"},
|
|
"K": {"POR", "COL", "UZB", "COD"},
|
|
"L": {"ENG", "CRO", "GHA", "PAN"},
|
|
}
|
|
|
|
defaultMatches := []logic.Match{
|
|
// === ROUND 1 ===
|
|
{TeamA: "MEX", TeamB: "RSA", IsCompleted: true, ScoreA: 2, ScoreB: 0}, // Group A
|
|
{TeamA: "KOR", TeamB: "CZE", IsCompleted: true, ScoreA: 2, ScoreB: 1}, // Group A
|
|
{TeamA: "CAN", TeamB: "BIH", IsCompleted: true, ScoreA: 1, ScoreB: 1}, // Group B
|
|
{TeamA: "USA", TeamB: "PAR", IsCompleted: true, ScoreA: 4, ScoreB: 1}, // Group D
|
|
{TeamA: "QAT", TeamB: "SUI", IsCompleted: true, ScoreA: 1, ScoreB: 1}, // Group B
|
|
{TeamA: "BRA", TeamB: "MAR", IsCompleted: true, ScoreA: 1, ScoreB: 1}, // Group C
|
|
{TeamA: "SCO", TeamB: "HAI", IsCompleted: true, ScoreA: 1, ScoreB: 0}, // Group C
|
|
{TeamA: "AUS", TeamB: "TUR", IsCompleted: true, ScoreA: 2, ScoreB: 0}, // Group D
|
|
{TeamA: "GER", TeamB: "CUW", IsCompleted: true, ScoreA: 7, ScoreB: 1}, // Group E
|
|
{TeamA: "NED", TeamB: "JPN", IsCompleted: true, ScoreA: 2, ScoreB: 2}, // Group F
|
|
{TeamA: "CIV", TeamB: "ECU", IsCompleted: true, ScoreA: 1, ScoreB: 0}, // Group E
|
|
{TeamA: "SWE", TeamB: "TUN", IsCompleted: true, ScoreA: 5, ScoreB: 1}, // Group F
|
|
{TeamA: "ESP", TeamB: "CPV", IsCompleted: true, ScoreA: 0, ScoreB: 0}, // Group H
|
|
{TeamA: "BEL", TeamB: "EGY", IsCompleted: true, ScoreA: 1, ScoreB: 1}, // Group G
|
|
{TeamA: "KSA", TeamB: "URU", IsCompleted: true, ScoreA: 1, ScoreB: 1}, // Group H
|
|
{TeamA: "IRN", TeamB: "NZL", IsCompleted: true, ScoreA: 2, ScoreB: 2}, // Group G
|
|
{TeamA: "FRA", TeamB: "SEN", IsCompleted: true, ScoreA: 3, ScoreB: 1}, // Group I
|
|
{TeamA: "IRQ", TeamB: "NOR", IsCompleted: true, ScoreA: 1, ScoreB: 4}, // Group I
|
|
{TeamA: "ARG", TeamB: "ALG", IsCompleted: true, ScoreA: 3, ScoreB: 0}, // Group J
|
|
{TeamA: "AUT", TeamB: "JOR", IsCompleted: true, ScoreA: 3, ScoreB: 1}, // Group J
|
|
{TeamA: "POR", TeamB: "COD", IsCompleted: true, ScoreA: 1, ScoreB: 1}, // Group K
|
|
{TeamA: "ENG", TeamB: "CRO", IsCompleted: true, ScoreA: 4, ScoreB: 2}, // Group L
|
|
{TeamA: "GHA", TeamB: "PAN", IsCompleted: true, ScoreA: 1, ScoreB: 0}, // Group L
|
|
{TeamA: "UZB", TeamB: "COL", IsCompleted: true, ScoreA: 1, ScoreB: 3}, // Group K
|
|
|
|
// === ROUND 2 ===
|
|
{TeamA: "CZE", TeamB: "RSA", IsCompleted: true, ScoreA: 1, ScoreB: 1}, // Group A
|
|
{TeamA: "SUI", TeamB: "BIH", IsCompleted: true, ScoreA: 4, ScoreB: 1}, // Group B
|
|
{TeamA: "CAN", TeamB: "QAT", IsCompleted: true, ScoreA: 6, ScoreB: 0}, // Group B
|
|
{TeamA: "MEX", TeamB: "KOR", IsCompleted: true, ScoreA: 1, ScoreB: 0}, // Group A
|
|
{TeamA: "USA", TeamB: "AUS", IsCompleted: true, ScoreA: 2, ScoreB: 0}, // Group D
|
|
{TeamA: "SCO", TeamB: "MAR", IsCompleted: true, ScoreA: 0, ScoreB: 1}, // Group C
|
|
{TeamA: "BRA", TeamB: "HAI", IsCompleted: true, ScoreA: 3, ScoreB: 0}, // Group C
|
|
{TeamA: "TUR", TeamB: "PAR", IsCompleted: true, ScoreA: 0, ScoreB: 1}, // Group D
|
|
{TeamA: "NED", TeamB: "SWE", IsCompleted: true, ScoreA: 5, ScoreB: 1}, // Group F
|
|
{TeamA: "GER", TeamB: "CIV", IsCompleted: true, ScoreA: 2, ScoreB: 1}, // Group E
|
|
{TeamA: "ECU", TeamB: "CUW", IsCompleted: true, ScoreA: 0, ScoreB: 0}, // Group E
|
|
{TeamA: "TUN", TeamB: "JPN", IsCompleted: true, ScoreA: 0, ScoreB: 4}, // Group F
|
|
{TeamA: "ESP", TeamB: "KSA", IsCompleted: true, ScoreA: 4, ScoreB: 0}, // Group H
|
|
{TeamA: "BEL", TeamB: "IRN", IsCompleted: true, ScoreA: 0, ScoreB: 0}, // Group G
|
|
{TeamA: "URU", TeamB: "CPV", IsCompleted: true, ScoreA: 2, ScoreB: 2}, // Group H
|
|
{TeamA: "NZL", TeamB: "EGY", IsCompleted: true, ScoreA: 1, ScoreB: 3}, // Group G
|
|
{TeamA: "ARG", TeamB: "AUT", IsCompleted: true, ScoreA: 2, ScoreB: 0}, // Group J
|
|
{TeamA: "FRA", TeamB: "IRQ", IsCompleted: true, ScoreA: 3, ScoreB: 0}, // Group I
|
|
{TeamA: "NOR", TeamB: "SEN", IsCompleted: true, ScoreA: 3, ScoreB: 2}, // Group I
|
|
{TeamA: "JOR", TeamB: "ALG", IsCompleted: true, ScoreA: 1, ScoreB: 2}, // Group J
|
|
{TeamA: "POR", TeamB: "UZB", IsCompleted: true, ScoreA: 5, ScoreB: 0}, // Group K
|
|
{TeamA: "ENG", TeamB: "GHA", IsCompleted: true, ScoreA: 0, ScoreB: 0}, // Group L
|
|
{TeamA: "PAN", TeamB: "CRO", IsCompleted: true, ScoreA: 0, ScoreB: 1}, // Group L
|
|
{TeamA: "COL", TeamB: "COD", IsCompleted: true, ScoreA: 1, ScoreB: 0}, // Group K
|
|
|
|
// === ROUND 3 ===
|
|
{TeamA: "SUI", TeamB: "CAN", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group B
|
|
{TeamA: "BIH", TeamB: "QAT", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group B
|
|
{TeamA: "SCO", TeamB: "BRA", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group C
|
|
{TeamA: "MAR", TeamB: "HAI", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group C
|
|
{TeamA: "CZE", TeamB: "MEX", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group A
|
|
{TeamA: "RSA", TeamB: "KOR", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group A
|
|
{TeamA: "CUW", TeamB: "CIV", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group E
|
|
{TeamA: "ECU", TeamB: "GER", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group E
|
|
{TeamA: "JPN", TeamB: "SWE", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group F
|
|
{TeamA: "TUN", TeamB: "NED", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group F
|
|
{TeamA: "TUR", TeamB: "USA", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group D
|
|
{TeamA: "PAR", TeamB: "AUS", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group D
|
|
{TeamA: "NOR", TeamB: "FRA", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group I
|
|
{TeamA: "SEN", TeamB: "IRQ", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group I
|
|
{TeamA: "CPV", TeamB: "KSA", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group H
|
|
{TeamA: "URU", TeamB: "ESP", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group H
|
|
{TeamA: "EGY", TeamB: "IRN", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group G
|
|
{TeamA: "NZL", TeamB: "BEL", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group G
|
|
{TeamA: "PAN", TeamB: "ENG", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group L
|
|
{TeamA: "CRO", TeamB: "GHA", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group L
|
|
{TeamA: "COL", TeamB: "POR", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group K
|
|
{TeamA: "COD", TeamB: "UZB", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group K
|
|
{TeamA: "ALG", TeamB: "AUT", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group J
|
|
{TeamA: "JOR", TeamB: "ARG", IsCompleted: false, ScoreA: 0, ScoreB: 0}, // Group J
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"groups": defaultGroups,
|
|
"completed_matches": defaultMatches,
|
|
"score_outcomes": logic.DefaultScoreOutcomes(),
|
|
})
|
|
}
|
|
|
|
func (t *footballTool) handleSimulate(c *gin.Context) {
|
|
var req SimulateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if len(req.Groups) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Groups map cannot be empty"})
|
|
return
|
|
}
|
|
|
|
// Reconstruct Teams
|
|
var teams []logic.Team
|
|
for grp, grpTeams := range req.Groups {
|
|
for _, teamName := range grpTeams {
|
|
teams = append(teams, logic.Team{Name: teamName, Group: grp})
|
|
}
|
|
}
|
|
|
|
// Generate all group matches (completed + uncompleted)
|
|
var allMatches []logic.Match
|
|
for _, grpTeams := range req.Groups {
|
|
for i := 0; i < len(grpTeams); i++ {
|
|
for j := i + 1; j < len(grpTeams); j++ {
|
|
tA := grpTeams[i]
|
|
tB := grpTeams[j]
|
|
|
|
// Check if this match is completed in input
|
|
isCompleted := false
|
|
scoreA, scoreB := 0, 0
|
|
for _, cm := range req.CompletedMatches {
|
|
if !cm.IsCompleted {
|
|
continue
|
|
}
|
|
if cm.TeamA == tA && cm.TeamB == tB {
|
|
isCompleted = true
|
|
scoreA = cm.ScoreA
|
|
scoreB = cm.ScoreB
|
|
break
|
|
} else if cm.TeamA == tB && cm.TeamB == tA {
|
|
isCompleted = true
|
|
scoreA = cm.ScoreB
|
|
scoreB = cm.ScoreA
|
|
break
|
|
}
|
|
}
|
|
|
|
allMatches = append(allMatches, logic.Match{
|
|
TeamA: tA,
|
|
TeamB: tB,
|
|
IsCompleted: isCompleted,
|
|
ScoreA: scoreA,
|
|
ScoreB: scoreB,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
sims := req.Simulations
|
|
if sims <= 0 {
|
|
sims = 50000
|
|
}
|
|
|
|
results := logic.RunMonteCarlo(teams, allMatches, req.ScoreOutcomes, sims)
|
|
c.JSON(http.StatusOK, results)
|
|
}
|