From 6195705dcbaa150ecd814b69cd7fae5fe9172d21 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Thu, 2 May 2024 02:12:52 +0300 Subject: [PATCH] TLThingy bug fix + progress bar now can appoximate number of wins/losses --- lib/data_objects/glicko.dart | 147 ++++++++++++++++++++++++++++++ lib/data_objects/tetrio.dart | 23 ++++- lib/views/main_view.dart | 46 +++++++--- lib/views/rank_averages_view.dart | 2 +- lib/widgets/gauget_num.dart | 2 +- lib/widgets/tl_progress_bar.dart | 91 +++++++++--------- lib/widgets/tl_thingy.dart | 21 +++-- 7 files changed, 265 insertions(+), 67 deletions(-) create mode 100644 lib/data_objects/glicko.dart diff --git a/lib/data_objects/glicko.dart b/lib/data_objects/glicko.dart new file mode 100644 index 0000000..50b4e88 --- /dev/null +++ b/lib/data_objects/glicko.dart @@ -0,0 +1,147 @@ +import 'dart:math'; +// I reimplemented kenany/glicko2-lite in dart lol +// Don't look here lol + +List scale(double rating, double rd, double options) { + double mu = (rating - options) / 173.7178; + double phi = rd / 173.7178; + return [ mu, phi ]; +} + +double g(phi) { + return 1 / sqrt(1 + 3 * pow(phi, 2) / pow(pi, 2)); +} + +double e(double mu, double muj, double phij) { + return 1 / (1 + exp(-g(phij) * (mu - muj))); +} + +List> scaleOpponents(double mu, List> opponents, double rating) { + return opponents.map((opp) { + var scaled = scale(opp[0], opp[1], rating); + return { + "muj": scaled[0], + "phij": scaled[1], + "gphij": g(scaled[1]), + "emmp": e(mu, scaled[0], scaled[1]), + "score": opp[2] + }; + }).toList(); +} + +double updateRating(List> opponents) { + double value = pow(opponents.first["gphij"]!, 2) * opponents.first["emmp"]! * (1 - opponents.first["emmp"]!); + opponents.skip(1).forEach((element) { + value += pow(element["gphij"]!, 2) * element["emmp"]! * (1 - element["emmp"]!); + }); + return 1 / value; +} + +double computeDelta(v, List> opponents) { + double value = opponents.first["gphij"]! * (opponents.first["score"]! - opponents.first["emmp"]!); + opponents.skip(1).forEach((element) { + value += opponents.first["gphij"]! * (opponents.first["score"]! - opponents.first["emmp"]!); + }); + return v * value; +} + +Function volF(double phi, double v, double delta, double a, double tau) { + num phi2 = pow(phi, 2); + num d2 = pow(delta, 2); + + return (x) { + double ex = exp(x); + double a2 = phi2 + v + ex; + double p2 = (x - a) / pow(tau, 2); + double p1 = (ex * (d2 - phi2 - v - ex)) / (2 * pow(a2, 2)); + return p1 - p2; + }; +} + +double computeVolatility(double sigma, double phi, double v, double delta, double options) { + // 5.1 + double a = log(pow(sigma, 2)); + Function f = volF(phi, v, delta, a, options); + + // 5.2 + double b; + if (pow(delta, 2) > pow(phi, 2) + v) { + b = log(pow(delta, 2) - pow(phi, 2) - v); + } + else { + double k = 1; + while (f(a - k * options) < 0) { + k++; + } + b = a - k * options; + } + + // 5.3 + double fa = f(a); + double fb = f(b); + + // 5.4 + while ((b - a).abs() > 0.000001) { + double c = a + (a - b) * fa / (fb - fa); + double fc = f(c); + + if (fc * fb <= 0) { + a = b; + fa = fb; + } + else { + fa /= 2; + } + + b = c; + fb = fc; + } + + // 5.5 + return exp(a / 2); +} + +double phiStar(sigmap, phi) { + return sqrt(pow(sigmap, 2) + pow(phi, 2)); +} + +Map newRating(phis, mu, v, opponents) { + double phip = 1 / sqrt(1 / pow(phis, 2) + 1 / v); + double value = opponents.first["gphij"]! * (opponents.first["score"]! - opponents.first["emmp"]!); + opponents.skip(1).forEach((element) { + value += element["gphij"]! * (element["score"]! - element["emmp"]!); + }); + double mup = mu + pow(phip, 2) * value; + return { "mu": mup, "phi": phip }; +} + +List unscale(mup, phip, options) { + double rating = 173.7178 * mup + options["rating"]; + double rd = 173.7178 * phip; + return [ rating, rd ]; +} + +List rate(double rating, double rd, double sigma, List> opponents, Map options) { + Map opts = { "rating": options["rating"]??1500, "tau": options["tau"]??0.5 }; + + // Step 2 + List scaled = scale(rating, rd, opts["rating"]!); + List> scaledOpponents = scaleOpponents(scaled[0], opponents, opts["rating"]!); + + // Step 3 + double v = updateRating(scaledOpponents); + + // Step 4 + double delta = computeDelta(v, scaledOpponents); + + // Step 5 + double sigmap = computeVolatility(sigma, scaled[1], v, delta, opts["tau"]!); + + // Step 6 + double phis = phiStar(sigmap, scaled[1]); + + // Step 7 + Map updated = newRating(phis, scaled[0], v, scaledOpponents); + + return unscale(updated['mu'], updated['phi'], opts)..add(sigmap); +} \ No newline at end of file diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 15457b3..e503ea5 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -1856,11 +1856,12 @@ class TetrioPlayersLeaderboard { "avgStride": avgStride, "avgInfDS": avgInfDS, "toEnterTR": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].rating : lowestTR, + "toEnterGlicko": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].glicko : 0, "entries": filtredLeaderboard }]; }else{ return [TetraLeagueAlpha(timestamp: DateTime.now(), apm: 0, pps: 0, vs: 0, glicko: 0, rd: noTrRd, gamesPlayed: 0, gamesWon: 0, bestRank: rank, decaying: false, rating: 0, rank: rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1), - {"players": filtredLeaderboard.length, "lowestTR": 0, "toEnterTR": 0}]; + {"players": filtredLeaderboard.length, "lowestTR": 0, "toEnterTR": 0, "toEnterGlicko": 0}]; } } @@ -1929,6 +1930,26 @@ class TetrioPlayersLeaderboard { 'd': getAverageOfRank("d")[1]["toEnterTR"] }; + Map get cutoffsGlicko => { + 'x': getAverageOfRank("x")[1]["toEnterGlicko"], + 'u': getAverageOfRank("u")[1]["toEnterGlicko"], + 'ss': getAverageOfRank("ss")[1]["toEnterGlicko"], + 's+': getAverageOfRank("s+")[1]["toEnterGlicko"], + 's': getAverageOfRank("s")[1]["toEnterGlicko"], + 's-': getAverageOfRank("s-")[1]["toEnterGlicko"], + 'a+': getAverageOfRank("a+")[1]["toEnterGlicko"], + 'a': getAverageOfRank("a")[1]["toEnterGlicko"], + 'a-': getAverageOfRank("a-")[1]["toEnterGlicko"], + 'b+': getAverageOfRank("b+")[1]["toEnterGlicko"], + 'b': getAverageOfRank("b")[1]["toEnterGlicko"], + 'b-': getAverageOfRank("b-")[1]["toEnterGlicko"], + 'c+': getAverageOfRank("c+")[1]["toEnterGlicko"], + 'c': getAverageOfRank("c")[1]["toEnterGlicko"], + 'c-': getAverageOfRank("c-")[1]["toEnterGlicko"], + 'd+': getAverageOfRank("d+")[1]["toEnterGlicko"], + 'd': getAverageOfRank("d")[1]["toEnterGlicko"] + }; + TetrioPlayersLeaderboard.fromJson(List json, String t, DateTime ts) { type = t; timestamp = ts; diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index f9315c1..2124a3f 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -86,6 +86,8 @@ class _MainState extends State with TickerProviderStateMixin { TetraLeagueAlpha? rankAverages; double? thatRankCutoff; double? nextRankCutoff; + double? thatRankGlickoCutoff; + double? nextRankGlickoCutoff; String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for String _titleNickname = ""; /// Each dropdown menu item contains list of dots for the graph @@ -198,8 +200,11 @@ class _MainState extends State with TickerProviderStateMixin { } if (me.tlSeason1.gamesPlayed > 9) { thatRankCutoff = everyone!.cutoffs[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank]; + thatRankGlickoCutoff = everyone!.cutoffsGlicko[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank]; nextRankCutoff = everyone!.cutoffs[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)]; + nextRankGlickoCutoff = everyone!.cutoffsGlicko[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)]; nextRankCutoff = nextRankCutoff??25000; + nextRankGlickoCutoff = nextRankGlickoCutoff??double.infinity; } } @@ -272,7 +277,6 @@ class _MainState extends State with TickerProviderStateMixin { // Also i need previous Tetra League State for comparison if avaliable if (uniqueTL.length >= 2){ compareWith = uniqueTL.toList().elementAtOrNull(uniqueTL.length - 2); - //chartsData = [for (var tl in uniqueTL) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, [tl.rating, tl.glicko, tl.rd, tl.apm, tl.pps, tl.vs, tl.nerdStats?.app, tl.nerdStats?.dss, tl.nerdStats?.dsp, tl.nerdStats?.appdsp, tl.nerdStats?.vsapm, tl.nerdStats?.cheese, tl.nerdStats?.gbe, tl.nerdStats?.nyaapp, tl.nerdStats?.area, tl.estTr?.esttr, tl.esttracc, tl.playstyle?.opener, tl.playstyle?.plonk, tl.playstyle?.infds, tl.playstyle?.stride])]; chartsData = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rating)], child: Text(t.statCellNum.tr)), DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.glicko!)], child: const Text("Glicko")), @@ -455,9 +459,11 @@ class _MainState extends State with TickerProviderStateMixin { bot: snapshot.data![0].role == "bot", guest: snapshot.data![0].role == "anon", thatRankCutoff: thatRankCutoff, + thatRankCutoffGlicko: thatRankGlickoCutoff, thatRankTarget: snapshot.data![0].tlSeason1.rank != "z" ? rankTargets[snapshot.data![0].tlSeason1.rank] : null, nextRankCutoff: nextRankCutoff, - nextRankTarget: snapshot.data![0].tlSeason1.rank != "z" || snapshot.data![0].tlSeason1.rank != "x" ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![0].tlSeason1.rank)+1)] : null, + nextRankCutoffGlicko: nextRankGlickoCutoff, + nextRankTarget: (snapshot.data![0].tlSeason1.rank != "z" && snapshot.data![0].tlSeason1.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![0].tlSeason1.rank)+1)] : null, averages: rankAverages, lbPositions: meAmongEveryone ), @@ -934,26 +940,44 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> { child: SfCartesianChart( tooltipBehavior: _tooltipBehavior, zoomPanBehavior: _zoomPanBehavior, - primaryXAxis: DateTimeAxis(), + primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? NumericAxis() : DateTimeAxis(), primaryYAxis: NumericAxis( rangePadding: ChartRangePadding.additional, ), series: [ - StepLineSeries<_HistoryChartSpot, DateTime>( + if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>( enableTooltip: true, // splineType: SplineType.cardinal, // cardinalSplineTension: 0.2, dataSource: widget.data, animationDuration: 0, - opacity: 1, + opacity: _smooth ? 0 : 1, + xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed, + yValueMapper: (_HistoryChartSpot data, _) => data.stat, + trendlines:[ + Trendline( + isVisible: _smooth, + period: (widget.data.length/175).floor(), + type: TrendlineType.movingAverage, + color: Colors.blue) + ], + ) + else StepLineSeries<_HistoryChartSpot, DateTime>( + enableTooltip: true, + // splineType: SplineType.cardinal, + // cardinalSplineTension: 0.2, + dataSource: widget.data, + animationDuration: 0, + opacity: _smooth ? 0 : 1, xValueMapper: (_HistoryChartSpot data, _) => data.timestamp, yValueMapper: (_HistoryChartSpot data, _) => data.stat, - // trendlines:[ - // Trendline( - // period: (widget.data.length/175).floor(), - // type: TrendlineType.movingAverage, - // color: Colors.blue) - // ], + trendlines:[ + Trendline( + isVisible: _smooth, + period: (widget.data.length/175).floor(), + type: TrendlineType.movingAverage, + color: Colors.blue) + ], ), ], ), diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart index aab2a18..ced944e 100644 --- a/lib/views/rank_averages_view.dart +++ b/lib/views/rank_averages_view.dart @@ -200,7 +200,7 @@ class RankState extends State with SingleTickerProviderStateMixin { direction: Axis.horizontal, alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.end, - spacing: 25, + spacing: 20, children: [ Column( children: [ diff --git a/lib/widgets/gauget_num.dart b/lib/widgets/gauget_num.dart index a255908..61b052d 100644 --- a/lib/widgets/gauget_num.dart +++ b/lib/widgets/gauget_num.dart @@ -98,7 +98,7 @@ class GaugetNum extends StatelessWidget { oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent : oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent ),), - if ((oldTl != null && oldTl!.gamesPlayed > 0) && pos != null) const TextSpan(text: " • "), + if (oldPlayerStat != null && pos != null) const TextSpan(text: " • "), if (pos != null) TextSpan(text: pos!.position >= 1000 ? "${t.top} ${f2.format(pos!.percentage*100)}%" : "№${pos!.position}", style: TextStyle(color: getColorOfRank(pos!.position))) ] ), diff --git a/lib/widgets/tl_progress_bar.dart b/lib/widgets/tl_progress_bar.dart index 3366d74..41df77d 100644 --- a/lib/widgets/tl_progress_bar.dart +++ b/lib/widgets/tl_progress_bar.dart @@ -3,27 +3,26 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; +import 'package:tetra_stats/data_objects/glicko.dart'; import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/utils/numers_formats.dart'; class TLProgress extends StatelessWidget{ - final double tr; - final String rank; - final int position; + final TetraLeagueAlpha tlData; final String? nextRank; final String? previousRank; - final int nextRankPosition; - final int previousRankPosition; final double? nextRankTRcutoff; final double? previousRankTRcutoff; + final double? nextRankGlickoCutoff; + final double? previousGlickoCutoff; final double? nextRankTRcutoffTarget; final double? previousRankTRcutoffTarget; - const TLProgress({super.key, required this.tr, required this.rank, required this.position, required this.nextRankPosition, required this.previousRankPosition, this.nextRank, this.previousRank, this.nextRankTRcutoff, this.previousRankTRcutoff, this.nextRankTRcutoffTarget, this.previousRankTRcutoffTarget}); + const TLProgress({super.key, required this.tlData, this.nextRank, this.previousRank, this.nextRankTRcutoff, this.previousRankTRcutoff, this.nextRankGlickoCutoff, this.previousGlickoCutoff, this.nextRankTRcutoffTarget, this.previousRankTRcutoffTarget}); double getBarPosition(){ - return min(max(0, 1 - (position - nextRankPosition)/(previousRankPosition - nextRankPosition)), 1); + return min(max(0, 1 - (tlData.standing - tlData.nextAt)/(tlData.prevAt - tlData.nextAt)), 1); } double? getBarTR(double tr){ @@ -32,45 +31,51 @@ class TLProgress extends StatelessWidget{ @override Widget build(BuildContext context) { - // return Container( - // alignment: Alignment.centerLeft, - // height: 50, - // width: MediaQuery.of(context).size.width, - // color: Colors.blue, - // child: Container( - // width: MediaQuery.of(context).size.width / 2, - // height: 50, - // color: Colors.red, - // ), - // ); + final glickoForWin = rate(tlData.glicko!, tlData.rd!, 0.06, [[tlData.glicko!, tlData.rd!, 1]], {})[0]-tlData.glicko!; return Padding( padding: const EdgeInsets.all(8.0), - child: SfLinearGauge( - minimum: 0, - maximum: 1, - interval: 1, - ranges: [ - if (previousRankTRcutoff != null && nextRankTRcutoff != null) LinearGaugeRange(endValue: getBarTR(tr)!, color: Colors.cyanAccent, position: LinearElementPosition.cross), - if (position != -1) LinearGaugeRange(endValue: getBarPosition(), color: Colors.cyanAccent, position: LinearElementPosition.cross), - if (previousRankTRcutoff != null && previousRankTRcutoffTarget != null) LinearGaugeRange(endValue: getBarTR(previousRankTRcutoffTarget!)!, color: Colors.greenAccent, position: LinearElementPosition.inside), - if (nextRankTRcutoff != null && nextRankTRcutoffTarget != null) LinearGaugeRange(startValue: getBarTR(nextRankTRcutoffTarget!)!, endValue: 1, color: Colors.yellowAccent, position: LinearElementPosition.inside) + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SfLinearGauge( + minimum: 0, + maximum: 1, + interval: 1, + ranges: [ + if (previousRankTRcutoff != null && nextRankTRcutoff != null) LinearGaugeRange(endValue: getBarTR(tlData.rating)!, color: Colors.cyanAccent, position: LinearElementPosition.cross) + else if (tlData.standing != -1) LinearGaugeRange(endValue: getBarPosition(), color: Colors.cyanAccent, position: LinearElementPosition.cross), + if (previousRankTRcutoff != null && previousRankTRcutoffTarget != null) LinearGaugeRange(endValue: getBarTR(previousRankTRcutoffTarget!)!, color: Colors.greenAccent, position: LinearElementPosition.inside), + if (nextRankTRcutoff != null && nextRankTRcutoffTarget != null) LinearGaugeRange(startValue: getBarTR(nextRankTRcutoffTarget!)!, endValue: 1, color: Colors.yellowAccent, position: LinearElementPosition.inside) + ], + markerPointers: [ + LinearShapePointer(value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20), + LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), child: Text("№ ${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}")) + ], + isMirrored: true, + showTicks: true, + axisLabelStyle: TextStyle(), + onGenerateLabels: () => [ + LinearAxisLabel(text: "${tlData.prevAt > 0 ? "№ ${f0.format(tlData.prevAt)}" : ""}\n ${intf.format(previousRankTRcutoff)} TR", value: 0), + LinearAxisLabel(text: "${tlData.nextAt > 0 ? "№ ${f0.format(tlData.nextAt)}" : ""}\n ${intf.format(nextRankTRcutoff)} TR", value: 1), + ], + // labelFormatterCallback: (value) { + // if (value == "0") return "${f0.format(previousRankPosition)}\n 26,700 TR"; + // else return f0.format(nextRankPosition); + // }, + showLabels: true + ), + Container( + width: MediaQuery.of(context).size.width, + height: 20, + child: Stack( + fit: StackFit.expand, + children: [ + Positioned(child: Text("${f2.format(tlData.rating-previousRankTRcutoff!)} (${f2.format((tlData.glicko!-previousGlickoCutoff!)/glickoForWin)} losses)"), left: 0,), + Positioned(child: Text("${f2.format(nextRankTRcutoff!-tlData.rating)} (${f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)} wins)"), right: 0,) + ],), + ) + ], - markerPointers: [ - LinearShapePointer(value: getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20), - LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: getBarPosition(), child: Text("№ ${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(position)}")) - ], - isMirrored: true, - showTicks: true, - axisLabelStyle: TextStyle(), - onGenerateLabels: () => [ - LinearAxisLabel(text: "№ ${f0.format(previousRankPosition)}\n ${intf.format(previousRankTRcutoff)} TR", value: 0), - LinearAxisLabel(text: "№ ${f0.format(nextRankPosition)}\n ${intf.format(nextRankTRcutoff)} TR", value: 1), - ], - // labelFormatterCallback: (value) { - // if (value == "0") return "${f0.format(previousRankPosition)}\n 26,700 TR"; - // else return f0.format(nextRankPosition); - // }, - showLabels: true ) ); } diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index b90f314..0bceac8 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -14,10 +14,6 @@ import 'package:tetra_stats/widgets/tl_progress_bar.dart'; var fDiff = NumberFormat("+#,###.###;-#,###.###"); var intFDiff = NumberFormat("+#,###;-#,###"); final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); -late RangeValues _currentRangeValues; -TetraLeagueAlpha? oldTl; -late TetraLeagueAlpha currentTl; -late List sortedStates; class TLThingy extends StatefulWidget { final TetraLeagueAlpha tl; @@ -30,10 +26,12 @@ class TLThingy extends StatefulWidget { final PlayerLeaderboardPosition? lbPositions; final TetraLeagueAlpha? averages; final double? thatRankCutoff; + final double? thatRankCutoffGlicko; final double? thatRankTarget; final double? nextRankCutoff; + final double? nextRankCutoffGlicko; final double? nextRankTarget; - const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR, this.lbPositions, this.averages, this.nextRankCutoff = 25000, this.thatRankCutoff = 0, this.nextRankTarget = 25000, this.thatRankTarget = 0}); + const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR, this.lbPositions, this.averages, this.nextRankCutoff = 25000, this.thatRankCutoff = 0, this.thatRankCutoffGlicko = 0, this.nextRankCutoffGlicko = double.infinity, this.nextRankTarget = 25000, this.thatRankTarget = 0}); @override State createState() => _TLThingyState(); @@ -41,6 +39,10 @@ class TLThingy extends StatefulWidget { class _TLThingyState extends State { late bool oskKagariGimmick; + late TetraLeagueAlpha? oldTl; + late TetraLeagueAlpha currentTl; + late RangeValues _currentRangeValues; + late List sortedStates; @override void initState() { @@ -161,14 +163,13 @@ class _TLThingyState extends State { // ), // ), TLProgress( - tr: currentTl.rating, - rank: currentTl.rank != "z" ? currentTl.rank : currentTl.percentileRank, - position: currentTl.standing, - nextRankPosition: currentTl.nextAt, - previousRankPosition: currentTl.prevAt, + tlData: currentTl, previousRankTRcutoff: widget.thatRankCutoff, + previousGlickoCutoff: widget.thatRankCutoffGlicko, + previousRank: widget.tl.prevRank, previousRankTRcutoffTarget: widget.thatRankTarget, nextRankTRcutoff: widget.nextRankCutoff, + nextRankGlickoCutoff: widget.nextRankCutoffGlicko, nextRankTRcutoffTarget: widget.nextRankTarget, nextRank: widget.tl.nextRank ),