leaderboards done (kinda)

This commit is contained in:
dan63047 2024-09-15 19:38:07 +03:00
parent c3214f5ba9
commit dbdfe29dc0
5 changed files with 142 additions and 34 deletions

View File

@ -3,9 +3,11 @@
import 'package:tetra_stats/data_objects/aggregate_stats.dart';
import 'package:tetra_stats/data_objects/record_extras.dart';
import 'package:tetra_stats/data_objects/results_stats.dart';
import 'package:tetra_stats/data_objects/tetrio_prisecter.dart';
class RecordSingle {
late String? userId;
late String username;
late String replayId;
late String ownId;
late String gamemode;
@ -15,8 +17,9 @@ class RecordSingle {
late int countryRank;
late AggregateStats aggregateStats;
late RecordExtras extras;
late Prisecter prisecter;
RecordSingle({required this.userId, required this.replayId, required this.ownId, required this.timestamp, required this.stats, required this.rank, required this.countryRank, required this.aggregateStats});
RecordSingle({required this.replayId, required this.ownId, required this.timestamp, required this.stats, required this.rank, required this.countryRank, required this.aggregateStats});
RecordSingle.fromJson(Map<String, dynamic> json, int ran, int cran) {
ownId = json['_id'];
@ -24,10 +27,14 @@ class RecordSingle {
stats = ResultsStats.fromJson(json['results']['stats']);
replayId = json['replayid'];
timestamp = DateTime.parse(json['ts']);
if (json['user'] != null) userId = json['user']['id'];
if (json['user'] != null) {
userId = json['user']['id'];
username = json['user']['username'];
}
rank = ran;
countryRank = cran;
aggregateStats = AggregateStats.fromJson(json['results']['aggregatestats']);
prisecter = Prisecter.fromJson(json['p']);
var ex = json['extras'] as Map<String, dynamic>;
switch (ex.keys.firstOrNull){
case "zenith":

View File

@ -6,6 +6,7 @@ import 'package:tetra_stats/data_objects/est_tr.dart';
import 'package:tetra_stats/data_objects/nerd_stats.dart';
import 'package:tetra_stats/data_objects/playstyle.dart';
import 'package:tetra_stats/data_objects/tetrio_constants.dart';
import 'package:tetra_stats/data_objects/tetrio_prisecter.dart';
class TetrioPlayerFromLeaderboard {
late String userId;
@ -33,6 +34,8 @@ class TetrioPlayerFromLeaderboard {
late int gamesWonTotal;
late Duration playtime;
late int ar;
late Map<String, int> ar_counts;
late Prisecter prisecter;
TetrioPlayerFromLeaderboard(
this.userId,
@ -91,6 +94,11 @@ class TetrioPlayerFromLeaderboard {
gamesWonTotal = json['gameswon'] as int;
playtime = Duration(microseconds: (json['gametime'].toDouble() * 1000000).floor());
ar = json['ar'];
ar_counts = {};
for (var entry in json['ar_counts'].keys){
ar_counts[entry.toString()] = json['ar_counts'][entry];
}
prisecter = Prisecter.fromJson(json['p']);
nerdStats = NerdStats(apm, pps, vs);
estTr = EstTr(apm, pps, vs, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);
playstyle = Playstyle(apm, pps, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank);

View File

@ -0,0 +1,18 @@
class Prisecter {
late final num pri;
late final num sec;
late final num ter;
Prisecter(this.pri, this.sec, this.ter);
@override
String toString() {
return "${pri}:${sec}:${ter}";
}
Prisecter.fromJson(Map<String, dynamic> json){
pri = json['pri'];
sec = json['sec'];
ter = json['ter'];
}
}

View File

@ -798,8 +798,7 @@ class TetrioService extends DB {
}
}
Future<List<TetrioPlayerFromLeaderboard>> fetchTetrioLeaderboard({double? after, String? lb}) async {
const int lbLength = 100;
Future<List<TetrioPlayerFromLeaderboard>> fetchTetrioLeaderboard({String? prisecter, String? lb}) async {
// TetrioPlayersLeaderboard? cached = _cache.get("league", TetrioPlayersLeaderboard);
// if (cached != null) return cached;
@ -809,7 +808,7 @@ class TetrioService extends DB {
} else {
url = Uri.https('ch.tetr.io', 'api/users/by/${lb??"league"}', {
"limit": "100",
if (after != null && after != -1) "after": "$after:0:0"
if (prisecter != null) "after": prisecter
});
}
try{
@ -853,6 +852,57 @@ class TetrioService extends DB {
}
}
Future<List<RecordSingle>> fetchTetrioRecordsLeaderboard({String? prisecter, String? lb}) async{
Uri url;
if (kIsWeb) {
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLLeaderboard"});
} else {
url = Uri.https('ch.tetr.io', 'api/records/${lb??"40l_global"}', {
"limit": "100",
if (prisecter != null) "after": prisecter
});
}
try{
final response = await client.get(url);
switch (response.statusCode) {
case 200:
_lbPositions.clear();
var rawJson = jsonDecode(response.body);
if (rawJson['success']) { // if api confirmed that everything ok
List<RecordSingle> leaderboard = [];
for (Map<String, dynamic> entry in rawJson['data']['entries']) {
leaderboard.add(RecordSingle.fromJson(entry, -1, -1));
}
developer.log("fetchTLLeaderboard: Leaderboard retrieved and cached", name: "services/tetrio_crud");
//_leaderboardsCache[rawJson['cache']['cached_until'].toString()] = leaderboard;
//_cache.store(leaderboard, rawJson['cache']['cached_until']);
return leaderboard;
} else { // idk how to hit that one
developer.log("fetchTLLeaderboard: Bruh", name: "services/tetrio_crud", error: rawJson);
throw Exception("Failed to get leaderboard (problems on the tetr.io side)"); // will it be on tetr.io side?
}
case 403:
throw TetrioForbidden();
case 429:
throw TetrioTooManyRequests();
case 418:
throw TetrioOskwareBridgeProblem();
case 500:
case 502:
case 503:
case 504:
throw TetrioInternalProblem();
default:
developer.log("fetchTLLeaderboard: Failed to fetch leaderboard", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
}
} on http.ClientException catch (e, s) {
developer.log("$e, $s");
throw http.ClientException(e.message, e.uri);
}
}
TetrioPlayersLeaderboard? getCachedLeaderboard(){
return _cache.get("league", TetrioPlayersLeaderboard);
}

View File

@ -200,6 +200,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
0 => DestinationHome(searchFor: _searchFor, constraints: constraints),
1 => DestinationGraphs(searchFor: _searchFor, constraints: constraints),
2 => DestinationLeaderboards(constraints: constraints),
3 => DestinationCutoffs(constraints: constraints),
_ => Text("Unknown destination $destination")
},
)
@ -215,14 +216,17 @@ class DestinationCutoffs extends StatefulWidget{
const DestinationCutoffs({super.key, required this.constraints});
@override
State<DestinationLeaderboards> createState() => _DestinationCutoffsState();
State<DestinationCutoffs> createState() => _DestinationCutoffsState();
}
class _DestinationCutoffsState extends State<DestinationLeaderboards> {
class _DestinationCutoffsState extends State<DestinationCutoffs> {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
return Column(
children: [
Card(),
]
);
}
}
@ -238,19 +242,31 @@ class DestinationLeaderboards extends StatefulWidget{
enum Leaderboards{
tl,
xp,
ar
ar,
sprint,
blitz,
zenith,
zenithex,
}
class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
//Duration postSeasonLeft = seasonStart.difference(DateTime.now());
final Map<Leaderboards, String> leaderboards = {Leaderboards.tl: "Tetra League", Leaderboards.xp: "XP", Leaderboards.ar: "Acievement Points"};
final Map<Leaderboards, String> leaderboards = {
Leaderboards.tl: "Tetra League",
Leaderboards.xp: "XP",
Leaderboards.ar: "Acievement Points",
Leaderboards.sprint: "40 Lines",
Leaderboards.blitz: "Blitz",
Leaderboards.zenith: "Quick Play",
Leaderboards.zenithex: "Quick Play Expert",
};
Leaderboards _currentLb = Leaderboards.tl;
final StreamController<List<TetrioPlayerFromLeaderboard>> _dataStreamController = StreamController<List<TetrioPlayerFromLeaderboard>>();
final StreamController<List<dynamic>> _dataStreamController = StreamController<List<dynamic>>();
late final ScrollController _scrollController;
Stream<List<TetrioPlayerFromLeaderboard>> get dataStream => _dataStreamController.stream;
List<TetrioPlayerFromLeaderboard> list = [];
Stream<List<dynamic>> get dataStream => _dataStreamController.stream;
List<dynamic> list = [];
bool _isFetchingData = false;
double after = 25000.00;
String? prisecter;
Future<void> _fetchData() async {
if (_isFetchingData) {
@ -262,19 +278,19 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
setState(() {});
final items = switch(_currentLb){
Leaderboards.tl => await teto.fetchTetrioLeaderboard(after: after),
Leaderboards.xp => await teto.fetchTetrioLeaderboard(after: after, lb: "xp"),
Leaderboards.ar => await teto.fetchTetrioLeaderboard(after: after, lb: "ar"),
Leaderboards.tl => await teto.fetchTetrioLeaderboard(prisecter: prisecter),
Leaderboards.xp => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "xp"),
Leaderboards.ar => await teto.fetchTetrioLeaderboard(prisecter: prisecter, lb: "ar"),
Leaderboards.sprint => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter),
Leaderboards.blitz => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "blitz_global"),
Leaderboards.zenith => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenith_global"),
Leaderboards.zenithex => await teto.fetchTetrioRecordsLeaderboard(prisecter: prisecter, lb: "zenithex_global"),
};
list.addAll(items);
_dataStreamController.add(list);
after = switch (_currentLb){
Leaderboards.tl => list.last.tr,
Leaderboards.xp => list.last.xp,
Leaderboards.ar => list.last.ar.toDouble(),
};
prisecter = list.last.prisecter.toString();
} catch (e) {
_dataStreamController.addError(e);
} finally {
@ -332,11 +348,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
onTap: () {
_currentLb = leaderboards.keys.elementAt(index);
list.clear();
after = switch (_currentLb){
Leaderboards.tl => 25000.00,
Leaderboards.xp => -1.00,
Leaderboards.ar => -1.00,
};
prisecter = null;
_fetchData();
},
),
@ -350,7 +362,7 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
SizedBox(
width: widget.constraints.maxWidth - 350 - 88,
child: Card(
child: StreamBuilder<List<TetrioPlayerFromLeaderboard>>(
child: StreamBuilder<List<dynamic>>(
stream: dataStream,
builder:(context, snapshot) {
switch (snapshot.connectionState){
@ -371,12 +383,25 @@ class _DestinationLeaderboardsState extends State<DestinationLeaderboards> {
itemBuilder: (BuildContext context, int index){
return ListTile(
leading: Text(intf.format(index+1)),
title: Text(snapshot.data![index].username),
title: Text(snapshot.data![index].username, style: TextStyle(fontSize: 22)),
trailing: Text(switch (_currentLb){
Leaderboards.tl => f2.format(snapshot.data![index].tr),
Leaderboards.xp => f2.format(snapshot.data![index].level),
Leaderboards.ar => intf.format(snapshot.data![index].ar),
}),
Leaderboards.tl => "${f2.format(snapshot.data![index].tr)} TR",
Leaderboards.xp => "LVL ${f2.format(snapshot.data![index].level)}",
Leaderboards.ar => "${intf.format(snapshot.data![index].ar)} AR",
Leaderboards.sprint => get40lTime(snapshot.data![index].stats.finalTime.inMicroseconds),
Leaderboards.blitz => intf.format(snapshot.data![index].stats.score),
Leaderboards.zenith => "${f2.format(snapshot.data![index].stats.zenith!.altitude)} m",
Leaderboards.zenithex => "${f2.format(snapshot.data![index].stats.zenith!.altitude)} m"
}, style: TextStyle(fontSize: 28)),
subtitle: Text(switch (_currentLb){
Leaderboards.tl => "${f2.format(snapshot.data![index].apm)} APM, ${f2.format(snapshot.data![index].pps)} PPS, ${f2.format(snapshot.data![index].vs)} VS, ${f2.format(snapshot.data![index].nerdStats.app)} APP, ${f2.format(snapshot.data![index].nerdStats.vsapm)} VS/APM",
Leaderboards.xp => "${f2.format(snapshot.data![index].xp)} XP${snapshot.data![index].playtime.isNegative ? "" : ", ${playtime(snapshot.data![index].playtime)} of gametime"}",
Leaderboards.ar => "${snapshot.data![index].ar_counts}",
Leaderboards.sprint => "${intf.format(snapshot.data![index].stats.finesse.faults)} FF, ${f2.format(snapshot.data![index].stats.kpp)} KPP, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${intf.format(snapshot.data![index].stats.piecesPlaced)} P",
Leaderboards.blitz => "lvl ${snapshot.data![index].stats.level}, ${f2.format(snapshot.data![index].stats.pps)} PPS, ${f2.format(snapshot.data![index].stats.spp)} SPP",
Leaderboards.zenith => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B",
Leaderboards.zenithex => "${f2.format(snapshot.data![index].aggregateStats.apm)} APM, ${f2.format(snapshot.data![index].aggregateStats.pps)} PPS, ${intf.format(snapshot.data![index].stats.kills)} KO's, ${f2.format(snapshot.data![index].stats.cps)} climb speed (${f2.format(snapshot.data![index].stats.zenith!.peakrank)} peak), ${intf.format(snapshot.data![index].stats.topBtB)} B2B"
}, style: TextStyle(color: Colors.grey, fontSize: 12)),
);
}
),