From 6e3ffc2ebc29be1228a9e2a3fde2d22f7d005710 Mon Sep 17 00:00:00 2001 From: jedibob5 Date: Thu, 28 Aug 2025 22:01:48 -0500 Subject: [PATCH 1/4] Add training camp logic to API --- controller/TrainingCampController.go | 4 +- main.go | 2 +- managers/TrainingCampManager.go | 894 ++++++++++++++++++++++++++- 3 files changed, 894 insertions(+), 6 deletions(-) diff --git a/controller/TrainingCampController.go b/controller/TrainingCampController.go index 0ada0926..d4013d36 100644 --- a/controller/TrainingCampController.go +++ b/controller/TrainingCampController.go @@ -8,8 +8,8 @@ import ( ) // GetAllCollegeTeamsForRosterPage -func UploadTrainingCampCSVData(w http.ResponseWriter, r *http.Request) { - managers.UploadTrainingCampCSV() +func RunTrainingCamps(w http.ResponseWriter, r *http.Request) { + managers.RunTrainingCamps() json.NewEncoder(w).Encode("Training Camp Complete") } diff --git a/main.go b/main.go index c4d135fb..08af09eb 100644 --- a/main.go +++ b/main.go @@ -343,7 +343,7 @@ func handleRequests() http.Handler { apiRouter.HandleFunc("/trades/nfl/proposal/cancel/{proposalID}", controller.CancelTradeOffer).Methods("GET") // Training Camp - apiRouter.HandleFunc("/nfl/training/camp/upload", controller.UploadTrainingCampCSVData).Methods("GET") + apiRouter.HandleFunc("/nfl/training/camp", controller.RunTrainingCamps).Methods("GET") // Transfer Intentions apiRouter.HandleFunc("/simfba/sync/transfer/intention", controller.ProcessTransferIntention).Methods("GET") diff --git a/managers/TrainingCampManager.go b/managers/TrainingCampManager.go index 42d45233..c0da119c 100644 --- a/managers/TrainingCampManager.go +++ b/managers/TrainingCampManager.go @@ -1,13 +1,20 @@ package managers import ( - "github.com/CalebRose/SimFBA/dbprovider" - "github.com/CalebRose/SimFBA/repository" + "bufio" + "encoding/csv" + "log" + "math/rand/v2" + "os" + "slices" + "strconv" + "strings" + "github.com/CalebRose/SimFBA/structs" "github.com/CalebRose/SimFBA/util" ) -func UploadTrainingCampCSV() { +/*func UploadTrainingCampCSV() { db := dbprovider.GetInstance().GetDB() teamPath := "C:\\Users\\ctros\\go\\src\\github.com\\CalebRose\\SimFBA\\data\\2025\\2025_simnfl_rookie_camp_results.csv" @@ -55,4 +62,885 @@ func UploadTrainingCampCSV() { repository.SaveNFLPlayer(nflPlayer, db) } +}*/ + +func RunTrainingCamps() { + //readPath := "C:\\Users\\ctros\\go\\src\\github.com\\CalebRose\\SimFBA\\data\\2026\\trainingcamp.csv" + readPath := "C:\\Code\\SimSN\\SimFBA\\data\\2026\\trainingcamp.csv" + writePath := "C:\\Code\\SimSN\\SimFBA\\data\\2026\\trainingcamp_results.csv" + + drillSelectionsCSV := util.ReadCSV(readPath) + + drillResultsCSV, err := os.Create(writePath) + if err != nil { + panic(err) + } + + csvWriter := csv.NewWriter(bufio.NewWriter(drillResultsCSV)) + + csvWriter.Write([]string{"Team", "DrillPosition", "Archetype", "FirstName", "LastName", "PositionDrill", "PositionDrillAttribute", "PositionDrillResult", "TeamDrill", "TeamDrillAttribute", + "TeamDrillResult", "EventText", "InjuryText", "WeeksOut", "FootballIQ", "Speed", "Carrying", "Agility", "Catching", "RouteRunning", "ZoneCoverage", "ManCoverage", "Strength", + "Tackle", "PassBlock", "RunBlock", "PassRush", "RunDefense", "ThrowPower", "ThrowAccuracy", "KickAccuracy", "KickPower", "PuntAccuracy", "PuntPower"}) + + defer drillResultsCSV.Close() + defer csvWriter.Flush() + + for idx, row := range drillSelectionsCSV { + if idx == 0 { + continue + } + + teamId := row[0] + + players := GetNFLPlayersWithContractsByTeamID(teamId) + + positionOverrides := getPositionOverrides(strings.ToLower(row[13])) + + for _, player := range players { + drillPosition := player.Position + drillArchetype := player.Archetype + if (slices.Contains(positionOverrides, strings.ToLower(player.FirstName+player.LastName)) && player.PositionTwo != "") || player.Position == "ATH" { + drillPosition = player.PositionTwo + drillArchetype = player.ArchetypeTwo + } + + positionDrill := getPositionDrill(drillPosition, row, player) + teamDrill := row[12] + + runDrills(player, drillPosition, drillArchetype, positionDrill, teamDrill, csvWriter) + } + } +} + +func runDrills(player structs.NFLPlayer, drillPosition string, drillArchetype string, positionDrill string, teamDrill string, csvWriter *csv.Writer) { + positionDrillAttribute := getAttribute(drillPosition, drillArchetype, positionDrill) + teamDrillAttribute := getAttribute(drillPosition, drillArchetype, teamDrill) + + log.Printf("Running drills for: %s %s %s %s %s. Position Drill: %s (attr: %s), Team Drill: %s (attr: %s)\n", + player.TeamAbbr, drillArchetype, drillPosition, player.FirstName, player.LastName, positionDrill, positionDrillAttribute, teamDrill, teamDrillAttribute) + + eventModifier := getEventModifier(player) + eventText := "" + if eventModifier != 0 { + eventText = events[0][eventModifier][rand.IntN(len(events[0][eventModifier]))] + log.Printf("Camp event occurred for %s %s %s %s: %s Modifier: %d\n", player.TeamAbbr, player.Position, player.FirstName, player.LastName, eventText, eventModifier) + } + + injuryText, injuryWeeks := checkInjury(player) + if injuryWeeks > 0 { + log.Printf("%s %s %s %s suffered an injury during minicamp! %s, out for %d weeks.\n", player.TeamAbbr, player.Position, player.FirstName, player.LastName, injuryText, injuryWeeks) + } + + changedAttrs := &structs.CollegePlayerProgressions{ + FootballIQ: player.FootballIQ, + Speed: player.Speed, + Carrying: player.Carrying, + Agility: player.Agility, + Catching: player.Catching, + RouteRunning: player.RouteRunning, + ZoneCoverage: player.ZoneCoverage, + ManCoverage: player.ManCoverage, + Strength: player.Strength, + Tackle: player.Tackle, + PassBlock: player.PassBlock, + RunBlock: player.RunBlock, + PassRush: player.PassRush, + RunDefense: player.RunDefense, + ThrowPower: player.ThrowPower, + ThrowAccuracy: player.ThrowAccuracy, + KickAccuracy: player.KickAccuracy, + KickPower: player.KickPower, + PuntAccuracy: player.PuntAccuracy, + PuntPower: player.PuntPower, + InjuryText: injuryText, + WeeksOfRecovery: injuryWeeks, + } + + positionDrillResult := getDrillResult(player, eventModifier) + log.Printf("Position Drill Result for %s %s %s %s: %s (attr: %s) Result: %d\n", player.TeamAbbr, player.Position, player.FirstName, player.LastName, positionDrill, positionDrillAttribute, positionDrillResult) + teamDrillResult := getDrillResult(player, eventModifier) + log.Printf("Team Drill Result for %s %s %s %s: %s (attr: %s) Result: %d\n", player.TeamAbbr, player.Position, player.FirstName, player.LastName, teamDrill, teamDrillAttribute, teamDrillResult) + + applyDrillResult(changedAttrs, positionDrillAttribute, positionDrillResult) + applyDrillResult(changedAttrs, teamDrillAttribute, teamDrillResult) + + csvWriter.Write([]string{player.TeamAbbr, drillPosition, drillArchetype, player.FirstName, player.LastName, positionDrill, positionDrillAttribute, + strconv.Itoa(positionDrillResult), teamDrill, teamDrillAttribute, strconv.Itoa(teamDrillResult), eventText, injuryText, + strconv.Itoa(injuryWeeks), strconv.Itoa(changedAttrs.FootballIQ), strconv.Itoa(changedAttrs.Speed), + strconv.Itoa(changedAttrs.Carrying), strconv.Itoa(changedAttrs.Agility), strconv.Itoa(changedAttrs.Catching), + strconv.Itoa(changedAttrs.RouteRunning), strconv.Itoa(changedAttrs.ZoneCoverage), strconv.Itoa(changedAttrs.ManCoverage), + strconv.Itoa(changedAttrs.Strength), strconv.Itoa(changedAttrs.Tackle), strconv.Itoa(changedAttrs.PassBlock), + strconv.Itoa(changedAttrs.RunBlock), strconv.Itoa(changedAttrs.PassRush), strconv.Itoa(changedAttrs.RunDefense), + strconv.Itoa(changedAttrs.ThrowPower), strconv.Itoa(changedAttrs.ThrowAccuracy), strconv.Itoa(changedAttrs.KickAccuracy), + strconv.Itoa(changedAttrs.KickPower), strconv.Itoa(changedAttrs.PuntAccuracy), strconv.Itoa(changedAttrs.PuntPower)}, + ) +} + +func getPositionOverrides(overrides string) []string { + if overrides == "" { + return []string{} + } + // Player names should ideally be formatted as FirstnameLastname and spaces in between, so they can be treated as part of the same column of the CSV. + return strings.Split(overrides, " ") +} + +func getPositionDrill(drillPosition string, row []string, player structs.NFLPlayer) string { + switch drillPosition { + case "QB": + return row[1] + case "RB": + return row[2] + case "FB": + return row[3] + case "TE": + return row[4] + case "WR": + return row[5] + case "OT", "OG", "C": + return row[6] + case "DT", "DE": + return row[7] + case "ILB": + return row[8] + case "OLB": + if player.Archetype == "Pass Rush" { + return row[7] + } else { + return row[8] + } + case "CB": + return row[9] + case "FS", "SS": + return row[10] + case "K", "P": + return row[11] + default: + return "tackle" + } +} + +func getEventModifier(player structs.NFLPlayer) int { + discipline := float32(player.Discipline) + + negative := (-.6 * discipline) + 60 + positive := (.6 * discipline) + negative + + // Older veterans are more acclimated to the NFL and are less likely to have camp events, positive or negative. + if player.Experience > 2 { + negative = negative / 2 + positive = positive / 2 + } else if player.Experience > 5 { + negative = negative / 4 + positive = positive / 4 + } + + eventRoll := float32(rand.IntN(100)) + if eventRoll < negative { + eventSeverity := rand.IntN(100) + if eventSeverity < 60 { + return -1 + } else if eventSeverity < 90 { + return -2 + } else { + return -3 + } + } else if eventRoll < positive { + eventSeverity := rand.IntN(100) + if eventSeverity < 60 { + return 1 + } else if eventSeverity < 90 { + return 2 + } else { + return 3 + } + } + // no event + return 0 +} + +// Build global dictionary of events +var events = []map[int][]string{ + { + 3: {"Dominates in drills, consistently outperforming expectations.", + "Takes the lead in drills, setting the pace for others to follow.", + "Emerges as an early standout in minicamp reports.", + "Impresses coaching staff with natural leadership skills during team huddles.", + "Consistently executes plays at a high level, earning trust from the coaching staff."}, + 2: {"Receives praise in team meetings for attention to detail.", + "Quickly picks up the playbook and shows understanding in practice.", + "Earns a shout-out in a press conference from the head coach.", + "Demonstrates unexpected versatility by excelling in an unfamiliar role.", + "Sets a strong example for fellow rookies with a positive attitude and work ethic."}, + 1: {"Impresses position coach with consistent effort.", + "Completes all conditioning drills without issue.", + "Makes a solid play during a scrimmage.", + "Responds well to coaching, quickly implementing feedback.", + "Consistently shows good sportsmanship and camaraderie with teammates."}, + -1: {"Shows up late to a team meeting.", + "Struggles with conditioning drills.", + "Misses a minor assignment in a scrimmage and gets yelled at by a coach over it.", + "Gets unusually frustrated after losing a rep in individual drills.", + "Position coaches completly forget his name."}, + -2: {"Blows a key play during a scrimmage in front of the coaching staff.", + "Gets called out in team meetings for lack of focus.", + "Struggles with communication and timing on the field.", + "Repeatedly forgets assignments, slowing down drills for others.", + "Performs poorly in conditioning, noticeably lagging behind teammates."}, + -3: {"Misses multiple meetings or practices without an excuse, causing a formal warning from the team.", + "Has an on-field meltdown during a scrimmage, leading to being pulled from practice.", + "Publicly criticizes coaching decisions during interviews, damaging relationships with the staff.", + "Consistently fails to execute assignments, prompting questions about future roster status.", + "Demonstrates reckless behavior off the field, sparking disciplinary actions."}, + }, +} + +func checkInjury(player structs.NFLPlayer) (string, int) { + injuryCheck := rand.IntN(1000) + if injuryCheck < 25 { + return getInjuryDetails(player) + } + return "None", 0 +} + +func getInjuryDetails(player structs.NFLPlayer) (string, int) { + injuryRoll := getInjuryRoll() + weeksOut := 0 + + if injuryRoll == 10 { + weeksOut = rand.IntN(20) + 1 + } else { + // results in a range from 0-15, spread roughly evenly across injury ratings from 0-100 + injuryModifier := int((100.0 - float32(player.Injury)) / 6.67) + weeksOut = injuryTimes[injuryRoll][15-injuryModifier] + } + + severity := getInjurySeverity(weeksOut) + + return getInjuryText(severity), weeksOut +} + +func getInjuryRoll() int { + roll := rand.IntN(1000) + + if roll < 21 { + return 0 + } else if roll < 83 { + return 1 + } else if roll < 166 { + return 2 + } else if roll < 270 { + return 3 + } else if roll < 416 { + return 4 + } else if roll < 583 { + return 5 + } else if roll < 729 { + return 6 + } else if roll < 833 { + return 7 + } else if roll < 916 { + return 8 + } else if roll < 978 { + return 9 + } else { + return 10 + } +} + +var injuryTimes = [][16]int{ + {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, + {13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0}, + {11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0}, + {9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0}, + {7, 7, 6, 5, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0}, + {5, 5, 4, 4, 3, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0}, + {6, 6, 5, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, + {8, 7, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0}, + {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0}, + {12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0}, +} + +func getInjurySeverity(weeksOut int) string { + if weeksOut < 3 { + return "Minor" + } else if weeksOut < 7 { + return "Moderate" + } else if weeksOut < 13 { + return "Severe" + } else { + return "Season Ending" + } +} + +func getInjuryText(severity string) string { + switch severity { + case "Minor": + return minorInjuries[rand.IntN(len(minorInjuries))] + case "Moderate": + return moderateInjuries[rand.IntN(len(moderateInjuries))] + case "Severe": + return severeInjuries[rand.IntN(len(severeInjuries))] + case "Season Ending": + return seasonEndingInjuries[rand.IntN(len(seasonEndingInjuries))] + } + return "Unknown Injury" +} + +func getDrillResult(player structs.NFLPlayer, eventModifier int) int { + badDrillThreshold := 5 + neutralDrillThreshold := 15 + okayDrillThreshold := 50 + goodDrillThreshold := 80 + + // Older veterans are less likely to have major breakthroughs at camp + if player.Experience >= 5 { + badDrillThreshold = 5 + neutralDrillThreshold = 40 + okayDrillThreshold = 80 + goodDrillThreshold = 95 + } + + result := 0 + drillRoll := rand.IntN(100) + if drillRoll < badDrillThreshold { + result = -1 + } else if drillRoll < neutralDrillThreshold { + result = 0 + } else if drillRoll < okayDrillThreshold { + result = 1 + } else if drillRoll < goodDrillThreshold { + result = 2 + } else { + // great drill result + result = 3 + } + + return result + eventModifier +} + +func applyDrillResult(progression *structs.CollegePlayerProgressions, attribute string, modifier int) { + if attribute == "football_iq" { + progression.FootballIQ += modifier + } + if attribute == "speed" { + progression.Speed += modifier + } + if attribute == "carrying" { + progression.Carrying += modifier + } + if attribute == "agility" { + progression.Agility += modifier + } + if attribute == "catching" { + progression.Catching += modifier + } + if attribute == "route_running" { + progression.RouteRunning += modifier + } + if attribute == "zone_coverage" { + progression.ZoneCoverage += modifier + } + if attribute == "man_coverage" { + progression.ManCoverage += modifier + } + if attribute == "strength" { + progression.Strength += modifier + } + if attribute == "tackle" { + progression.Tackle += modifier + } + if attribute == "pass_block" { + progression.PassBlock += modifier + } + if attribute == "run_block" { + progression.RunBlock += modifier + } + if attribute == "pass_rush" { + progression.PassRush += modifier + } + if attribute == "run_defense" { + progression.RunDefense += modifier + } + if attribute == "throw_power" { + progression.ThrowPower += modifier + } + if attribute == "throw_accuracy" { + progression.ThrowAccuracy += modifier + } + if attribute == "kick_accuracy" { + progression.KickAccuracy += modifier + } + if attribute == "kick_power" { + progression.KickPower += modifier + } + if attribute == "punt_accuracy" { + progression.PuntAccuracy += modifier + } + if attribute == "punt_power" { + progression.PuntPower += modifier + } +} + +// Returns which attribute to change +func getAttribute(position string, archetype string, drill string) string { + if drill == "speed" { + return "speed" + } else if drill == "lift" { + return "strength" + } else if drill == "film" { + return "football_iq" + } else if drill == "plyometrics" { + return "agility" + } else if position == "QB" { + if drill == "dropback" { + return "throw_power" + } else if drill == "screen" { + return "throw_accuracy" + } else if strings.Contains(drill, "pass") { + if archetype == "Pocket" || archetype == "Balanced" { + return "throw_power" + } else { + return "throw_accuracy" + } + } else { + if archetype == "Pocket" || archetype == "Balanced" { + return "throw_accuracy" + } else { + return "throw_power" + } + } + } else if position == "RB" { + if drill == "gauntlet" { + return "carrying" + } else if drill == "square" { + return "route_running" + } else if drill == "blitzpickup" { + return "pass_block" + } else if drill == "jugs" { + return "catching" + } else if strings.Contains(drill, "pass") { + switch archetype { + case "Speed", "Balanced": + return "catching" + case "Power": + return "pass_block" + default: + return "route_running" + } + } else { + switch archetype { + case "Power": + return "strength" + case "Receiving": + return "agility" + default: + chance := rand.IntN(2) + if chance == 0 { + return "strength" + } + return "agility" + } + } + } else if position == "FB" { + if drill == "gauntlet" { + return "carrying" + } else if drill == "square" { + return "route_running" + } else if drill == "blitzpickup" { + return "pass_block" + } else if drill == "jugs" { + return "catching" + } else if drill == "lead" { + return "run_block" + } else if strings.Contains(drill, "pass") { + if archetype == "Blocking" { + return "pass_block" + } else { + return "catching" + } + } else { + switch archetype { + case "Blocking": + return "run_block" + case "Rushing": + return "strength" + case "Receiving": + return "agility" + default: + chance := rand.IntN(3) + if chance == 0 { + return "strength" + } + if chance == 1 { + return "run_block" + } + return "agility" + } + } + } else if position == "TE" { + if drill == "gauntlet" { + return "carrying" + } else if drill == "square" { + return "route_running" + } else if drill == "blitzpickup" { + return "pass_block" + } else if drill == "jugs" { + return "catching" + } else if drill == "arc" { + return "run_block" + } else if strings.Contains(drill, "pass") { + switch archetype { + case "Blocking": + return "pass_block" + case "Vertical Threat": + return "speed" + default: + return "catching" + } + } else { + return "run_block" + } + } else if position == "WR" { + if drill == "gauntlet" { + return "carrying" + } else if drill == "square" { + return "route_running" + } else if drill == "jugs" { + return "catching" + } else if drill == "screenblock" { + return "run_block" + } else if strings.Contains(drill, "pass") { + switch archetype { + case "Possession", "Red Zone Threat": + return "catching" + case "Speed": + return "speed" + default: + return "route_running" + } + } else { + return "run_block" + } + } else if position == "OT" { + if drill == "mirror" { + return "pass_block" + } else if drill == "sled" { + return "run_block" + } else if strings.Contains(drill, "pass") { + return "pass_block" + } else { + return "run_block" + } + } else if position == "OG" { + if drill == "mirror" { + return "pass_block" + } else if drill == "sled" { + return "run_block" + } else if strings.Contains(drill, "pass") { + return "pass_block" + } else { + return "run_block" + } + } else if position == "C" { + if drill == "mirror" { + return "pass_block" + } else if drill == "sled" { + return "run_block" + } else if strings.Contains(drill, "pass") { + return "pass_block" + } else { + return "run_block" + } + } else if position == "DT" { + if drill == "rip" { + return "pass_rush" + } else if drill == "shed" { + return "run_defense" + } else if strings.Contains(drill, "pass") { + return "pass_rush" + } else { + return "run_defense" + } + } else if position == "DE" { + if drill == "rip" { + return "pass_rush" + } else if drill == "shed" { + return "run_defense" + } else if strings.Contains(drill, "pass") { + return "pass_rush" + } else { + return "run_defense" + } + } else if position == "OLB" { + // EDGE + if archetype == "Pass Rush" || archetype == "Run Stopper" { + if drill == "rip" { + return "pass_rush" + } else if drill == "shed" { + return "run_defense" + } else if strings.Contains(drill, "pass") { + return "pass_rush" + } else { + return "run_defense" + } + // Off Ball + } else { + if drill == "runfit" { + return "run_defense" + } else if drill == "rushlane" { + return "pass_rush" + } else if drill == "zonedrop" { + return "zone_coverage" + } else if drill == "hipturn" { + return "man_coverage" + } else if strings.Contains(drill, "pass") { + chance := rand.IntN(2) + if chance == 0 { + return "zone_coverage" + } + return "man_coverage" + } else { + return "run_defense" + } + } + } else if position == "ILB" { + if drill == "runfit" { + return "run_defense" + } else if drill == "rushlane" { + return "pass_rush" + } else if drill == "zonedrop" { + return "zone_coverage" + } else if drill == "hipturn" { + return "man_coverage" + } else if strings.Contains(drill, "pass") { + chance := rand.IntN(2) + if chance == 0 { + return "zone_coverage" + } + return "man_coverage" + } else { + return "run_defense" + } + } else if position == "CB" { + if drill == "zonedrop" { + return "zone_coverage" + } else if drill == "hipturn" { + return "man_coverage" + } else if drill == "jugs" { + return "catching" + } else if strings.Contains(drill, "pass") { + switch archetype { + case "Man Coverage": + return "man_coverage" + case "Zone Coverage": + return "zone_coverage" + default: + chance := rand.IntN(2) + if chance == 0 { + return "zone_coverage" + } + return "man_coverage" + } + } else { + return "tackle" + } + } else if position == "FS" { + if drill == "centerfield" { + return "zone_coverage" + } else if drill == "match" { + return "man_coverage" + } else if drill == "jugs" { + return "catching" + } else if drill == "alley" { + return "run_defense" + } else if drill == "handcombat" { + return "pass_rush" + } else if strings.Contains(drill, "pass") { + switch archetype { + case "Man Coverage": + return "man_coverage" + case "Zone Coverage": + return "zone_coverage" + default: + chance := rand.IntN(2) + if chance == 0 { + return "zone_coverage" + } + return "man_coverage" + } + } else { + return "tackle" + } + } else if position == "SS" { + if drill == "centerfield" { + return "zone_coverage" + } else if drill == "match" { + return "man_coverage" + } else if drill == "jugs" { + return "catching" + } else if drill == "alley" { + return "run_defense" + } else if drill == "handcombat" { + return "pass_rush" + } else if strings.Contains(drill, "pass") { + switch archetype { + case "Man Coverage": + return "man_coverage" + case "Zone Coverage": + return "zone_coverage" + default: + chance := rand.IntN(2) + if chance == 0 { + return "zone_coverage" + } + return "man_coverage" + } + } else { + return "tackle" + } + } else if position == "K" { + switch drill { + case "accuracy": + return "kick_accuracy" + case "power": + return "kick_power" + default: + return "football_iq" + } + } else if position == "P" { + switch drill { + case "accuracy": + return "punt_accuracy" + case "power": + return "punt_power" + default: + return "football_iq" + } + } + return "bad position" +} + +var minorInjuries = []string{ + "Illness", + "Stinger", + "Strained Biceps", + "Strained Triceps", + "Bruised Foot", + "Sprained Foot", + "Bruised Hip", + "Strained Hip", + "Strained Groin", + "Strained Calf", + "Strained Quadriceps", + "Sprained Wrist", + "Elbow Tendonitis", + "Bruised Elbow", + "Sprained Elbow", + "Strained Back", + "Hyperextended Back", + "Ankle Bruise", + "Ankle Sprain", + "Strained Shoulder", + "Shoulder Tendonitis", + "Separated Shoulder", + "Sprained Thumb", + "Sprained Knee", + "Concussion", +} + +var moderateInjuries = []string{ + "Lacerated Spleen", + "Fractured Toe", + "Dislocated Toe", + "Bruised Toe", + "Sprained Toe", + "Turf Toe", + "Wrist Bruise", + "Sprained Wrist", + "Hip Strain", + "Fractured Ribs", + "Achilles Tendonitis", + "Bruised Achilles", + "Dislocated Elbow", + "Elbow Tendonitis", + "Sprained Elbow", + "Strained Groin", + "Pulled Groin", + "Strained Calf", + "Pulled Calf", + "Bruised Thumb", + "Sprained Thumb", + "Dislocated Thumb", + "Fractured Thumb", + "MCL Bruise", + "PCL Bruise", + "Patellar Tendon Bruise", + "Strained Quadriceps", + "Pulled Quadriceps", + "Concussion", + "Strained Biceps", + "Pulled Biceps", + "Strained Triceps", + "Pulled Triceps", + "Strained Pectoral", + "Pulled Pectoral", + "High Ankle Sprain", + "Bruised Hamstring", + "Pulled Hamstring", + "Neck Bruise", + "Sprained Neck", + "ACL Bruise", + "Dislocated Shoulder", + "Shoulder Tendonitis", + "Separated Shoulder", + "Sprained Rotator Cuff", + "Dislocated Ankle", + "Dislocated Foot", + "Sprained Foot", + "Back Disk Tear", +} + +var severeInjuries = []string{ + "Biceps Tear", + "Triceps Tear", + "Quadriceps Tear", + "MCL Bruise", + "MCL Tendonitis", + "PCL Bruise", + "PCL Tendonitis", + "Patellar Tendon Bruise", + "Patellar Tendonitis", + "Knee Meniscus Bruise", + "Knee Meniscus Tear", + "Achilles Tendonitis", + "Hamstring Tendonitis", + "Fractured Wrist", + "Fractured Jaw", + "ACL Bruise", + "ACL Tendonitis", + "Calf Tear", + "Groin Tear", + "Pulled Pectoral", + "Pectoral Tear", + "Fractured Ribs", + "Sprained Neck", + "Back Disk Tear", + "Fractured Ankle", + "Fractured Foot", + "Strained Rotator Cuff", +} + +var seasonEndingInjuries = []string{ + "Ruptured Hamstring", + "Patellar Tendon Tear", + "Knee Meniscus Tear", + "Fractured Foot", + "Ruptured Achilles", + "MCL Tear", + "PCL Tear", + "Fractured Hip", + "Fractured Spine", + "Fractured Ankle", + "ACL Tear", + "Rotator Cuff Tear", } From cd862ad915b43935f5ca50abf3483f3a293e3721 Mon Sep 17 00:00:00 2001 From: jedibob5 Date: Fri, 29 Aug 2025 14:18:02 -0500 Subject: [PATCH 2/4] Finalizing rough draft of training camp code --- controller/TrainingCampController.go | 11 ++++++-- main.go | 2 +- managers/TrainingCampManager.go | 40 ++++++++++++++++++++++------ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/controller/TrainingCampController.go b/controller/TrainingCampController.go index d4013d36..8efe830a 100644 --- a/controller/TrainingCampController.go +++ b/controller/TrainingCampController.go @@ -5,11 +5,18 @@ import ( "net/http" "github.com/CalebRose/SimFBA/managers" + "github.com/gorilla/mux" ) // GetAllCollegeTeamsForRosterPage func RunTrainingCamps(w http.ResponseWriter, r *http.Request) { - managers.RunTrainingCamps() + vars := mux.Vars(r) + year := vars["year"] + err := managers.RunTrainingCamps(year) - json.NewEncoder(w).Encode("Training Camp Complete") + if err != nil { + json.NewEncoder(w).Encode(err.Error()) + } else { + json.NewEncoder(w).Encode("Training Camp Complete") + } } diff --git a/main.go b/main.go index 08af09eb..093ccaad 100644 --- a/main.go +++ b/main.go @@ -343,7 +343,7 @@ func handleRequests() http.Handler { apiRouter.HandleFunc("/trades/nfl/proposal/cancel/{proposalID}", controller.CancelTradeOffer).Methods("GET") // Training Camp - apiRouter.HandleFunc("/nfl/training/camp", controller.RunTrainingCamps).Methods("GET") + apiRouter.HandleFunc("/nfl/training/camp/{year}", controller.RunTrainingCamps).Methods("GET") // Transfer Intentions apiRouter.HandleFunc("/simfba/sync/transfer/intention", controller.ProcessTransferIntention).Methods("GET") diff --git a/managers/TrainingCampManager.go b/managers/TrainingCampManager.go index c0da119c..bae85009 100644 --- a/managers/TrainingCampManager.go +++ b/managers/TrainingCampManager.go @@ -3,6 +3,7 @@ package managers import ( "bufio" "encoding/csv" + "errors" "log" "math/rand/v2" "os" @@ -10,8 +11,11 @@ import ( "strconv" "strings" + "github.com/CalebRose/SimFBA/dbprovider" + "github.com/CalebRose/SimFBA/repository" "github.com/CalebRose/SimFBA/structs" "github.com/CalebRose/SimFBA/util" + "gorm.io/gorm" ) /*func UploadTrainingCampCSV() { @@ -64,10 +68,25 @@ import ( } }*/ -func RunTrainingCamps() { - //readPath := "C:\\Users\\ctros\\go\\src\\github.com\\CalebRose\\SimFBA\\data\\2026\\trainingcamp.csv" - readPath := "C:\\Code\\SimSN\\SimFBA\\data\\2026\\trainingcamp.csv" - writePath := "C:\\Code\\SimSN\\SimFBA\\data\\2026\\trainingcamp_results.csv" +func RunTrainingCamps(year string) error { + db := dbprovider.GetInstance().GetDB() + + readPath := "C:\\Users\\ctros\\go\\src\\github.com\\CalebRose\\SimFBA\\data\\" + year + "\\trainingcamp.csv" + writePath := "C:\\Users\\ctros\\go\\src\\github.com\\CalebRose\\SimFBA\\data\\" + year + "\\trainingcamp_results.csv" + + _, err := os.Stat(readPath) + if errors.Is(err, os.ErrNotExist) { + return errors.New("Training camp CSV not found for year " + year) + } else if err != nil { + return errors.New("Error checking for training camp CSV for " + year + ": " + err.Error()) + } + + _, err = os.Stat(writePath) + if err == nil { + return errors.New("Training camp output CSV already exists for " + year + ". Training camp may have already applied.") + } else if !errors.Is(err, os.ErrNotExist) { + return errors.New("Something weird happened. Training camp output may already exist for " + year + ", but an unrelated error occurred while checking for it: " + err.Error()) + } drillSelectionsCSV := util.ReadCSV(readPath) @@ -78,7 +97,7 @@ func RunTrainingCamps() { csvWriter := csv.NewWriter(bufio.NewWriter(drillResultsCSV)) - csvWriter.Write([]string{"Team", "DrillPosition", "Archetype", "FirstName", "LastName", "PositionDrill", "PositionDrillAttribute", "PositionDrillResult", "TeamDrill", "TeamDrillAttribute", + csvWriter.Write([]string{"Team", "DrillPosition", "Archetype", "FirstName", "LastName", "Experience", "PositionDrill", "PositionDrillAttribute", "PositionDrillResult", "TeamDrill", "TeamDrillAttribute", "TeamDrillResult", "EventText", "InjuryText", "WeeksOut", "FootballIQ", "Speed", "Carrying", "Agility", "Catching", "RouteRunning", "ZoneCoverage", "ManCoverage", "Strength", "Tackle", "PassBlock", "RunBlock", "PassRush", "RunDefense", "ThrowPower", "ThrowAccuracy", "KickAccuracy", "KickPower", "PuntAccuracy", "PuntPower"}) @@ -107,12 +126,13 @@ func RunTrainingCamps() { positionDrill := getPositionDrill(drillPosition, row, player) teamDrill := row[12] - runDrills(player, drillPosition, drillArchetype, positionDrill, teamDrill, csvWriter) + runDrills(player, drillPosition, drillArchetype, positionDrill, teamDrill, csvWriter, db) } } + return nil } -func runDrills(player structs.NFLPlayer, drillPosition string, drillArchetype string, positionDrill string, teamDrill string, csvWriter *csv.Writer) { +func runDrills(player structs.NFLPlayer, drillPosition string, drillArchetype string, positionDrill string, teamDrill string, csvWriter *csv.Writer, db *gorm.DB) { positionDrillAttribute := getAttribute(drillPosition, drillArchetype, positionDrill) teamDrillAttribute := getAttribute(drillPosition, drillArchetype, teamDrill) @@ -164,7 +184,7 @@ func runDrills(player structs.NFLPlayer, drillPosition string, drillArchetype st applyDrillResult(changedAttrs, positionDrillAttribute, positionDrillResult) applyDrillResult(changedAttrs, teamDrillAttribute, teamDrillResult) - csvWriter.Write([]string{player.TeamAbbr, drillPosition, drillArchetype, player.FirstName, player.LastName, positionDrill, positionDrillAttribute, + csvWriter.Write([]string{player.TeamAbbr, drillPosition, drillArchetype, player.FirstName, player.LastName, strconv.FormatUint(uint64(player.Experience), 10), positionDrill, positionDrillAttribute, strconv.Itoa(positionDrillResult), teamDrill, teamDrillAttribute, strconv.Itoa(teamDrillResult), eventText, injuryText, strconv.Itoa(injuryWeeks), strconv.Itoa(changedAttrs.FootballIQ), strconv.Itoa(changedAttrs.Speed), strconv.Itoa(changedAttrs.Carrying), strconv.Itoa(changedAttrs.Agility), strconv.Itoa(changedAttrs.Catching), @@ -174,6 +194,10 @@ func runDrills(player structs.NFLPlayer, drillPosition string, drillArchetype st strconv.Itoa(changedAttrs.ThrowPower), strconv.Itoa(changedAttrs.ThrowAccuracy), strconv.Itoa(changedAttrs.KickAccuracy), strconv.Itoa(changedAttrs.KickPower), strconv.Itoa(changedAttrs.PuntAccuracy), strconv.Itoa(changedAttrs.PuntPower)}, ) + // COMMENT THE BELOW 3 LINES IF YOU WANT TO TEST LOCALLY + player.ApplyTrainingCampInfo(*changedAttrs) + player.GetOverall() + repository.SaveNFLPlayer(player, db) } func getPositionOverrides(overrides string) []string { From 5e905f08dcf11cfdfba8e26e667c485489ce0322 Mon Sep 17 00:00:00 2001 From: jedibob5 Date: Fri, 29 Aug 2025 15:25:13 -0500 Subject: [PATCH 3/4] a couple more minor changes --- managers/TrainingCampManager.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/managers/TrainingCampManager.go b/managers/TrainingCampManager.go index bae85009..7151088f 100644 --- a/managers/TrainingCampManager.go +++ b/managers/TrainingCampManager.go @@ -194,9 +194,9 @@ func runDrills(player structs.NFLPlayer, drillPosition string, drillArchetype st strconv.Itoa(changedAttrs.ThrowPower), strconv.Itoa(changedAttrs.ThrowAccuracy), strconv.Itoa(changedAttrs.KickAccuracy), strconv.Itoa(changedAttrs.KickPower), strconv.Itoa(changedAttrs.PuntAccuracy), strconv.Itoa(changedAttrs.PuntPower)}, ) - // COMMENT THE BELOW 3 LINES IF YOU WANT TO TEST LOCALLY player.ApplyTrainingCampInfo(*changedAttrs) player.GetOverall() + repository.SaveNFLPlayer(player, db) } @@ -829,7 +829,12 @@ func getAttribute(position string, archetype string, drill string) string { case "power": return "kick_power" default: - return "football_iq" + // for team drills, pick a kicking attribute at random. K/P progressions could use the extra bonus. + if rand.IntN(2) == 0 { + return "kick_accuracy" + } else { + return "kick_power" + } } } else if position == "P" { switch drill { @@ -838,7 +843,12 @@ func getAttribute(position string, archetype string, drill string) string { case "power": return "punt_power" default: - return "football_iq" + // for team drills, pick a punting attribute at random. K/P progressions could use the extra bonus. + if rand.IntN(2) == 0 { + return "punt_accuracy" + } else { + return "punt_power" + } } } return "bad position" From e2952724f618163d2c1c6a68410fc77816a390ab Mon Sep 17 00:00:00 2001 From: jedibob5 Date: Tue, 9 Sep 2025 12:46:01 -0500 Subject: [PATCH 4/4] switch to age over experience --- managers/TrainingCampManager.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/managers/TrainingCampManager.go b/managers/TrainingCampManager.go index 7151088f..c9ebc84b 100644 --- a/managers/TrainingCampManager.go +++ b/managers/TrainingCampManager.go @@ -97,7 +97,7 @@ func RunTrainingCamps(year string) error { csvWriter := csv.NewWriter(bufio.NewWriter(drillResultsCSV)) - csvWriter.Write([]string{"Team", "DrillPosition", "Archetype", "FirstName", "LastName", "Experience", "PositionDrill", "PositionDrillAttribute", "PositionDrillResult", "TeamDrill", "TeamDrillAttribute", + csvWriter.Write([]string{"Team", "DrillPosition", "Archetype", "FirstName", "LastName", "Age", "PositionDrill", "PositionDrillAttribute", "PositionDrillResult", "TeamDrill", "TeamDrillAttribute", "TeamDrillResult", "EventText", "InjuryText", "WeeksOut", "FootballIQ", "Speed", "Carrying", "Agility", "Catching", "RouteRunning", "ZoneCoverage", "ManCoverage", "Strength", "Tackle", "PassBlock", "RunBlock", "PassRush", "RunDefense", "ThrowPower", "ThrowAccuracy", "KickAccuracy", "KickPower", "PuntAccuracy", "PuntPower"}) @@ -184,7 +184,7 @@ func runDrills(player structs.NFLPlayer, drillPosition string, drillArchetype st applyDrillResult(changedAttrs, positionDrillAttribute, positionDrillResult) applyDrillResult(changedAttrs, teamDrillAttribute, teamDrillResult) - csvWriter.Write([]string{player.TeamAbbr, drillPosition, drillArchetype, player.FirstName, player.LastName, strconv.FormatUint(uint64(player.Experience), 10), positionDrill, positionDrillAttribute, + csvWriter.Write([]string{player.TeamAbbr, drillPosition, drillArchetype, player.FirstName, player.LastName, strconv.Itoa(player.Age), positionDrill, positionDrillAttribute, strconv.Itoa(positionDrillResult), teamDrill, teamDrillAttribute, strconv.Itoa(teamDrillResult), eventText, injuryText, strconv.Itoa(injuryWeeks), strconv.Itoa(changedAttrs.FootballIQ), strconv.Itoa(changedAttrs.Speed), strconv.Itoa(changedAttrs.Carrying), strconv.Itoa(changedAttrs.Agility), strconv.Itoa(changedAttrs.Catching), @@ -250,10 +250,10 @@ func getEventModifier(player structs.NFLPlayer) int { positive := (.6 * discipline) + negative // Older veterans are more acclimated to the NFL and are less likely to have camp events, positive or negative. - if player.Experience > 2 { + if player.Age > 24 { negative = negative / 2 positive = positive / 2 - } else if player.Experience > 5 { + } else if player.Age > 27 { negative = negative / 4 positive = positive / 4 } @@ -417,7 +417,7 @@ func getDrillResult(player structs.NFLPlayer, eventModifier int) int { goodDrillThreshold := 80 // Older veterans are less likely to have major breakthroughs at camp - if player.Experience >= 5 { + if player.Age > 27 { badDrillThreshold = 5 neutralDrillThreshold = 40 okayDrillThreshold = 80