Files
own-tools/pkg/football/tool.go
T
haopengzhan c4caed6569
Build and Push Docker Image / build (push) Successful in 3m50s
feat: update round 2 match results with completed scores
Update the Round 2 matches in the football tool defaults to reflect actual final scores, marking all 24 matches as completed with correct results.
2026-06-23 23:37:30 -07:00

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