diff --git a/controller/CronController.go b/controller/CronController.go index a0997a3..dbf49d8 100644 --- a/controller/CronController.go +++ b/controller/CronController.go @@ -6,6 +6,7 @@ import ( "github.com/CalebRose/SimFBA/dbprovider" "github.com/CalebRose/SimFBA/managers" "github.com/CalebRose/SimFBA/repository" + "github.com/CalebRose/SimFBA/structs" ) func CronTest() { @@ -46,6 +47,9 @@ func SyncRecruitingViaCron() { ts := managers.GetTimestamp() if ts.RunCron && !ts.IsOffSeason && !ts.CollegeSeasonOver && !ts.CFBSpringGames && ts.CollegeWeek > 0 && ts.CollegeWeek < 21 { managers.SyncRecruiting(ts) + if ts.CollegeWeek == 20 { + managers.GenerateWalkOns() + } } if ts.RunCron && ts.IsOffSeason && ts.TransferPortalPhase == 2 { managers.EnterTheTransferPortal() @@ -65,6 +69,39 @@ func SyncFreeAgencyViaCron() { } } +func RunCFBProgressionsViaCron() { + db := dbprovider.GetInstance().GetDB() + ts := managers.GetTimestamp() + if ts.CollegeWeek < 21 { + return + } + if ts.CollegeSeasonOver && !ts.ProgressedCollegePlayers { + // Reset progression flags on all teams and players before running + db.Model(&structs.CollegeTeam{}).Where("id > ?", 0).Updates(map[string]interface{}{"players_progressed": false, "recruits_added": false}) + db.Model(&structs.CollegePlayer{}).Where("id > ?", 0).Update("has_progressed", false) + + managers.CFBProgressionMain() + ts.ToggleCollegeProgression() + repository.SaveTimestamp(ts, db) + } +} + +func RunNFLProgressionsViaCron() { + db := dbprovider.GetInstance().GetDB() + ts := managers.GetTimestamp() + if ts.NFLWeek < 23 { + return + } + + if ts.NFLSeasonOver && !ts.ProgressedProfessionalPlayers { + // Reset progression flags on all teams and players before running + db.Model(&structs.NFLPlayer{}).Where("id > ?", 0).Update("has_progressed", false) + managers.NFLProgressionMain() + ts.ToggleProfessionalProgression() + repository.SaveTimestamp(ts, db) + } +} + func SyncToNextWeekViaCron() { ts := managers.GetTimestamp() if !ts.RunGames { @@ -75,20 +112,22 @@ func SyncToNextWeekViaCron() { ts = managers.MoveUpWeek() } managers.AssignTeamGrades() - if ts.NFLSeasonOver && ts.CollegeSeasonOver && !ts.IsNFLOffSeason && !ts.IsOffSeason { - if !ts.ProgressedCollegePlayers && !ts.ProgressedProfessionalPlayers { - // Progress College - ts.ToggleCollegeProgression() - // Progress NFL - - ts.ToggleProfessionalProgression() + // Once National Championship is over and we move up a week. + if ts.CollegeSeasonOver && ts.CollegeWeek == 21 { + // Sync Promises + managers.SyncPromises() + ts.TransferPortalPhase = 1 + ts.TransferPortalRound = 1 + db := dbprovider.GetInstance().GetDB() + repository.SaveTimestamp(ts, db) + } - db := dbprovider.GetInstance().GetDB() - ts.MoveUpSeason() - repository.SaveTimestamp(ts, db) - managers.GenerateOffseasonData() - } + if ts.NFLSeasonOver && ts.CollegeSeasonOver && !ts.IsNFLOffSeason && !ts.IsOffSeason && ts.ProgressedCollegePlayers && ts.ProgressedProfessionalPlayers { + db := dbprovider.GetInstance().GetDB() + ts.MoveUpSeason() + repository.SaveTimestamp(ts, db) + managers.GenerateOffseasonData() } } } diff --git a/main.go b/main.go index db9aac6..a89f0ef 100644 --- a/main.go +++ b/main.go @@ -117,10 +117,10 @@ func handleRequests() http.Handler { // 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/fix/recruit/points", controller.RecalibrateRecruitPoints).Methods("GET") apiRouter.HandleFunc("/admin/run/the/games/", controller.RunTheGames).Methods("GET") // apiRouter.HandleFunc("/admin/overall/progressions/next/season", controller.ProgressToNextSeason).Methods("GET") - apiRouter.HandleFunc("/admin/overall/progressions/nfl", controller.ProgressNFL).Methods("GET") + // apiRouter.HandleFunc("/admin/overall/progressions/nfl", controller.ProgressNFL).Methods("GET") apiRouter.HandleFunc("/admin/trades/accept/sync/{proposalID}", controller.SyncAcceptedTrade).Methods("GET") apiRouter.HandleFunc("/admin/trades/veto/sync/{proposalID}", controller.VetoAcceptedTrade).Methods("GET") apiRouter.HandleFunc("/admin/trades/cleanup", controller.CleanUpRejectedTrades).Methods("GET") @@ -282,7 +282,7 @@ func handleRequests() http.Handler { // Rankings Controls // apiRouter.HandleFunc("/simfba/cfb/croots/generate/", controller.GenerateRecruits).Methods("GET") - // apiRouter.HandleFunc("/rankings/assign/all/croots/", controller.AssignAllRecruitRanks).Methods("GET") + apiRouter.HandleFunc("/rankings/assign/all/croots/", controller.AssignAllRecruitRanks).Methods("GET") // Recruiting Controls apiRouter.HandleFunc("/recruiting/overview/dashboard/{teamID}", controller.GetRecruitingProfileForDashboardByTeamID).Methods("GET") @@ -467,26 +467,28 @@ func handleCron() *cron.Cron { // Run RES c.AddFunc("0 7 * * 4", controller.RunRESViaCron) // Sync Recruiting - c.AddFunc("0 17 * * 3", controller.SyncRecruitingViaCron) + c.AddFunc("0 16 * * 3", controller.SyncRecruitingViaCron) // Sync Free Agency c.AddFunc("0 16 * * *", controller.SyncFreeAgencyViaCron) // Sync Extension Offers // Run the Games c.AddFunc("0 4 * * 4", controller.RunTheGamesViaCron) // Reveal Timeslot Results - c.AddFunc("0 21 * * 4", controller.ShowCFBThursdayViaCron) // Thurs Night - c.AddFunc("0 20 * * 4", controller.ShowNFLThursdayViaCron) // Thurs NFL - c.AddFunc("0 21 * * 5", controller.ShowCFBFridayViaCron) // Fri Night - c.AddFunc("0 15 * * 6", controller.ShowCFBSatMornViaCron) // Sat. Morning - c.AddFunc("0 17 * * 6", controller.ShowCFBSatAftViaCron) // Sat. Afternoon - c.AddFunc("0 19 * * 6", controller.ShowCFBSatEveViaCron) // Sat. Evening - c.AddFunc("0 21 * * 6", controller.ShowCFBSatNitViaCron) // Sat. Night - c.AddFunc("0 15 * * 0", controller.ShowNFLSunNoonViaCron) // Sun Noon - c.AddFunc("0 17 * * 0", controller.ShowNFLSunAftViaCron) // Sun Aft - c.AddFunc("0 19 * * 0", controller.ShowNFLSunNitViaCron) // Sun Nit + c.AddFunc("0 22 * * 4", controller.ShowCFBThursdayViaCron) // Thurs Night + c.AddFunc("0 21 * * 4", controller.ShowNFLThursdayViaCron) // Thurs NFL + c.AddFunc("0 22 * * 5", controller.ShowCFBFridayViaCron) // Fri Night + c.AddFunc("0 16 * * 6", controller.ShowCFBSatMornViaCron) // Sat. Morning + c.AddFunc("0 18 * * 6", controller.ShowCFBSatAftViaCron) // Sat. Afternoon + c.AddFunc("0 20 * * 6", controller.ShowCFBSatEveViaCron) // Sat. Evening + c.AddFunc("0 22 * * 6", controller.ShowCFBSatNitViaCron) // Sat. Night + c.AddFunc("0 16 * * 0", controller.ShowNFLSunNoonViaCron) // Sun Noon + c.AddFunc("0 18 * * 0", controller.ShowNFLSunAftViaCron) // Sun Aft + c.AddFunc("0 20 * * 0", controller.ShowNFLSunNitViaCron) // Sun Nit c.AddFunc("0 17 * * 1", controller.ShowNFLMonNitViaCron) // Mon Nit // Sync Week c.AddFunc("0 18 * * 1", controller.SyncToNextWeekViaCron) + c.AddFunc("0 2 * * 2", controller.RunCFBProgressionsViaCron) + c.AddFunc("0 3 * * 2", controller.RunNFLProgressionsViaCron) } c.Start() diff --git a/managers/GenerationManager.go b/managers/GenerationManager.go index 885d7f8..a9b9319 100644 --- a/managers/GenerationManager.go +++ b/managers/GenerationManager.go @@ -477,7 +477,7 @@ func GenerateWalkOns() { // Recruit Generation for _, pos := range positionList { - if count >= walkonLimit { + if count >= walkonLimit || (count > 10 && team.TotalCommitments > 12) { break } @@ -516,10 +516,9 @@ func GenerateWalkOns() { recruitProfileBatchList = append(recruitProfileBatchList, recruitPlayerRecord) newID++ team.IncreaseCommitCount() - repository.SaveRecruitingTeamProfile(team, db) - } count = 0 + repository.SaveRecruitingTeamProfile(team, db) fmt.Println("Finished walkon generation for " + team.TeamAbbreviation) } repository.CreateFaceRecordsBatch(db, faces, 500) diff --git a/managers/MapHelper.go b/managers/MapHelper.go index c1b6f69..b0470c8 100644 --- a/managers/MapHelper.go +++ b/managers/MapHelper.go @@ -413,3 +413,13 @@ func MakeRecruitTeamProfileMapByTeamID(profiles []structs.RecruitingTeamProfile) return profileMap } + +func MakeHistoricCollegePlayerMap(players []structs.HistoricCollegePlayer) map[uint]structs.CollegePlayer { + profileMap := make(map[uint]structs.CollegePlayer) + + for _, rp := range players { + profileMap[rp.ID] = structs.CollegePlayer(rp) + } + + return profileMap +} diff --git a/managers/TransferPortalManager.go b/managers/TransferPortalManager.go index 34c78ac..b8ad00e 100644 --- a/managers/TransferPortalManager.go +++ b/managers/TransferPortalManager.go @@ -8,6 +8,7 @@ import ( "net/http" "sort" "strconv" + "strings" "sync" "github.com/CalebRose/SimFBA/constants" @@ -1881,3 +1882,196 @@ func getMajorNeedsMap() map[string]bool { return majorNeedsMap } + +// End of season run +// At end of season, sync through promises to confirm if promises were made +func SyncPromises() { + db := dbprovider.GetInstance().GetDB() + ts := GetTimestamp() + seasonID := strconv.Itoa(int(ts.CollegeSeasonID)) + teamProfileMap := GetTeamProfileMap() + activePromises := GetAllCollegePromises() + collegePlayerMap := GetCollegePlayerMap() + historicCollegePlayers := GetAllHistoricCollegePlayers() + historicPlayerMap := MakeHistoricCollegePlayerMap(historicCollegePlayers) + standingsMap := GetCollegeStandingsMap(seasonID) + seasonStatsMap := GetCollegePlayerSeasonStatsMap(seasonID, "2") + gameplanMap := GetCollegeGameplanMap() + + for _, promise := range activePromises { + if !promise.IsActive || !promise.PromiseMade { + continue + } + isHistoric := false + benchMarkStr := "" + result := "" + player := collegePlayerMap[promise.CollegePlayerID] + if player.ID == 0 { + player = historicPlayerMap[promise.CollegePlayerID] + if player.ID == 0 { + continue + } + isHistoric = true + } + // If player is already going to portal, carry on! + if player.TransferStatus == 2 { + // Remove promise since there was likely a preceding promise + repository.DeleteCollegePromise(promise, db) + continue + } + teamID := strconv.Itoa(int(promise.TeamID)) + team := teamProfileMap[teamID] + + seasonStats := seasonStatsMap[promise.CollegePlayerID] + if promise.PromiseType == "Wins" { + benchMarkStr = strconv.Itoa(int(promise.Benchmark)) + standings := standingsMap[promise.TeamID] + result = strconv.Itoa(int(standings.TotalWins)) + if standings.TotalWins >= promise.Benchmark { + promise.FulfillPromise() + } + } else if promise.PromiseType == "Snaps" { + benchMarkStr = strconv.Itoa(int(promise.Benchmark)) + snapsPerGame := float64(seasonStats.Snaps) / float64(seasonStats.GamesPlayed) + result = util.ConvertFloatTostring(snapsPerGame) + if snapsPerGame >= float64(promise.Benchmark) { + promise.FulfillPromise() + } + } else if promise.PromiseType == "Home State Game" || promise.PromiseType == "Different State" { + // Loop through games + benchMarkStr = promise.BenchmarkStr + result = "Did not play game in requested state." + games := GetCollegeGamesByTeamIdAndSeasonId(teamID, seasonID, false) + for _, game := range games { + stateKey := util.GetStateKey(promise.BenchmarkStr) + if game.State == stateKey || game.State == promise.BenchmarkStr { + result = "" + promise.FulfillPromise() + break + } + } + } else if promise.PromiseType == "No Redshirt" { + result = "Was Redshirted" + if !player.IsRedshirting { + result = "" + promise.FulfillPromise() + } + } else if promise.PromiseType == "National Championship" { + result = "Did not win the Natty." + standings := standingsMap[promise.TeamID] + if standings.PostSeasonStatus == "National Champion" { + result = "" + promise.FulfillPromise() + } + } else if promise.PromiseType == "Conference Championship" { + result = "Did not win Conference Championship" + standings := standingsMap[promise.TeamID] + if standings.IsConferenceChampion { + result = "" + promise.FulfillPromise() + } + } else if promise.PromiseType == "Specific Coach" { + // Fulfill for now, will need to adjust value + promise.FulfillPromise() + } else if promise.PromiseType == "Playoffs" { + standings := standingsMap[promise.TeamID] + postSeasonStatus := standings.PostSeasonStatus + // postSeasonStatus has substring "Round of" or postSeasonStatus == "Sweet 16" or "Elite 8" or "Final 4" or contains "National Champion", fullfill + if strings.Contains(postSeasonStatus, "Playoffs") || postSeasonStatus == "Semifinals" || postSeasonStatus == "Quarterfinals" || strings.Contains(postSeasonStatus, "National Champion") { + result = "" + promise.FulfillPromise() + } + } else if promise.PromiseType == "Good Gameplan Fit" { + gameplan := gameplanMap[promise.TeamID] + goodGameplanFit := IsGoodSchemeFit(gameplan.OffensiveScheme, gameplan.DefensiveScheme, player.Archetype, player.Position) + if goodGameplanFit { + result = "" + promise.FulfillPromise() + } + } else if promise.PromiseType == "Not Bad Gameplan Fit" { + gameplan := gameplanMap[promise.TeamID] + badSchemeFit := IsBadSchemeFit(gameplan.OffensiveScheme, gameplan.DefensiveScheme, player.Archetype, player.Position) + if !badSchemeFit { + result = "" + promise.FulfillPromise() + } + } + weightValue := getPromiseWeightValue(!promise.IsFullfilled, promise.PromiseWeight, team.PortalReputation) + team.AdjustPortalReputation(weightValue) + repository.SaveRecruitingTeamProfile(*team, db) + if !promise.IsFullfilled && !isHistoric { + message := "Breaking News! " + player.TeamAbbr + " " + player.FirstName + " " + player.LastName + " will be re-entering the portal after a promise was broken! Promise: " + promise.PromiseType + " | Expected: " + benchMarkStr + " | Result: " + result + player.WillTransfer() + repository.SaveCollegePlayerRecord(player, db) + CreateNewsLog("CFB", message, "Portal", int(team.TeamID), ts) + } + repository.DeleteCollegePromise(promise, db) + } +} + +func getPromiseWeightValue(isPenalty bool, weight string, reputation int) int { + switch weight { + case "Why even try?": + if isPenalty { + return -1 + } + return 1 + case "Low": + if isPenalty { + return -5 + } + return 3 + case "Extremely Low": + if isPenalty { + return -1 + } + return 1 + case "Very Low": + if isPenalty { + return -3 + } + return 2 + case "Medium": + if isPenalty { + return -10 + } + if reputation > 100 { + return 3 + } + return 8 + case "High": + if isPenalty { + return -20 + } + if reputation > 100 { + return 5 + } + return 15 + case "Very High": + if isPenalty { + return -30 + } + if reputation > 100 { + return 5 + } + return 20 + case "Extremely High": + if isPenalty { + return -35 + } + if reputation > 100 { + return 5 + } + return 25 + case "If you make this promise then you better win it!": + if isPenalty { + return -50 + } + if reputation > 100 { + return 10 + } + return 35 + } + + return 0 +} diff --git a/structs/CollegeStandings.go b/structs/CollegeStandings.go index abf211b..3eec7e8 100644 --- a/structs/CollegeStandings.go +++ b/structs/CollegeStandings.go @@ -1,21 +1,26 @@ package structs -import "github.com/jinzhu/gorm" +import ( + "strings" + + "github.com/jinzhu/gorm" +) type CollegeStandings struct { gorm.Model - TeamID int - TeamName string - SeasonID int - Season int - LeagueID uint - LeagueName string - ConferenceID int - ConferenceName string - DivisionID int - PostSeasonStatus string - IsFBS bool - Rank uint + TeamID int + TeamName string + SeasonID int + Season int + LeagueID uint + LeagueName string + ConferenceID int + ConferenceName string + DivisionID int + PostSeasonStatus string + IsFBS bool + Rank uint + IsConferenceChampion bool BaseStandings } @@ -28,7 +33,7 @@ func (ns *CollegeStandings) CalculatePercentages() { ns.TotalWinPercentage = 0 } if totalConfGames > 0 { - ns.ConfWinPercentage = float32(ns.TotalWins) / float32(totalConfGames) + ns.ConfWinPercentage = float32(ns.ConferenceWins) / float32(totalConfGames) } else { ns.ConfWinPercentage = 0 } @@ -37,6 +42,18 @@ func (ns *CollegeStandings) CalculatePercentages() { func (cs *CollegeStandings) UpdateCollegeStandings(game CollegeGame) { isAway := cs.TeamID == game.AwayTeamID winner := (!isAway && game.HomeTeamWin) || (isAway && game.AwayTeamWin) + if winner && strings.Contains(game.GameTitle, "Championship") && game.IsConferenceChampionship { + cs.IsConferenceChampion = true + } else if winner && strings.Contains(game.GameTitle, "Championship") && game.IsNationalChampionship { + cs.PostSeasonStatus = "National Champion" + } else if winner && strings.Contains(game.GameTitle, "Round 1") && game.IsPlayoffGame { + cs.PostSeasonStatus = "Playoffs" + } else if winner && strings.Contains(game.GameTitle, "Bowl") && game.IsPlayoffGame && game.Week == 18 { + cs.PostSeasonStatus = "Quarterfinals" + } else if winner && strings.Contains(game.GameTitle, "Bowl") && game.IsPlayoffGame && game.Week == 19 { + cs.PostSeasonStatus = "Semifinals" + } + if winner { cs.TotalWins += 1 if isAway { diff --git a/structs/TeamRecruitingProfile.go b/structs/TeamRecruitingProfile.go index 1286768..aa4cc58 100644 --- a/structs/TeamRecruitingProfile.go +++ b/structs/TeamRecruitingProfile.go @@ -88,6 +88,13 @@ func (r *RecruitingTeamProfile) ResetScholarshipCount() { func (r *RecruitingTeamProfile) AdjustPortalReputation(points int) { r.PortalReputation = r.PortalReputation + points + if r.PortalReputation < 0 { + r.PortalReputation = 1 + } + if r.PortalReputation > 120 { + r.PortalReputation = 120 + } + } func (r *RecruitingTeamProfile) AllocateSpentPoints(points float64) { diff --git a/structs/Timestamp.go b/structs/Timestamp.go index 3b28ed1..2ea6afd 100644 --- a/structs/Timestamp.go +++ b/structs/Timestamp.go @@ -76,7 +76,7 @@ func (t *Timestamp) MoveUpWeekNFL() { t.NFLWeekID -= 4 t.NFLPreseason = false } - if t.NFLWeek > 21 { + if t.NFLWeek > 22 { t.NFLSeasonOver = true } } @@ -216,7 +216,6 @@ func (t *Timestamp) TogglePollRan() { } func (t *Timestamp) EndTheCollegeSeason() { - t.IsOffSeason = true t.TransferPortalPhase = 1 t.CollegeSeasonOver = true } diff --git a/util/SyncUtil.go b/util/SyncUtil.go index fceb801..2b91085 100644 --- a/util/SyncUtil.go +++ b/util/SyncUtil.go @@ -164,6 +164,10 @@ func ConvertStringToFloat(num string) float64 { return floatNum } +func ConvertFloatTostring(num float64) string { + return strconv.FormatFloat(num, 'f', -1, 64) +} + // Reads specific CSV values as Boolean. If the value is "0" or "FALSE" or "False", it will be read as false. Anything else is considered True. func ConvertStringToBool(str string) bool { if str == "NULL" || str == "0" || str == "FALSE" || str == "False" { @@ -263,3 +267,65 @@ func IsCrootCloseToHome(crootState string, crootCity string, teamState string, a } return false } + +func GetStateKey(key string) string { + if len(key) == 2 { + return key + } + stateMap := map[string]string{ + "Alaska": "AK", + "California": "CA", + "Hawai'i": "HI", + "Hawaii": "HI", + "Idaho": "ID", + "Nevada": "NV", + "Oregon": "OR", + "Utah": "UT", + "Washington": "WA", + "Arizona": "AZ", + "Arkansas": "AR", + "New Mexico": "NM", + "Oklahoma": "OK", + "Texas": "TX", + "Colorado": "CO", + "Kansas": "KS", + "Montana": "MT", + "Nebraska": "NE", + "North Dakota": "ND", + "South Dakota": "SD", + "Wyoming": "WY", + "Illinois": "IL", + "Indiana": "IN", + "Iowa": "IA", + "Kentucky": "KY", + "Michigan": "MI", + "Minnesota": "MN", + "Missouri": "MO", + "Ohio": "OH", + "Wisconsin": "WI", + "Alabama": "AL", + "Florida": "FL", + "Georgia": "GA", + "Louisiana": "LA", + "Mississippi": "MS", + "North Carolina": "NC", + "South Carolina": "SC", + "Tennessee": "TN", + "Delaware": "DE", + "Maryland": "MD", + "New Jersey": "NJ", + "New York": "NY", + "Pennsylvania": "PA", + "Virginia": "VA", + "West Virginia": "WV", + "District of Columbia": "DC", + "DC": "DC", + "Connecticut": "CT", + "Maine": "ME", + "Massachusetts": "MA", + "New Hampshire": "NH", + "Rhode Island": "RI", + "Vermont": "VT", + } + return stateMap[key] +}