Ranks averages + Improved leaderboard + bugfix

This commit is contained in:
dan63047 2023-07-09 19:50:17 +03:00
parent 9fb74f051c
commit 3fb7b1fabb
11 changed files with 168 additions and 28 deletions

View File

@ -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

View File

@ -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;

View File

@ -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';

View File

@ -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")],
)
),
),

View File

@ -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)],

View File

@ -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));
})
),
);
}
}

View File

@ -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,
),
);
},
);
}));

View File

@ -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