From f974477106d7b80671338a2e035d088a105fe2bb Mon Sep 17 00:00:00 2001 From: Michael Simard Date: Sun, 10 Jan 2021 09:44:21 -0600 Subject: [PATCH] best and worst stats --- Sources/App/Controllers/StatsController.swift | 176 +++++++++++------- Sources/App/Data/MapData.swift | 69 +++++++ Sources/App/Models/AllStats.swift | 39 +++- Sources/App/Models/DashboardItem.swift | 19 ++ Sources/App/Models/Map.swift | 16 ++ 5 files changed, 252 insertions(+), 67 deletions(-) create mode 100644 Sources/App/Data/MapData.swift create mode 100644 Sources/App/Models/DashboardItem.swift create mode 100644 Sources/App/Models/Map.swift diff --git a/Sources/App/Controllers/StatsController.swift b/Sources/App/Controllers/StatsController.swift index ef8645c..8b7b8b8 100644 --- a/Sources/App/Controllers/StatsController.swift +++ b/Sources/App/Controllers/StatsController.swift @@ -24,8 +24,6 @@ struct StatsController: RouteCollection { statsRoute.post("logMatch", use: logMatch) statsRoute.get("history","page",":page", use: history) statsRoute.get("history", use: history) - - } @@ -82,10 +80,9 @@ struct StatsController: RouteCollection { let winCount = totals[0] let lossCount = totals[1] - let ratio = self.getRatio(num: Double(winCount), den: Double(lossCount)) - return Stats(winLoss: ratio, totalWins: Int(winCount), totalLosses: Int(lossCount)) + return Stats( totalWins: Int(winCount), totalLosses: Int(lossCount)) } - + func getStatsWithMostRecentDailyRecord(matches:[Match], game:String? = nil) -> StatsWithMostRecentDailyRecord { let stats = getStats(matches: matches) @@ -95,7 +92,7 @@ struct StatsController: RouteCollection { return ret } - + func mostRecentDailyStats (matches:[Match], game:String? = nil) -> Stats{ let daysPlayed = getDaysPlayed(matches: matches) @@ -109,15 +106,15 @@ struct StatsController: RouteCollection { self.shouldCountMatch(match: match) if let game = game { -// if game == "mw" { -// shouldInclude = shouldInclude && match.codGame == "mw" -// } -// else if game == "bocw" { -// shouldInclude = shouldInclude && match.codGame == "bocw" -// -// }else { -// -// } + // if game == "mw" { + // shouldInclude = shouldInclude && match.codGame == "mw" + // } + // else if game == "bocw" { + // shouldInclude = shouldInclude && match.codGame == "bocw" + // + // }else { + // + // } shouldInclude = shouldInclude && match.codGame == game } @@ -246,7 +243,7 @@ struct StatsController: RouteCollection { })) // print ( Date().timeIntervalSince(startTime)) - + let mostRecentDailyStats = self.mostRecentDailyStats(matches: matches) @@ -260,16 +257,16 @@ struct StatsController: RouteCollection { // print ( Date().timeIntervalSince(startTime)) - // let cumulativeWinLossRatios = self.getCumulativeWinLossRatios(matches: matches) + // let cumulativeWinLossRatios = self.getCumulativeWinLossRatios(matches: matches) // print ( Date().timeIntervalSince(startTime)) -// let highestWinLossRatio = cumulativeWinLossRatios.reduce("0") { (highestRatio, dataPoint) -> String in -// if dataPoint.y > Double(highestRatio)!{ -// return String(dataPoint.y) -// } -// return highestRatio -// } + // let highestWinLossRatio = cumulativeWinLossRatios.reduce("0") { (highestRatio, dataPoint) -> String in + // if dataPoint.y > Double(highestRatio)!{ + // return String(dataPoint.y) + // } + // return highestRatio + // } // print ( Date().timeIntervalSince(startTime)) @@ -356,8 +353,7 @@ struct StatsController: RouteCollection { let combined = winCount.and(lossCount) return combined.map { (winCount, lossCount) -> (Stats) in - let ratio:Double = (Double(winCount) / Double(lossCount)).truncate(places: 2) - return Stats.init(winLoss: String(ratio), totalWins: winCount, totalLosses: lossCount) + return Stats.init( totalWins: winCount, totalLosses: lossCount) } } @@ -380,7 +376,7 @@ struct StatsController: RouteCollection { return combined.map { (winCount, lossCount) -> (Stats) in - return Stats.init(winLoss: self.getRatio(num: Double(winCount), den: Double(lossCount)), totalWins: winCount, totalLosses: lossCount) + return Stats.init(totalWins: winCount, totalLosses: lossCount) } } @@ -403,8 +399,7 @@ struct StatsController: RouteCollection { let combined = winCount.and(lossCount) return combined.map { (winCount, lossCount) -> (Stats) in - let ratio:Double = (Double(winCount) / Double(lossCount)).truncate(places: 2) - return Stats.init(winLoss: String(ratio), totalWins: winCount, totalLosses: lossCount) + return Stats.init(totalWins: winCount, totalLosses: lossCount) } } @@ -547,17 +542,17 @@ struct StatsController: RouteCollection { func overall(req: Request) throws -> EventLoopFuture { - + let statsWithHyder = statsWithPlayer(req: req, playerId: 5) let statsWithoutHyder = statsWithoutPlayer(req: req, playerId: 5) - + let hyderFuture = statsWithHyder.and(statsWithoutHyder) let hyderStats = hyderFuture.map { (withHyder, withoutHyder) -> [Stats] in return [withHyder, withoutHyder] - } + } let matches = Match.query(on: req.db).all() @@ -571,22 +566,22 @@ struct StatsController: RouteCollection { var mwStats:StatsWithMostRecentDailyRecord? var bocwStats:StatsWithMostRecentDailyRecord? var mostRecentStats:Stats? - + group.enter() queue.async { overallStats = self.getStatsWithMostRecentDailyRecord(matches: matches) group.leave() } - + group.enter() queue.async { mwStats = self.getStatsWithMostRecentDailyRecord(matches: matches.filter({ (match) -> Bool in return match.codGame == "mw" && self.shouldCountMatch(match: match ) })) group.leave() - + } - + group.enter() queue.async { @@ -594,33 +589,86 @@ struct StatsController: RouteCollection { return match.codGame == "bocw" && self.shouldCountMatch(match: match ) })) group.leave() - - } - - group.enter() - queue.async { - mostRecentStats = self.mostRecentDailyStats(matches: matches) - group.leave() + } + // group.enter() + // queue.async { + // mostRecentStats = self.mostRecentDailyStats(matches: matches) + // group.leave() + // } group.wait() + let mapStats = getMapStats(matches: matches) + let bestMap = getBestMap(records: mapStats) + let worstMap = getWorstMap(records: mapStats) + let dashboardItems = [ + DashboardItem(title: "Overall", content: overallStats!.record, title2: "Ratio", content2: overallStats!.winLossRatio), + DashboardItem(title: "MW Overall", content: mwStats!.record, title2: "Ratio", content2: mwStats!.winLossRatio), + DashboardItem(title: "Cold War Overall", content: bocwStats!.record, title2: "Ratio", content2: bocwStats!.winLossRatio), + DashboardItem(title: "With Hyder", content: hyderStats[0].record, title2: "Ratio", content2: hyderStats[0].winLossRatio), + DashboardItem(title: "No Hyder", content: hyderStats[1].record, title2: "Ratio", content2: hyderStats[1].winLossRatio), + DashboardItem(title: "Best Map", content: MapData.allMaps[bestMap]?.name ?? "error", title2: "Ratio", content2: "\(mapStats[bestMap]!.winLossRatio) \(mapStats[bestMap]!.record)"), + DashboardItem(title: "Worst Map", content: MapData.allMaps[worstMap]?.name ?? "error", title2: "Ratio", content2: "\(mapStats[worstMap]!.winLossRatio) \(mapStats[worstMap]!.record)"), + ] - - - return OverallStats(overall: overallStats!, mwStats: mwStats!, bocwStats: bocwStats!, mostRecentRecord: "\(mostRecentStats!.totalWins) - \(mostRecentStats!.totalLosses)", statsWithHyder:hyderStats[0], statsWithoutHyder: hyderStats[1]) + + return OverallStats(overall: overallStats!, mwStats: mwStats!, bocwStats: bocwStats!, mostRecentRecord: "Temporarily Unavailable", statsWithHyder:hyderStats[0], statsWithoutHyder: hyderStats[1], dashboardItems:dashboardItems) } -// return Match.query(on: req.db).all().map { (matches) -> OverallStats in -// -// -// } + // return Match.query(on: req.db).all().map { (matches) -> OverallStats in + // + // + // } } + + func getBestMap (records :[ Int:Stats] ) -> Int { + + let maps = records.keys.sorted { (map1, map2) -> Bool in + return records[map1]?.getRatioDouble() ?? 0.0 < records[map2]?.getRatioDouble() ?? 0.0 + + } + return maps.last ?? -1 + } + + func getWorstMap (records :[ Int:Stats] ) -> Int { + + let maps = records.keys.sorted { (map1, map2) -> Bool in + return records[map1]?.getRatioDouble() ?? 0.0 < records[map2]?.getRatioDouble() ?? 0.0 + } + return maps.first ?? -1 + } + + + + func getMapStats(matches:[Match]) -> [Int:Stats] { + var mapStats:[Int:Stats] = [Int:Stats]() + for match in matches { + + if match.codGame == "mw" { + if let map = match.map, let mapInt = Int(map) { + + if mapStats[mapInt] == nil { + mapStats[mapInt] = Stats(totalWins: 0, totalLosses: 0) + } + + if match.win { + mapStats[mapInt]?.totalWins += 1 + } + else{ + mapStats[mapInt]?.totalLosses += 1 + } + } + } + } + return mapStats + + } func statsWithPlayer(req: Request, playerId:Int) -> EventLoopFuture { return Match.query(on: req.db) @@ -629,7 +677,7 @@ struct StatsController: RouteCollection { return self.getStats(matches: matches) } - } + } func statsWithoutPlayer (req: Request, playerId:Int) -> EventLoopFuture { return Match.query(on: req.db) @@ -640,19 +688,7 @@ struct StatsController: RouteCollection { } - private func getRatio( num:Double, den:Double) -> String { - - var returnString = "" - let deno = (den != 0) ? den : 1 - - returnString = String((Double(num) / Double(deno)).truncate(places: 2)) - - if den == 0 { - returnString = returnString + "+" - } - return returnString - - } + private func getDaysPlayed(req:Request) -> EventLoopFuture<[CODDate]> { @@ -722,6 +758,20 @@ struct StatsController: RouteCollection { } } + private func getRatio( num:Double, den:Double) -> String { + + var returnString = "" + let deno = (den != 0) ? den : 1 + + returnString = String((Double(num) / Double(deno)).truncate(places: 2)) + + if den == 0 { + returnString = returnString + "+" + } + return returnString + + } + func getCumulativeWinLossRatios(req:Request) -> EventLoopFuture<[DataPoint]> { diff --git a/Sources/App/Data/MapData.swift b/Sources/App/Data/MapData.swift new file mode 100644 index 0000000..fed19e9 --- /dev/null +++ b/Sources/App/Data/MapData.swift @@ -0,0 +1,69 @@ +// +// MapData.swift +// App +// +// Created by Michael Simard on 1/10/21. +// + +import Foundation + +class MapData { + + + static let allMaps:[Int:Map] = [ + 0: Map(id: 0, name: "Satellite", imageName: "bocw_satellite"), + 1: Map(id: 1, name: "Armada", imageName: "bocw_armada"), + 2: Map(id: 2, name: "Nuketown", imageName: "bocw_nuketown"), + 3: Map(id: 3, name: "Cartel", imageName: "bocw_cartel"), + 4: Map(id: 4, name: "Garrison", imageName: "bocw_garrison"), + 5: Map(id: 5, name: "Miami", imageName: "bocw_miami"), + 6: Map(id: 6, name: "Moscow", imageName: "bocw_moscow"), + 7: Map(id: 7, name: "Crossroads", imageName: "bocw_crossroads"), + 8: Map(id: 8, name: "Checkmate", imageName: "bocw_checkmate"), + 9: Map(id: 9, name: "Arklov Peak", imageName: "mw_arklov"), + 10: Map(id: 10, name: "Atlas Superstore", imageName: "mw_atlas"), + 11: Map(id: 11, name: "Talsik Backlot", imageName: "mw_backlot"), + 12: Map(id: 12, name: "Broadcast", imageName: "mw_broadcast"), + 13: Map(id: 13, name: "Azhir Cave", imageName: "mw_caves"), + 14: Map(id: 14, name: "Chesire Park", imageName: "mw_chesire"), + 15: Map(id: 15, name: "Crash", imageName: "mw_crash"), + 28: Map(id: 28, name: "Gun Runner", imageName: "mw_gunrunner"), + 16: Map(id: 16, name: "Hackney Yard", imageName: "mw_hackney"), + 17: Map(id: 17, name: "Suldal Harbor", imageName: "mw_harbor"), + 18: Map(id: 18, name: "Hardhat", imageName: "mw_hardhat"), + 19: Map(id: 19, name: "Hovec Sawmill", imageName: "mw_hovec"), + 20: Map(id: 20, name: "Khandor Hideout", imageName: "mw_khandor"), + 21: Map(id: 21, name: "Petrov Oil Rig", imageName: "mw_oil"), + 22: Map(id: 22, name: "St. Petrograd", imageName: "mw_petrograd"), + 23: Map(id: 23, name: "Rammaza", imageName: "mw_rammaza"), + 24: Map(id: 24, name: "Scrapyard", imageName: "mw_scrapyard"), + 25: Map(id: 25, name: "Shoot House", imageName: "mw_shoothouse"), + 26: Map(id: 26, name: "Mialstor Tank Factory", imageName: "mw_tank"), + 27: Map(id: 27, name: "Vacant", imageName: "mw_vacant"), + 31: Map(id: 31, name: "The Pines", imageName: "bocw_pines"), + 29: Map(id: 29, name: "Nuketown Holiday", imageName: "bocw_nuketown_holiday"), + 30: Map(id: 30, name: "Raid", imageName: "bocw_raid"), + 32: Map(id: 32, name: "Piccadilly", imageName: "mw_piccadilly"), + 33: Map(id: 33, name: "Grazna Raid", imageName: "mw_grazna") + ] + + static let bocwMaps = { + + return [1,3,8,7,4,2,29,31,5,6,30,0].compactMap { + return allMaps[$0]; + } + .sorted { (m1, m2) -> Bool in + return m1.name < m2.name + } + } + static let mwMaps = { + return [9,10,11,12,13,14,15,28,16,17,18,19,20,21,22,23,24,25,26,27,32,33].compactMap { + return allMaps[$0]; + }.sorted { (m1, m2) -> Bool in + return m1.name < m2.name + } + } + + + +} diff --git a/Sources/App/Models/AllStats.swift b/Sources/App/Models/AllStats.swift index 4aedf38..c2c5355 100644 --- a/Sources/App/Models/AllStats.swift +++ b/Sources/App/Models/AllStats.swift @@ -50,8 +50,9 @@ final class OverallStats: Content { var statsWithHyder:Stats var statsWithoutHyder:Stats + var dashboardItems:[DashboardItem] = [] - init( overall:StatsWithMostRecentDailyRecord, mwStats:StatsWithMostRecentDailyRecord, bocwStats:StatsWithMostRecentDailyRecord, mostRecentRecord:String, statsWithHyder:Stats, statsWithoutHyder:Stats){ + init( overall:StatsWithMostRecentDailyRecord, mwStats:StatsWithMostRecentDailyRecord, bocwStats:StatsWithMostRecentDailyRecord, mostRecentRecord:String, statsWithHyder:Stats, statsWithoutHyder:Stats, dashboardItems:[DashboardItem]){ self.overall = overall self.mwStats = mwStats; @@ -60,6 +61,8 @@ final class OverallStats: Content { self.statsWithHyder = statsWithHyder self.statsWithoutHyder = statsWithoutHyder + + self.dashboardItems = dashboardItems } } @@ -81,15 +84,39 @@ final class MonthStats: Content { final class Stats: Content { - var winLossRatio:String + + var winLossRatio:String { + get { + return getRatio() + } + } var totalWins:Int var totalLosses:Int - init( winLoss:String, totalWins:Int, totalLosses:Int) { - self.winLossRatio = winLoss + init( totalWins:Int, totalLosses:Int) { self.totalWins = totalWins self.totalLosses = totalLosses } + + var record:String { + return "\(totalWins)-\(totalLosses)" + } + + private func getRatio() -> String { + var returnString = "" + let deno = (totalLosses != 0) ? totalLosses : 1 + returnString = String(getRatioDouble()) + if deno == 0 { + returnString = returnString + "+" + } + return returnString + } + + func getRatioDouble() -> Double { + let deno = (totalLosses != 0) ? totalLosses : 1 + + return (Double(totalWins) / Double(deno)).truncate(places: 3) + } } final class StatsWithMostRecentDailyRecord: Content { @@ -104,6 +131,10 @@ final class StatsWithMostRecentDailyRecord: Content { self.totalLosses = totalLosses self.mostRecentRecord = mostRecentRecord } + + var record:String { + return "\(totalWins)-\(totalLosses)" + } } diff --git a/Sources/App/Models/DashboardItem.swift b/Sources/App/Models/DashboardItem.swift new file mode 100644 index 0000000..9f938dc --- /dev/null +++ b/Sources/App/Models/DashboardItem.swift @@ -0,0 +1,19 @@ +// +// DashboardItem.swift +// App +// +// Created by Michael Simard on 1/9/21. +// + +import Foundation +import Fluent +import Vapor + +struct DashboardItem: Content { + var title:String + var content:String + var title2:String? = nil + var content2:String? = nil + + +} diff --git a/Sources/App/Models/Map.swift b/Sources/App/Models/Map.swift new file mode 100644 index 0000000..99715b6 --- /dev/null +++ b/Sources/App/Models/Map.swift @@ -0,0 +1,16 @@ +// +// Map.swift +// App +// +// Created by Michael Simard on 1/10/21. +// + +import Foundation + +struct Map: Hashable { + var id: Int + var name:String + var imageName:String +} + +