Ranks averages + Improved leaderboard + bugfix
This commit is contained in:
parent
9fb74f051c
commit
3fb7b1fabb
|
@ -15,9 +15,9 @@
|
|||
- ~~Tetra League matches history~~
|
||||
- ~~Tetra League historic charts for tracked players~~
|
||||
- ~~Better UI with delta and hints for stats~~ *v0.2.0, we are here*
|
||||
- ~~Ability to compare player with APM-PPS-VS stats~~ *dev build are here*
|
||||
- Ability to fetch Tetra League leaderboard
|
||||
- Average stats for ranks
|
||||
- ~~Ability to compare player with APM-PPS-VS stats~~
|
||||
- ~~Ability to fetch Tetra League leaderboard~~
|
||||
- ~~Average stats for ranks~~ *dev build are here*
|
||||
- Ability to compare player with avgRank
|
||||
- UI Animations
|
||||
- i18n, EN and RU locales
|
||||
|
|
|
@ -13,6 +13,25 @@ const double appdspWeight = 140;
|
|||
const double vsapmWeight = 60;
|
||||
const double cheeseWeight = 1.25;
|
||||
const double gbeWeight = 315;
|
||||
const Map<String, double> ranksCutoffs = {
|
||||
"x": 0.01,
|
||||
"u": 0.05,
|
||||
"ss": 0.11,
|
||||
"s+": 0.17,
|
||||
"s": 0.23,
|
||||
"s-": 0.3,
|
||||
"a+": 0.38,
|
||||
"a": 0.46,
|
||||
"a-": 0.54,
|
||||
"b+": 0.62,
|
||||
"b": 0.7,
|
||||
"b-": 0.78,
|
||||
"c+": 0.84,
|
||||
"c": 0.9,
|
||||
"c-": 0.95,
|
||||
"d+": 0.975,
|
||||
"d": 1
|
||||
};
|
||||
|
||||
Duration doubleSecondsToDuration(double value) {
|
||||
value = value * 1000000;
|
||||
|
@ -719,7 +738,7 @@ class TetraLeagueAlpha {
|
|||
nextRank = json['next_rank'];
|
||||
nextAt = json['next_at'];
|
||||
percentileRank = json['percentile_rank'];
|
||||
nerdStats = (apm != null && pps != null && apm != null) ? NerdStats(apm!, pps!, vs!) : null;
|
||||
nerdStats = (apm != null && pps != null && vs != null) ? NerdStats(apm!, pps!, vs!) : null;
|
||||
estTr = (nerdStats != null) ? EstTr(apm!, pps!, vs!, (rd != null) ? rd! : 69, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe) : null;
|
||||
playstyle =
|
||||
(nerdStats != null) ? Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank) : null;
|
||||
|
@ -845,6 +864,55 @@ class TetrioPlayersLeaderboard {
|
|||
|
||||
TetrioPlayersLeaderboard(this.type, this.leaderboard);
|
||||
|
||||
List<dynamic> getAverageOfRank(String rank){
|
||||
List<TetrioPlayerFromLeaderboard> filtredLeaderboard = List.from(leaderboard);
|
||||
filtredLeaderboard.removeWhere((element) => element.rank != rank);
|
||||
if (filtredLeaderboard.isEmpty) throw Exception("Invalid rank");
|
||||
double avgAPM = 0, avgPPS = 0, avgVS = 0, avgTR = 0, avgGlicko = 0, avgRD = 0, lowestTR = 25000;
|
||||
int avgGamesPlayed = 0, avgGamesWon = 0, totalGamesPlayed = 0, totalGamesWon = 0;
|
||||
for (var entry in filtredLeaderboard){
|
||||
avgAPM += entry.apm;
|
||||
avgPPS += entry.pps;
|
||||
avgVS += entry.vs;
|
||||
avgTR += entry.rating;
|
||||
avgGlicko += entry.glicko;
|
||||
avgRD += entry.rd;
|
||||
totalGamesPlayed += entry.gamesPlayed;
|
||||
totalGamesWon += entry.gamesWon;
|
||||
if (entry.rating < lowestTR) lowestTR = entry.rating;
|
||||
}
|
||||
avgAPM /= filtredLeaderboard.length;
|
||||
avgPPS /= filtredLeaderboard.length;
|
||||
avgVS /= filtredLeaderboard.length;
|
||||
avgTR /= filtredLeaderboard.length;
|
||||
avgGlicko /= filtredLeaderboard.length;
|
||||
avgRD /= filtredLeaderboard.length;
|
||||
avgGamesPlayed = (totalGamesPlayed / filtredLeaderboard.length).floor();
|
||||
avgGamesWon = (totalGamesWon / filtredLeaderboard.length).floor();
|
||||
return [TetraLeagueAlpha(apm: avgAPM, pps: avgPPS, vs: avgVS, glicko: avgGlicko, rd: avgRD, gamesPlayed: avgGamesPlayed, gamesWon: avgGamesWon, bestRank: rank, decaying: false, rating: avgTR, rank: rank, percentileRank: rank, percentile: 0, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1),
|
||||
{"totalGamesPlayed": totalGamesPlayed, "totalGamesWon": totalGamesWon, "players": filtredLeaderboard.length, "lowestTR": lowestTR, "toEnterTR": leaderboard[(leaderboard.length * ranksCutoffs[rank]!).floor()-1].rating}];
|
||||
}
|
||||
|
||||
Map<String, List<dynamic>> get averages => {
|
||||
'x': getAverageOfRank("x"),
|
||||
'u': getAverageOfRank("u"),
|
||||
'ss': getAverageOfRank("ss"),
|
||||
's+': getAverageOfRank("s+"),
|
||||
's': getAverageOfRank("s"),
|
||||
's-': getAverageOfRank("s-"),
|
||||
'a+': getAverageOfRank("a+"),
|
||||
'a': getAverageOfRank("a"),
|
||||
'a-': getAverageOfRank("a-"),
|
||||
'b+': getAverageOfRank("b+"),
|
||||
'b': getAverageOfRank("b"),
|
||||
'b-': getAverageOfRank("b-"),
|
||||
'c+': getAverageOfRank("c+"),
|
||||
'c': getAverageOfRank("c"),
|
||||
'c-': getAverageOfRank("c-"),
|
||||
'd+': getAverageOfRank("d+"),
|
||||
'd': getAverageOfRank("d")
|
||||
};
|
||||
|
||||
TetrioPlayersLeaderboard.fromJson(List<dynamic> json, String t, DateTime ts) {
|
||||
type = t;
|
||||
timestamp = ts;
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||
import 'package:tetra_stats/services/sqlite_db_controller.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||
|
|
|
@ -820,7 +820,7 @@ class CompareState extends State<CompareView> {
|
|||
)
|
||||
],
|
||||
)
|
||||
] : [Text("Please, enter username, user ID, or APM-PPS-VS values (divider doesn't matter) to both of fields")],
|
||||
] : [const Text("Please, enter username, user ID, or APM-PPS-VS values (divider doesn't matter) to both of fields")],
|
||||
)
|
||||
),
|
||||
),
|
||||
|
|
|
@ -31,7 +31,8 @@ final NumberFormat f4 = NumberFormat.decimalPatternDigits(decimalDigits: 4);
|
|||
final DateFormat dateFormat = DateFormat.yMMMd().add_Hms();
|
||||
|
||||
class MainView extends StatefulWidget {
|
||||
const MainView({Key? key}) : super(key: key);
|
||||
final String? player;
|
||||
const MainView({Key? key, this.player}) : super(key: key);
|
||||
|
||||
String get title => "Tetra Stats: $_titleNickname";
|
||||
|
||||
|
@ -89,8 +90,12 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
teto.open();
|
||||
_scrollController = ScrollController();
|
||||
_tabController = TabController(length: 6, vsync: this);
|
||||
_getPreferences()
|
||||
if (widget.player != null){
|
||||
changePlayer(widget.player!);
|
||||
}else{
|
||||
_getPreferences()
|
||||
.then((value) => changePlayer(prefs.getString("player") ?? "dan63047"));
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -178,7 +183,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
drawer: NavDrawer(changePlayer),
|
||||
drawer: widget.player == null ? NavDrawer(changePlayer) : null,
|
||||
appBar: AppBar(
|
||||
title: !_searchBoolean
|
||||
? Text(
|
||||
|
@ -222,6 +227,10 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
const PopupMenuItem(
|
||||
value: "refresh",
|
||||
child: Text('Refresh'),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: "/states",
|
||||
child: Text('Show stored data'),
|
||||
|
@ -236,7 +245,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
),
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == "tll") {teto.fetchTLLeaderboard();
|
||||
if (value == "refresh") {changePlayer(_searchFor);
|
||||
return;}
|
||||
Navigator.pushNamed(context, value);
|
||||
},
|
||||
|
@ -542,7 +551,7 @@ class _HistoryChartThigy extends StatelessWidget{
|
|||
height: MediaQuery.of(context).size.height - 100,
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding( padding: bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 0, 48) ,
|
||||
Padding( padding: bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 16, 48) ,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineBarsData: [LineChartBarData(spots: data)],
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:tetra_stats/views/tl_leaderboard_view.dart';
|
||||
|
||||
class RankAveragesView extends StatefulWidget {
|
||||
const RankAveragesView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => RanksAverages();
|
||||
}
|
||||
|
||||
class RanksAverages extends State<RankAveragesView> {
|
||||
Map<String, List<dynamic>> averages = {};
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
teto.fetchTLLeaderboard().then((value) {averages = value.averages; setState(() {
|
||||
});});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Ranks averages"),
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
body: SafeArea(
|
||||
child: ListView.builder(
|
||||
itemCount: averages.length,
|
||||
itemBuilder: (context, index){
|
||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||
List<String> keys = averages.keys.toList();
|
||||
return ListTile(
|
||||
leading: Image.asset("res/tetrio_tl_alpha_ranks/${keys[index]}.png", height: 48),
|
||||
title: Text("${averages[keys[index]]?[1]["players"]} players", style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||
subtitle: Text("${f2.format(averages[keys[index]]?[0].apm)} APM, ${f2.format(averages[keys[index]]?[0].pps)} PPS, ${f2.format(averages[keys[index]]?[0].vs)} VS, ${f2.format(averages[keys[index]]?[0].nerdStats.app)} APP, ${f2.format(averages[keys[index]]?[0].nerdStats.vsapm)} VS/APM"),
|
||||
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null));
|
||||
})
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||
import 'package:tetra_stats/views/states_view.dart';
|
||||
import 'package:tetra_stats/views/main_view.dart';
|
||||
import 'package:tetra_stats/views/ranks_averages_view.dart';
|
||||
|
||||
final TetrioService teto = TetrioService();
|
||||
|
||||
|
@ -22,6 +22,21 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Tetra League Leaderboard"),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const RankAveragesView(),
|
||||
maintainState: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.compress),
|
||||
tooltip: "Averages",
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
body: SafeArea(
|
||||
|
@ -39,9 +54,9 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
|
|||
headerSliverBuilder: (context, value) {
|
||||
String howManyPlayers(int numberOfPlayers) => Intl.plural(
|
||||
numberOfPlayers,
|
||||
zero: 'Empty list. Press "Track" button in previous view to add current player here',
|
||||
one: 'There is only one player',
|
||||
other: 'There are $numberOfPlayers players',
|
||||
zero: 'Empty list. Looks like something is wrong...',
|
||||
one: 'There is only one player... What?',
|
||||
other: 'There are $numberOfPlayers ranked players.',
|
||||
name: 'howManyPeople',
|
||||
args: [numberOfPlayers],
|
||||
desc: 'Description of how many people are seen in a place.',
|
||||
|
@ -62,25 +77,27 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
|
|||
body: ListView.builder(
|
||||
itemCount: allPlayers!.length,
|
||||
itemBuilder: (context, index) {
|
||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||
return ListTile(
|
||||
leading: Text((index+1).toString(), style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)),
|
||||
title: Text("${allPlayers[index].username}", style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||
leading: Text((index+1).toString(), style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28) : null),
|
||||
title: Text(allPlayers[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||
subtitle: Text(
|
||||
"${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].app)} APP, ${f2.format(allPlayers[index].vsapm)} VS/APM"),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("${f2.format(allPlayers[index].rating)} TR", style: const TextStyle(fontSize: 28)),
|
||||
Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: 48),
|
||||
Text("${f2.format(allPlayers[index].rating)} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
|
||||
Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: bigScreen ? 48 : 16),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => StatesView(states: allPlayers!),
|
||||
// ),
|
||||
// );
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MainView(player: allPlayers[index].userId),
|
||||
maintainState: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}));
|
||||
|
|
|
@ -159,6 +159,9 @@ flutter:
|
|||
- res/tetrio_badges/tawsignite_expert.png
|
||||
- res/tetrio_badges/tawslg.png
|
||||
- res/tetrio_badges/tetralympic_masters.png
|
||||
- res/tetrio_badges/thaitour_1.png
|
||||
- res/tetrio_badges/thaitour_2.png
|
||||
- res/tetrio_badges/thaitour_3.png
|
||||
- res/tetrio_badges/ttsdtc_1.png
|
||||
- res/tetrio_badges/ttsdtc_2.png
|
||||
- res/tetrio_badges/ttsdtc_3.png
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Loading…
Reference in New Issue