diff --git a/controller/AdminController.go b/controller/AdminController.go index eb8e96b..30c8809 100644 --- a/controller/AdminController.go +++ b/controller/AdminController.go @@ -135,6 +135,13 @@ func SyncAIBoards(w http.ResponseWriter, r *http.Request) { fmt.Println(w, "Team Ranks successfully generated.") } +func AllocateAIRedshirts(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + seasonID := vars["seasonID"] + managers.AllocateAIRedshirts(seasonID) + http.ResponseWriter(w).Write([]byte("Redshirts allocated to AI teams.")) +} + func RunTheGames(w http.ResponseWriter, r *http.Request) { managers.RunTheGames() fmt.Println(w, "Games for current week are set to run.") diff --git a/main.go b/main.go index 15fa42b..890cf47 100644 --- a/main.go +++ b/main.go @@ -114,6 +114,8 @@ func handleRequests() http.Handler { apiRouter.HandleFunc("/admin/recruiting/class/size", controller.GetRecruitingClassSizeForTeams).Methods("GET") apiRouter.HandleFunc("/admin/ai/fill/boards", controller.FillAIBoards).Methods("GET") apiRouter.HandleFunc("/admin/ai/sync/boards", controller.SyncAIBoards).Methods("GET") + // This endpoint should be used right before the redshirt deadline. + apiRouter.HandleFunc("/admin/ai/apply/redshirts/{seasonID}", controller.AllocateAIRedshirts).Methods("GET") // apiRouter.HandleFunc("/admin/fix/affinities", controller.RecalibrateCrootProfiles).Methods("GET") apiRouter.HandleFunc("/admin/fix/recruit/points", controller.RecalibrateRecruitPoints).Methods("GET") apiRouter.HandleFunc("/admin/run/the/games/", controller.RunTheGames).Methods("GET") diff --git a/managers/SyncManager.go b/managers/SyncManager.go index c3faeb3..a938e6b 100644 --- a/managers/SyncManager.go +++ b/managers/SyncManager.go @@ -7,6 +7,7 @@ import ( "math/rand" "sort" "strconv" + "strings" "time" "github.com/CalebRose/SimFBA/dbprovider" @@ -1191,6 +1192,74 @@ func getRecruitingOdds(ts structs.Timestamp, croot structs.Recruit, team structs } } +func AllocateAIRedshirts(seasonId string) { + allAICollegeTeams := GetAllAvailableCollegeTeams() + seasonStatsMap := GetCFBPlayerSeasonStatMapBySeason(seasonId, "2") + + for _, team := range allAICollegeTeams { + log.Printf("\nAllocating redshirts for %s\n", team.TeamAbbr) + redshirtTargets := GetAllCollegePlayersByTeamId(strconv.Itoa(int(team.ID))) + positionCountMap := getPositionCounts(redshirtTargets) + + redshirtCount := 0 + redshirts := make([]string, 20) + for _, target := range redshirtTargets { + if redshirtCount >= 20 { + break + } + + playerSeasonStats := seasonStatsMap[uint(target.PlayerID)] + + /* + * Redshirt top 20 players that have never been redshirted and either have no snaps or a long-term injury this season. + * Also skips players if redshirting would put the team below that position minimum. + * GetAllCollegePlayersByTeamId returns players sorted by OVR by default. + */ + if (playerSeasonStats.GamesPlayed == 0 || + (target.InjuryType != "" && target.WeeksOfRecovery >= 10)) && + !target.IsRedshirt && !target.IsRedshirting && + isAboveMinPositionCount(target.Position, positionCountMap) { + + redshirts[redshirtCount] = fmt.Sprintf("%s %s %s %s, GamesPlayed: %d, Injury Weeks: %d\n", team.TeamAbbr, target.Position, target.FirstName, target.LastName, playerSeasonStats.GamesPlayed, target.WeeksOfRecovery) + SetRedshirtStatusForPlayer(strconv.Itoa(target.TeamID)) + positionCountMap[target.Position] -= 1 + redshirtCount++ + } + } + log.Printf("Redshirts for %s:\n%s\n", team.TeamAbbr, strings.Join(redshirts, "")) + } +} + +func getPositionCounts(players []structs.CollegePlayer) map[string]int { + counts := make(map[string]int) + for _, player := range players { + counts[player.Position]++ + } + return counts +} + +func isAboveMinPositionCount(position string, positionCountMap map[string]int) bool { + minPositionThreshold := 0; + + switch position { + case "ATH": + minPositionThreshold = 0 + case "P", "K": + minPositionThreshold = 1 + case "QB", "RB", "FB", "TE", "FS", "SS", "C": + minPositionThreshold = 3 + case "OT", "OG", "DE", "DT", "OLB", "ILB": + minPositionThreshold = 4 + case "WR", "CB": + minPositionThreshold = 5 + default: + panic(fmt.Sprintf("Invalid position %s found in redshirt logic", position)) + } + + positionCount := positionCountMap[position] + return positionCount > minPositionThreshold +} + func getCoachMap() map[uint]structs.CollegeCoach { coachMap := make(map[uint]structs.CollegeCoach)