Merge pull request #108 from dan63047/master

лылодывоиаышгауоиж
This commit is contained in:
dan63047 2024-07-10 21:33:55 +03:00 committed by GitHub
commit 3bd5d94cef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 947 additions and 186 deletions

View File

@ -1,25 +1,551 @@
//import 'dart:convert';
//import 'dart:io';
// import 'dart:convert';
// import 'dart:developer' as developer;
// import 'dart:ffi';
// import 'dart:math';
// import 'package:logging/logging.dart' as logging;
// import 'package:vector_math/vector_math_64.dart';
// import 'dart:io';
// import 'tetrio_multiplayer_replay.dart';
//import 'package:path_provider/path_provider.dart';
// class HandlingHandler{
// double das;
// double arr;
// late double dasLeft; // frames
// late double arrLeft; // frames
// bool sdfActive = false;
// bool activeLeft = false;
// bool activeRight = false;
// int direction = 0; // -1 - left, 1 - right, 0 - none
//import 'tetrio_multiplayer_replay.dart';
// HandlingHandler(this.das, this.arr){
// dasLeft = das;
// arrLeft = arr;
// }
/// That thing allows me to test my new staff i'm trying to implement
//void main() async {
// List<Tetromino> queue = List.from(tetrominoes);
// TetrioRNG rng = TetrioRNG(0);
// queue = rng.shuffleList(queue);
// print(queue);
// queue = List.from(tetrominoes);
// queue = rng.shuffleList(queue);
// print(queue);
// @override
// String toString(){
// return "das: ${das}f; arr: ${arr}f";
// }
// var downloadPath = await getDownloadsDirectory();
// ReplayData replay = ReplayData.fromJson(jsonDecode(File("${downloadPath!.path}/65b504a9ade6d287b8427af0").readAsStringSync()));
// List<List<Tetromino>> board = [for (var i = 0 ; i < 40; i++) [for (var i = 0 ; i < 10; i++) Tetromino.empty]];
// print(replay.rawJson);
// int movementKeyPressed(bool left, bool right, double subframe){
// if (left) {
// activeLeft = left;
// direction = -1;
// }
// if (right) {
// activeRight = right;
// direction = 1;
// }
// dasLeft = das - (1 - subframe);
// return direction;
// }
//print("");
// void movementKeyReleased(bool left, bool right, double subframe){
// if (left) {
// activeLeft = !left;
// }
// if (right) {
// activeRight = !right;
// }
// if (activeLeft) {
// direction = -1;
// }
// if (activeRight) {
// direction = 1;
// }
// if (activeLeft && activeRight){
// arrLeft = arr;
// dasLeft = das;
// direction = 0;
// }
// }
// int processMovenent(double delta){
// if (!activeLeft && !activeRight) return 0;
// if (dasLeft > 0.0) {
// dasLeft -= delta;
// if (dasLeft < 0.0) {
// arrLeft += dasLeft;
// dasLeft = 0.0;
// return direction;
// }else{
// return 0;
// }
// }else{
// arrLeft -= delta;
// if (arr == 0.0) return direction*10;
// if (arrLeft < 0.0) {
// arrLeft += arr;
// return direction;
// }else {
// return 0;
// }
// }
// }
// }
// class Board{
// int width;
// int height;
// int bufferHeight;
// late List<List<Tetromino>> board;
// late int totalHeight;
// Board(this.width, this.height, this.bufferHeight){
// totalHeight = height+bufferHeight;
// board = [for (var i = 0 ; i < totalHeight; i++) [for (var i = 0 ; i < width; i++) Tetromino.empty]];
// }
// @override
// String toString() {
// String result = "";
// for (var row in board.reversed){
// for (var cell in row) result += cell.name[0];
// result += "\n";
// }
// return result;
// }
// bool isOccupied(Coords coords)
// {
// if (coords.x < 0 || coords.x >= width ||
// coords.y < 0 || coords.y >= totalHeight ||
// board[coords.y][coords.x] != Tetromino.empty) return true;
// return false;
// }
// bool positionIsValid(Tetromino type, Coords coords, int r){
// List<Coords> shape = shapes[type.index][r];
// for (Coords mino in shape){
// if (isOccupied(coords+mino)) return false;
// }
// return true;
// }
// bool wasATSpin(Tetromino type, Coords coords, int rot){
// if (!(positionIsValid(type, Coords(coords.x+1, coords.y), rot) ||
// positionIsValid(type, Coords(coords.x-1, coords.y), rot) ||
// positionIsValid(type, Coords(coords.x, coords.y+1), rot) ||
// positionIsValid(type, Coords(coords.x, coords.y-1), rot))
// ){
// return true;
// }
// return false;
// }
// void writeToBoard(Tetromino type, Coords coords, int rot) {
// if (!positionIsValid(type, coords, rot)) throw Exception("Attempted to write $type to $coords in $rot rot");
// List<Coords> shape = shapes[type.index][rot];
// for (Coords mino in shape){
// var finalCoords = coords+mino;
// board[finalCoords.y][finalCoords.x] = type;
// }
// }
// void writeGarbage(GarbageData data, [int? amt]){
// List<List<Tetromino>> garbage = [for (int i = 0; i < (amt??data.amt!); i++) [for (int k = 0; k < width; k++) k == data.column! ? Tetromino.empty : Tetromino.garbage]];
// board.insertAll(0, garbage);
// board.removeRange(height-garbage.length, height);
// }
// List<int> clearFullLines(){
// int linesCleared = 0;
// int garbageLines = 0;
// int difficultLineClear = 0;
// for (int i = 0; i < board.length; i++){
// if (board[i-linesCleared].every((element) => element != Tetromino.empty)){
// if (board[i-linesCleared].any((element) => element == Tetromino.garbage)) garbageLines++;
// board.removeAt(i-linesCleared);
// List<Tetromino> emptyRow = [for (int t = 0; t < width; t++) Tetromino.empty];
// board.add(emptyRow);
// linesCleared += 1;
// }
// }
// if (linesCleared >= 4) difficultLineClear = 1;
// return [linesCleared, garbageLines, difficultLineClear];
// }
// }
// class IncomingGarbage{
// int? frameOfThreat; // will enter board after this frame, null if unconfirmed
// GarbageData data;
// IncomingGarbage(this.data);
// @override
// String toString(){
// return "f$frameOfThreat: col${data.column} amt${data.amt}";
// }
// void confirm(int confirmationFrame, int garbageSpeed){
// frameOfThreat = confirmationFrame+garbageSpeed;
// }
// }
// class LineClearResult{
// int linesCleared;
// Tetromino piece;
// bool spin;
// int garbageCleared;
// int column;
// int attackProduced;
// LineClearResult(this.linesCleared, this.piece, this.spin, this.garbageCleared, this.column, this.attackProduced);
// }
// class Stats{
// int combo = -1;
// int btb = -1;
// int attackRecived = 0;
// int attackTanked = 0;
// LineClearResult processLineClear(List<int> clearFullLinesResult, Tetromino current, Coords pos, bool spinWasLastMove, bool tspin){
// if (clearFullLinesResult[0] > 0) combo++;
// else combo = -1;
// if (clearFullLinesResult[2] > 0) btb++;
// else btb = -1;
// int attack = 0;
// switch (clearFullLinesResult[0]){
// case 0:
// if (spinWasLastMove && tspin) {
// attack = garbage['t-spin']!;
// }
// break;
// case 1:
// if (spinWasLastMove && tspin) {
// attack = garbage['t-spin single']!;
// }else{
// attack = garbage['single']!;
// }
// break;
// case 2:
// if (spinWasLastMove && tspin) {
// attack = garbage['t-spin double']!;
// }else{
// attack = garbage['double']!;
// }
// break;
// case 3:
// if (spinWasLastMove && tspin) {
// attack = garbage['t-spin triple']!;
// }else{
// attack = garbage['triple']!;
// }
// break;
// case 4:
// if (spinWasLastMove && tspin) {
// attack = garbage['t-spin quad']!;
// }else{
// attack = garbage['quad']!;
// }
// break;
// case 5:
// if (spinWasLastMove && tspin) {
// attack = garbage['t-spin penta']!;
// }else{
// attack = garbage['penta']!;
// }
// break;
// case _:
// developer.log("${clearFullLinesResult[0]} lines cleared");
// break;
// }
// return LineClearResult(clearFullLinesResult[0], Tetromino.empty, false, clearFullLinesResult[1], 0, attack);
// }
// }
// class Simulation{
// }
// // That thing allows me to test my new staff i'm trying to implement
// void main() async {
// var replayJson = jsonDecode(File("/home/dan63047/Документы/replays/6550eecf2ffc5604e6224fc5.ttrm").readAsStringSync());
// // frame 994: garbage lost
// // frame 1550: T-spin failed
// ReplayData replay = ReplayData.fromJson(replayJson);
// TetrioRNG rng = TetrioRNG(replay.stats[0][0].seed);
// List<Tetromino> queue = rng.shuffleList(tetrominoes.toList());
// List<Event> events = readEventList(replay.rawJson);
// DataFullOptions? settings;
// HandlingHandler? handling;
// Map<KeyType, EventKeyPress> activeKeypresses = {};
// int currentFrame = 0;
// double subframesWent = 0;
// events.removeAt(0); // get rig of Event.start
// Event nextEvent = events.removeAt(0);
// Stats stats = Stats();
// Board board = Board(10, 20, 20);
// KicksetBase kickset = SRSPlus();
// List<IncomingGarbage> garbageQueue = [];
// Tetromino? hold;
// int rot = 0;
// bool spinWasLastMove = false;
// Coords coords = Coords(3, 21);
// double lockDelay = 30; // frames
// int lockResets = 15;
// bool floored = false;
// double gravityBucket = 1.0;
// developer.log("Seed is ${replay.stats[0][0].seed}, first bag is $queue");
// Tetromino current = queue.removeAt(0);
// //developer.log("Second bag is ${rng.shuffleList(tetrominoes)}");
// int sonicDrop(){
// int height = coords.y;
// while (board.positionIsValid(current, Coords(coords.x, height), rot)){
// height -= 1;
// }
// height += 1;
// return height;
// }
// Tetromino getNewOne(){
// if (queue.length <= 1) {
// List<Tetromino> nextPieces = rng.shuffleList(tetrominoes.toList());
// queue.addAll(nextPieces);
// }
// //developer.log("Next queue is $queue");
// rot = 0;
// lockResets = 15;
// lockDelay = 30;
// floored = false;
// gravityBucket = 1.0;
// return queue.removeAt(0);
// }
// bool handleRotation(int r){
// if (r == 0) return true;
// int futureRotation = (rot + r) % 4;
// List<Coords> tests = (current == Tetromino.I ? kickset.kickTableI : kickset.kickTable)[rot][r == -1 ? 0 : r];
// for (Coords test in tests){
// if (board.positionIsValid(current, coords+test, futureRotation)){
// coords += test;
// rot = futureRotation;
// return true;
// }
// }
// return false;
// }
// void handleHardDrop(){
// board.writeToBoard(current, coords, rot);
// bool tspin = board.wasATSpin(current, coords, rot);
// LineClearResult lineClear = stats.processLineClear(board.clearFullLines(), current, coords, spinWasLastMove, tspin);
// print("${lineClear.linesCleared} lines, ${lineClear.garbageCleared} garbage");
// if (garbageQueue.isNotEmpty) {
// if (lineClear.linesCleared > 0){
// int garbageToDelete = lineClear.attackProduced;
// for (IncomingGarbage garbage in garbageQueue){
// if (garbage.data.amt! >= garbageToDelete) {
// garbageToDelete -= garbage.data.amt!;
// lineClear.attackProduced = garbageToDelete;
// garbage.data.amt = 0;
// }else{
// garbage.data.amt = garbage.data.amt! - garbageToDelete;
// lineClear.attackProduced = 0;
// break;
// }
// }
// }else{
// int needToTake = settings!.garbageCap!;
// for (IncomingGarbage garbage in garbageQueue){
// if ((garbage.frameOfThreat??99999999) > currentFrame) break;
// if (garbage.data.amt! > needToTake) {
// board.writeGarbage(garbage.data, needToTake);
// garbage.data.amt = garbage.data.amt! - needToTake;
// break;
// } else {
// board.writeGarbage(garbage.data);
// needToTake -= garbage.data.amt!;
// garbage.data.amt = 0;
// }
// }
// }
// garbageQueue.removeWhere((element) => element.data.amt == 0);
// }
// current = getNewOne();
// coords = Coords(3, 21) + spawnPositionFixes[current.index];
// }
// void handleGravity(double frames){
// if (frames == 0) return;
// gravityBucket += settings != null ? (handling!.sdfActive ? max(settings.g! * settings.handling!.sdf, 0.05 * settings.handling!.sdf) : settings.g!) * frames : 0;
// int gravityImpact = 0;
// if (gravityBucket >= 1.0){
// gravityImpact = gravityBucket.truncate();
// gravityBucket -= gravityBucket.truncate();
// }
// while (gravityImpact > 0){
// if (board.positionIsValid(current, Coords(coords.x, coords.y-1), rot)) {coords.y -= 1; floored = false;}
// else floored = true;
// gravityImpact--;
// }
// if (floored) lockDelay -= frames;
// if (lockDelay <= 0 && floored){
// handleHardDrop();
// }
// }
// void handleMovement(double frames){
// if (frames == 0 || handling == null) return;
// int movement = handling.processMovenent(frames);
// while (movement.abs() > 0){
// if (board.positionIsValid(current, Coords(movement.isNegative ? coords.x-1 : coords.x+1, coords.y), rot)) movement.isNegative ? coords.x-- : coords.x++;
// movement.isNegative ? movement++ : movement--;
// }
// }
// coords += spawnPositionFixes[current.index];
// for (currentFrame; currentFrame <= replay.roundLengths[0]; currentFrame++){
// handleMovement(1-subframesWent);
// handleGravity(1-subframesWent);
// subframesWent = 0;
// if (settings?.handling?.sdf == 41) coords.y = sonicDrop();
// print("$currentFrame: $current at $coords\n$board");
// //print(stats.combo);
// if (currentFrame == nextEvent.frame){
// while (currentFrame == nextEvent.frame){
// print("Processing $nextEvent");
// switch (nextEvent.type){
// case EventType.start:
// developer.log("go");
// break;
// case EventType.full:
// settings = (nextEvent as EventFull).data.options;
// handling = HandlingHandler(settings!.handling!.das.toDouble(), settings.handling!.arr.toDouble());
// lockDelay = settings.locktime!.toDouble();
// lockResets = settings.lockresets!;
// break;
// case EventType.keydown:
// nextEvent as EventKeyPress;
// double subframesDiff = nextEvent.data.subframe - subframesWent;
// subframesWent += subframesDiff;
// handleMovement(subframesDiff);
// handleGravity(subframesDiff);
// //activeKeypresses[nextEvent.data.key] = nextEvent;
// switch (nextEvent.data.key){
// case KeyType.rotateCCW:
// handleRotation(-1);
// break;
// case KeyType.rotateCW:
// handleRotation(1);
// break;
// case KeyType.rotate180:
// handleRotation(2);
// break;
// case KeyType.moveLeft:
// case KeyType.moveRight:
// int pontencialMovement = handling!.movementKeyPressed(nextEvent.data.key == KeyType.moveLeft, nextEvent.data.key == KeyType.moveRight, nextEvent.data.subframe);
// if (board.positionIsValid(current, Coords(coords.x+pontencialMovement, coords.y), rot)) coords.x += pontencialMovement;
// break;
// case KeyType.softDrop:
// handling!.sdfActive = true;
// break;
// case KeyType.hardDrop:
// coords.y = sonicDrop();
// handleHardDrop();
// case KeyType.hold:
// switch (hold){
// case null:
// hold = current;
// current = getNewOne();
// break;
// case _:
// Tetromino temp;
// temp = hold;
// hold = current;
// current = temp;
// rot = 0;
// lockResets = 15;
// lockDelay = 30;
// gravityBucket = 1.0;
// floored = false;
// coords = Coords(3, 21) + spawnPositionFixes[current.index];
// break;
// }
// break;
// case KeyType.chat:
// // TODO: Handle this case.
// case KeyType.exit:
// // TODO: Handle this case.
// case KeyType.retry:
// // TODO: Handle this case.
// default:
// developer.log(nextEvent.data.key.name);
// }
// if (nextEvent.data.key == KeyType.rotateCW && nextEvent.data.key == KeyType.rotateCW && nextEvent.data.key == KeyType.rotateCW){
// spinWasLastMove = true;
// }else{
// spinWasLastMove = false;
// }
// break;
// case EventType.keyup:
// nextEvent as EventKeyPress;
// double subframesDiff = nextEvent.data.subframe - subframesWent;
// subframesWent += subframesDiff;
// handleMovement(subframesDiff);
// handleGravity(subframesDiff);
// switch (nextEvent.data.key){
// case KeyType.moveLeft:
// case KeyType.moveRight:
// handling!.movementKeyReleased(nextEvent.data.key == KeyType.moveLeft, nextEvent.data.key == KeyType.moveRight, nextEvent.data.subframe);
// break;
// case KeyType.softDrop:
// handling?.sdfActive = false;
// break;
// case KeyType.rotateCCW:
// // TODO: Handle this case.
// case KeyType.rotateCW:
// // TODO: Handle this case.
// case KeyType.rotate180:
// // TODO: Handle this case.
// case KeyType.hardDrop:
// // TODO: Handle this case.
// case KeyType.hold:
// // TODO: Handle this case.
// case KeyType.chat:
// // TODO: Handle this case.
// case KeyType.exit:
// // TODO: Handle this case.
// case KeyType.retry:
// // TODO: Handle this case.
// }
// activeKeypresses.remove(nextEvent.data.key);
// break;
// case EventType.end:
// currentFrame = replay.roundLengths[0]+1;
// break;
// case EventType.ige:
// nextEvent as EventIGE;
// switch (nextEvent.data.data.type){
// case "interaction":
// garbageQueue.add(IncomingGarbage((nextEvent.data.data as IGEdataInteraction).data));
// case "interaction_confirm":
// GarbageData data = (nextEvent.data.data as IGEdataInteraction).data;
// if(data.type != "targeted") garbageQueue.firstWhere((element) => element.data.iid == data.iid).confirm(currentFrame, settings!.garbageSpeed!);
// case "allow_targeting":
// case "target":
// default:
// developer.log("Unknown IGE type: ${nextEvent.data.type}", level: 900);
// break;
// }
// // garbage speed counts from interaction comfirm
// break;
// default:
// developer.log("Event wasn't processed: ${nextEvent}", level: 900);
// }
// try{
// nextEvent = events.removeAt(0);
// }
// catch (e){
// developer.log(e.toString());
// }
// }
// //developer.log("Next is: $nextEvent");
// }
// }
// exit(0);
//}
// }

View File

@ -897,11 +897,11 @@ class TetraLeagueAlphaRecord{
TetraLeagueAlphaRecord({required this.replayId, required this.ownId, required this.timestamp, required this.endContext, required this.replayAvalable});
TetraLeagueAlphaRecord.fromJson(Map<String, dynamic> json) {
ownId = json['_id'];
endContext = [EndContextMulti.fromJson(json['endcontext'][0]), EndContextMulti.fromJson(json['endcontext'][1])];
replayId = json['replayid'];
ownId = json['_id']??replayId;
timestamp = DateTime.parse(json['ts']);
replayAvalable = true;
replayAvalable = ownId != replayId;
}
Map<String, dynamic> toJson() {
@ -973,19 +973,19 @@ class EndContextMulti {
EndContextMulti.fromJson(Map<String, dynamic> json) {
userId = json['id'] ?? json['user']['_id'];
username = json['username'] ?? json['user']['username'];
handling = Handling.fromJson(json['handling']);
handling = json['handling'] != null ? Handling.fromJson(json['handling']) : Handling(arr: -1, das: -1, sdf: -1, dcd: 0, cancel: true, safeLock: true);
success = json['success'];
inputs = json['inputs'];
piecesPlaced = json['piecesplaced'];
inputs = json['inputs'] ?? -1;
piecesPlaced = json['piecesplaced'] ?? -1;
naturalOrder = json['naturalorder'];
wins = json['wins'];
points = json['points']['primary'];
secondary = json['points']['secondary'].toDouble();
tertiary = json['points']['tertiary'].toDouble();
secondaryTracking = json['points']['secondaryAvgTracking'].map((e) => e.toDouble()).toList();
tertiaryTracking = json['points']['tertiaryAvgTracking'].map((e) => e.toDouble()).toList();
secondaryTracking = json['points']['secondaryAvgTracking'] != null ? json['points']['secondaryAvgTracking'].map((e) => e.toDouble()).toList() : [];
tertiaryTracking = json['points']['tertiaryAvgTracking'] != null ? json['points']['tertiaryAvgTracking'].map((e) => e.toDouble()).toList() : [];
extra = json['points']['extra']['vs'].toDouble();
extraTracking = json['points']['extraAvgTracking']['aggregatestats___vsscore'].map((e) => e.toDouble()).toList();
extraTracking = json['points']['extraAvgTracking'] != null ? json['points']['extraAvgTracking']['aggregatestats___vsscore'].map((e) => e.toDouble()).toList() : [];
nerdStats = NerdStats(secondary, tertiary, extra);
nerdStatsTracking = [for (int i = 0; i < secondaryTracking.length; i++) NerdStats(secondaryTracking[i], tertiaryTracking[i], extraTracking[i])];
estTr = EstTr(secondary, tertiary, extra, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);

View File

@ -16,14 +16,14 @@ int biggestSpikeFromReplay(events){
spikeCounter = event['data']['data']['data']['amt'];
biggestSpike = spikeCounter;
}else{
if (event['data']['data']['frame'] - previousIGEeventFrame < 60){
if ((event['data']['data']['frame']??event['data']['frame']) - previousIGEeventFrame < 60){
spikeCounter = spikeCounter + event['data']['data']['data']['amt'] as int;
}else{
spikeCounter = event['data']['data']['data']['amt'];
}
biggestSpike = max(biggestSpike, spikeCounter);
}
previousIGEeventFrame = event['data']['data']['frame'];
previousIGEeventFrame = event['data']['data']['frame']??event['data']['frame'];
}
}
return biggestSpike;
@ -216,8 +216,8 @@ class ReplayData{
List<double> KPSmultipliedByWeights = [0, 0];
totalStats = [ReplayStats.createEmpty(), ReplayStats.createEmpty()];
for(var round in json['data']) {
int firstInEndContext = round['replays'][0]["events"].last['data']['export']['options']['gameid'].startsWith(endcontext[0].userId) ? 0 : 1;
int secondInEndContext = round['replays'][1]["events"].last['data']['export']['options']['gameid'].startsWith(endcontext[1].userId) ? 1 : 0;
int firstInEndContext = round['replays'][0]["events"].last['data']['export']['options']['username'].startsWith(endcontext[0].username) ? 0 : 1;
int secondInEndContext = round['replays'][1]["events"].last['data']['export']['options']['username'].startsWith(endcontext[1].username) ? 1 : 0;
int roundLength = max(round['replays'][0]['frames'], round['replays'][1]['frames']);
roundLengths.add(roundLength);
totalLength = totalLength + max(round['replays'][0]['frames'], round['replays'][1]['frames']);
@ -228,7 +228,7 @@ class ReplayData{
VSmultipliedByWeights[0] += endcontext[0].extraTracking[roundID]*roundLength;
VSmultipliedByWeights[1] += endcontext[1].extraTracking[roundID]*roundLength;
int winner = round['board'].indexWhere((element) => element['success'] == true);
roundWinners.add([round['board'][winner]['id'], round['board'][winner]['username']]);
roundWinners.add([round['board'][winner]['id']??round['board'][winner]['user']['_id'], round['board'][winner]['username']??round['board'][winner]['user']['username']]);
ReplayStats playerOne = ReplayStats.fromJson(round['replays'][firstInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][secondInEndContext]['events']), round['replays'][firstInEndContext]['frames']); // (events contain recived attacks)
ReplayStats playerTwo = ReplayStats.fromJson(round['replays'][secondInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][firstInEndContext]['events']), round['replays'][secondInEndContext]['frames']);
SPPmultipliedByWeights[0] += playerOne.spp*roundLength;
@ -283,7 +283,7 @@ class ReplayData{
// events.add(EventFull(id, frame, type, DataFull.fromJson(event["data"])));
// break;
// case EventType.targets:
// // TODO
// events.add(EventTargets(id, frame, type, Targets.fromJson(event["data"])));
// break;
// case EventType.keydown:
// events.add(EventKeyPress(id, frame, type,
@ -302,15 +302,24 @@ class ReplayData{
// ));
// break;
// case EventType.end:
// // TODO: Handle this case.
// events.add(EventEnd(id, frame, type, EndData(event['data']['reason'], DataFull.fromJson(event['data']['export']))));
// break;
// case EventType.ige:
// // TODO: Handle this case.
// events.add(EventIGE(id, frame, type, IGE(
// event['data']['id'],
// event['data']['frame'],
// event['data']['type'],
// event['data']['data']
// ))
// );
// break;
// case EventType.exit:
// // TODO: Handle this case.
// events.add(Event(id, frame, type));
// break;
// }
// id++;
// }
// return [];
// return events;
// }
// enum EventType
@ -356,10 +365,12 @@ class ReplayData{
// class Keypress{
// KeyType key;
// double subframe;
// late double subframe;
// bool released;
// Keypress(this.key, this.subframe, this.released);
// Keypress(this.key, num sframe, this.released){
// subframe = sframe.toDouble();
// }
// }
// class EventKeyPress extends Event{
@ -368,13 +379,144 @@ class ReplayData{
// EventKeyPress(super.id, super.frame, super.type, this.data);
// }
// class Targets{
// String? id;
// int? frame;
// String? type;
// List<dynamic>? data;
// Targets(this.id, this.frame, this.type, this.data);
// Targets.fromJson(Map<String, dynamic> json){
// id = json["id"];
// frame = json["frame"];
// type = json["type"];
// data = json["data"];
// }
// }
// class EventTargets extends Event{
// Targets data;
// EventTargets(super.id, super.frame, super.type, this.data);
// }
// class IGEdata{
// late String type;
// late String? gameid;
// late int? frame;
// IGEdata.fromJson(Map<String, dynamic> d){
// type = d['type'];
// gameid = d['gameid'];
// frame = d['frame'];
// }
// }
// enum GarbageStatus
// {
// sleeping,
// caution,
// spawn,
// danger
// }
// class GarbageData{
// int? id;
// int? iid;
// int? ackiid;
// String? username;
// late String type;
// bool? active;
// GarbageStatus? status;
// int? delay;
// bool? queued;
// int? amt;
// int? x;
// int? y;
// int? size;
// int? column;
// int? cid;
// bool? firstcycle;
// int? gid;
// bool? value;
// GarbageData.fromJson(Map<String, dynamic> data){
// id = data['id'];
// iid = data['iid'];
// ackiid = data['ackiid'];
// username = data['username'];
// type = data['type'];
// active = data['active'];
// status = data['status'] != null ? GarbageStatus.values[data['status']] : null;
// delay = data['delay'];
// queued = data['queued'];
// amt = data['amt'];
// x = data['x'];
// y = data['y'];
// size = data['size'];
// column = data['column'];
// cid = data['cid'];
// firstcycle = data['firstcycle'];
// gid = data['gid'];
// value = data['value'];
// }
// }
// class IGEdataTarget extends IGEdata{
// late List<dynamic> targets;
// GarbageData? data;
// //compatibility for v15 targets event
// String? sender_id;
// IGEdataTarget.fromJson(Map<String, dynamic> d) : super.fromJson(d){
// targets = d['targets'];
// data = d['data'] != null ? GarbageData.fromJson(d['data']) : null;
// }
// }
// class IGEdataAllowTargeting extends IGEdata{
// late bool value;
// IGEdataAllowTargeting.fromJson(Map<String, dynamic> d) : super.fromJson(d){
// value = d['value'];
// frame = d['frame'];
// }
// }
// class IGEdataInteraction extends IGEdata{
// late GarbageData data;
// String? sender;
// String? senderID;
// int? sentFrame;
// bool confirm = false;
// IGEdataInteraction.fromJson(Map<String, dynamic> d) : super.fromJson(d){
// //data = Targeted(d['data']['targeted'], d['data']['value']);
// data = GarbageData.fromJson(d['data']);
// sender = d['sender'];
// senderID = d['sender_id'];
// confirm = type == "interaction_confirm";
// }
// }
// class IGE{
// int id;
// int frame;
// String type;
// int amount;
// late IGEdata data;
// IGE(this.id, this.frame, this.type, this.amount);
// IGE(this.id, this.frame, this.type, Map<String, dynamic> d){
// data = switch (d["type"] as String) {
// "interaction" => IGEdataInteraction.fromJson(d),
// "interaction_confirm" => IGEdataInteraction.fromJson(d),
// "target" => IGEdataTarget.fromJson(d),
// "allow_targeting" => IGEdataAllowTargeting.fromJson(d),
// _ => IGEdata.fromJson(d),
// };
// }
// }
// class EventIGE extends Event{
@ -383,6 +525,19 @@ class ReplayData{
// EventIGE(super.id, super.frame, super.type, this.data);
// }
// class EndData {
// String reason;
// DataFull export;
// EndData(this.reason, this.export);
// }
// class EventEnd extends Event{
// EndData data;
// EventEnd(super.id, super.frame, super.type, this.data);
// }
// class Hold
// {
// String? piece;
@ -434,8 +589,9 @@ class ReplayData{
// stock = json["stock"];
// gMargin = json["gmargin"];
// gIncrease = json["gincrease"];
// garbageMultiplier = json["garbagemultiplier"];
// garbageCapIncrease = json["garbagecapincrease"];
// garbageMultiplier = json["garbagemultiplier"].toDouble();
// garbageCap = json["garbagecap"];
// garbageCapIncrease = json["garbagecapincrease"].toDouble();
// garbageCapMax = json["garbagecapmax"];
// garbageHoleSize = json["garbageholesize"];
// garbageBlocking = json["garbageblocking"];
@ -462,7 +618,7 @@ class ReplayData{
// class DataFullStats
// {
// double? seed;
// int? seed;
// int? lines;
// int? levelLines;
// int? levelLinesNeeded;
@ -511,8 +667,8 @@ class ReplayData{
// class DataFullGame
// {
// List<List<String?>>? board;
// List<String>? bag;
// List<dynamic>? board;
// List<dynamic>? bag;
// double? g;
// bool? playing;
// Hold? hold;
@ -595,54 +751,74 @@ class ReplayData{
// empty
// }
// class Coords{
// int x;
// int y;
// Coords(this.x, this.y);
// @override
// String toString() {
// return "($x; $y)";
// }
// Coords operator+(Coords other){
// return Coords(x+other.x, y+other.y);
// }
// Coords operator-(Coords other){
// return Coords(x-other.x, y-other.y);
// }
// }
// List<Tetromino> tetrominoes = [Tetromino.Z, Tetromino.L, Tetromino.O, Tetromino.S, Tetromino.I, Tetromino.J, Tetromino.T];
// List<List<List<Vector2>>> shapes = [
// List<List<List<Coords>>> shapes = [
// [ // Z
// [Vector2(0, 0), Vector2(1, 0), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(2, 0), Vector2(1, 1), Vector2(2, 1), Vector2(1, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(1, 2), Vector2(2, 2)],
// [Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), Vector2(0, 2)]
// [Coords(0, 2), Coords(1, 2), Coords(1, 1), Coords(2, 1)],
// [Coords(2, 2), Coords(2, 1), Coords(1, 1), Coords(1, 0)],
// [Coords(2, 0), Coords(1, 0), Coords(1, 1), Coords(0, 1)],
// [Coords(0, 0), Coords(0, 1), Coords(1, 1), Coords(1, 2)]
// ],
// [ // L
// [Vector2(2, 0), Vector2(0, 1), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(1, 2), Vector2(2, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(0, 2)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(1, 1), Vector2(1, 2)]
// [Coords(2, 2), Coords(2, 1), Coords(1, 1), Coords(0, 1)],
// [Coords(2, 0), Coords(1, 0), Coords(1, 1), Coords(1, 2)],
// [Coords(0, 0), Coords(0, 1), Coords(1, 1), Coords(2, 1)],
// [Coords(0, 2), Coords(1, 2), Coords(1, 1), Coords(1, 0)]
// ],
// [ // O
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)]
// [Coords(0, 0), Coords(1, 0), Coords(0, 1), Coords(1, 1)],
// [Coords(0, 0), Coords(1, 0), Coords(0, 1), Coords(1, 1)],
// [Coords(0, 0), Coords(1, 0), Coords(0, 1), Coords(1, 1)],
// [Coords(0, 0), Coords(1, 0), Coords(0, 1), Coords(1, 1)]
// ],
// [ // S
// [Vector2(1, 0), Vector2(2, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(2, 1), Vector2(2, 2)],
// [Vector2(1, 1), Vector2(2, 1), Vector2(0, 2), Vector2(1, 2)],
// [Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 2)]
// [Coords(2, 2), Coords(1, 2), Coords(1, 1), Coords(0, 1)],
// [Coords(2, 0), Coords(2, 1), Coords(1, 1), Coords(1, 2)],
// [Coords(0, 0), Coords(1, 0), Coords(1, 1), Coords(2, 1)],
// [Coords(0, 2), Coords(0, 1), Coords(1, 1), Coords(1, 0)]
// ],
// [ // I
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(3, 1)],
// [Vector2(2, 0), Vector2(2, 1), Vector2(2, 2), Vector2(2, 3)],
// [Vector2(0, 2), Vector2(1, 2), Vector2(2, 2), Vector2(3, 2)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(1, 2), Vector2(1, 3)]
// [Coords(0, 2), Coords(1, 2), Coords(2, 2), Coords(3, 2)],
// [Coords(2, 3), Coords(2, 2), Coords(2, 1), Coords(2, 0)],
// [Coords(3, 1), Coords(2, 1), Coords(1, 1), Coords(0, 1)],
// [Coords(1, 0), Coords(1, 1), Coords(1, 2), Coords(1, 3)]
// ],
// [ // J
// [Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(1, 0), Vector2(2, 0), Vector2(1, 1), Vector2(1, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(2, 2)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(0, 2), Vector2(1, 2)]
// [Coords(0, 2), Coords(0, 1), Coords(1, 1), Coords(2, 1)],
// [Coords(2, 2), Coords(1, 2), Coords(1, 1), Coords(1, 0)],
// [Coords(2, 0), Coords(2, 1), Coords(1, 1), Coords(0, 1)],
// [Coords(0, 0), Coords(1, 0), Coords(1, 1), Coords(1, 2)]
// ],
// [ // T
// [Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(2, 1), Vector2(1, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(1, 2)],
// [Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 2)]
// [Coords(1, 2), Coords(0, 1), Coords(1, 1), Coords(2, 1)],
// [Coords(2, 1), Coords(1, 2), Coords(1, 1), Coords(1, 0)],
// [Coords(1, 0), Coords(2, 1), Coords(1, 1), Coords(0, 1)],
// [Coords(0, 1), Coords(1, 0), Coords(1, 1), Coords(1, 2)]
// ]
// ];
// List<Vector2> spawnPositionFixes = [Vector2(1, 1), Vector2(1, 1), Vector2(0, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1)];
// List<Coords> spawnPositionFixes = [Coords(0, 0), Coords(0, 0), Coords(1, 1), Coords(0, 0), Coords(0, -1), Coords(0, 0), Coords(0, 0)];
// const Map<String, double> garbage = {
// const Map<String, int> garbage = {
// "single": 0,
// "double": 1,
// "triple": 2,
@ -665,3 +841,61 @@ class ReplayData{
// int comboMinifier = 1;
// double comboMinifierLog = 1.25;
// List<int> comboTable = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5];
// class KicksetBase {
// List<Coords>? additionalOffsets;
// late List<Coords> additionalOffsetEmpty;
// List<int>? spawnRotation;
// late List<List<List<Coords>>> kickTable; //kickTable[initRot][rotDirection-1][kick]
// late List<List<List<Coords>>> kickTableI;
// }
// class SRSPlus extends KicksetBase{
// SRSPlus(){
// kickTable = [
// [
// [Coords( 0, 0), Coords( 1, 0), Coords( 1, 1), Coords( 0,-2), Coords( 1,-2)], // 0 -> 270
// [Coords( 0, 0), Coords(-1, 0), Coords(-1, 1), Coords( 0,-2), Coords(-1,-2)], // 0 -> 90
// [Coords( 0, 0), Coords( 0, 1), Coords( 1, 1), Coords(-1, 1), Coords( 1, 0), Coords(-1, 0)], // 0 -> 180
// ],
// [
// [Coords( 0, 0), Coords( 1, 0), Coords( 1,-1), Coords( 0, 2), Coords( 1, 2)], // 90 -> 0
// [Coords( 0, 0), Coords( 1, 0), Coords( 1,-1), Coords( 0, 2), Coords( 1, 2)], // 90 -> 180
// [Coords( 0, 0), Coords( 1, 0), Coords( 1, 2), Coords( 1, 1), Coords( 0, 2), Coords( 0, 1)], // 90 -> 270
// ],
// [
// [Coords( 0, 0), Coords(-1, 0), Coords(-1, 1), Coords( 0,-2), Coords(-1,-2)], // 180 -> 90
// [Coords( 0, 0), Coords( 1, 0), Coords( 1, 1), Coords( 0,-2), Coords( 1,-2)], // 180 -> 270
// [Coords( 0, 0), Coords( 0,-1), Coords(-1,-1), Coords( 1,-1), Coords(-1, 0), Coords( 1, 0)], // 180 -> 0
// ],
// [
// [Coords( 0, 0), Coords(-1, 0), Coords(-1,-1), Coords( 0, 2), Coords(-1, 2)], // 270 -> 180
// [Coords( 0, 0), Coords(-1, 0), Coords(-1,-1), Coords( 0, 2), Coords(-1, 2)], // 270 -> 0
// [Coords( 0, 0), Coords(-1, 0), Coords(-1, 2), Coords(-1, 1), Coords( 0, 2), Coords( 0, 1)], // 270 -> 90
// ]
// ];
// kickTableI = [
// [
// [Coords( 0, 0), Coords(-1, 0), Coords( 2, 0), Coords( 2,-1), Coords(-1, 2)], // 0 -> 270
// [Coords( 0, 0), Coords( 1, 0), Coords( 2, 0), Coords(-1,-1), Coords( 1, 2)], // 0 -> 90
// [Coords( 0, 0), Coords( 0, 1)], // 0 -> 180
// ],
// [
// [Coords( 0, 0), Coords(-1, 0), Coords( 2, 0), Coords(-1,-2), Coords( 2,-1)], // 90 -> 0
// [Coords( 0, 0), Coords(-1, 0), Coords( 2, 0), Coords(-1, 2), Coords( 2, 1)], // 90 -> 180
// [Coords( 0, 0), Coords( 1, 0)], // 90 -> 270
// ],
// [
// [Coords( 0, 0), Coords(-2, 0), Coords( 1, 0), Coords(-2, 1), Coords( 1,-2)], // 180 -> 90
// [Coords( 0, 0), Coords(-2, 0), Coords(-1, 0), Coords( 2, 1), Coords(-1,-2)], // 180 -> 270
// [Coords( 0, 0), Coords( 0,-1)], // 180 -> 0
// ],
// [
// [Coords( 0, 0), Coords( 1, 0), Coords(-2, 0), Coords( 1, 2), Coords(-2,-1)], // 270 -> 180
// [Coords( 0, 0), Coords( 1, 0), Coords(-2, 0), Coords( 1,-2), Coords(-2, 1)], // 270 -> 0
// [Coords( 0, 0), Coords(-1, 0)], // 270 -> 90
// ]
// ];
// additionalOffsetEmpty = [Coords( 0, 0),Coords( 0, 0),Coords( 0, 0),Coords( 0, 0)];
// }
// }

View File

@ -6,7 +6,7 @@
/// Locales: 2
/// Strings: 1182 (591 per locale)
///
/// Built on 2024-06-16 at 21:03 UTC
/// Built on 2024-07-10 at 15:23 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@ -321,7 +321,7 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get fromBeginning => 'From beginning';
String get calc => 'Calc';
String get calcViewNoValues => 'Enter values to calculate the stats';
String get rankAveragesViewTitle => 'Ranks cutoff and average stats';
String get rankAveragesViewTitle => 'Ranks cutoffs';
String get sprintAndBlitsViewTitle => '40 lines and Blitz averages';
String sprintAndBlitsRelevance({required Object date}) => 'Relevance: ${date}';
String get rank => 'Rank';
@ -1016,7 +1016,7 @@ class _StringsRu implements Translations {
@override String get fromBeginning => 'С начала';
@override String get calc => 'Считать';
@override String get calcViewNoValues => 'Введите значения, чтобы посчитать статистику';
@override String get rankAveragesViewTitle => 'Требования рангов и средние значения';
@override String get rankAveragesViewTitle => 'Требования рангов';
@override String get sprintAndBlitsViewTitle => 'Средние результаты 40 линий и блица';
@override String sprintAndBlitsRelevance({required Object date}) => 'Актуальность: ${date}';
@override String get rank => 'Ранг';
@ -1703,7 +1703,7 @@ extension on Translations {
case 'fromBeginning': return 'From beginning';
case 'calc': return 'Calc';
case 'calcViewNoValues': return 'Enter values to calculate the stats';
case 'rankAveragesViewTitle': return 'Ranks cutoff and average stats';
case 'rankAveragesViewTitle': return 'Ranks cutoffs';
case 'sprintAndBlitsViewTitle': return '40 lines and Blitz averages';
case 'sprintAndBlitsRelevance': return ({required Object date}) => 'Relevance: ${date}';
case 'rank': return 'Rank';
@ -2314,7 +2314,7 @@ extension on _StringsRu {
case 'fromBeginning': return 'С начала';
case 'calc': return 'Считать';
case 'calcViewNoValues': return 'Введите значения, чтобы посчитать статистику';
case 'rankAveragesViewTitle': return 'Требования рангов и средние значения';
case 'rankAveragesViewTitle': return 'Требования рангов';
case 'sprintAndBlitsViewTitle': return 'Средние результаты 40 линий и блица';
case 'sprintAndBlitsRelevance': return ({required Object date}) => 'Актуальность: ${date}';
case 'rank': return 'Ранг';

View File

@ -379,7 +379,6 @@ class TetrioService extends DB {
TopTr result = TopTr(id, null);
developer.log("fetchTopTR: Probably, player doesn't have top TR", name: "services/tetrio_crud", error: response.statusCode);
_cache.store(result, DateTime.now().millisecondsSinceEpoch + 300000);
//_topTRcache[(DateTime.now().millisecondsSinceEpoch + 300000).toString()] = {id: null};
return result;
// if not 200 or 404 - throw a unique for each code exception
case 403:
@ -392,7 +391,10 @@ class TetrioService extends DB {
case 502:
case 503:
case 504:
throw P1nkl0bst3rInternalProblem();
TopTr result = TopTr(id, null);
developer.log("fetchTopTR: API returned ${response.statusCode}", name: "services/tetrio_crud", error: response.statusCode);
//_cache.store(result, DateTime.now().millisecondsSinceEpoch + 300000);
return result;
default:
developer.log("fetchTopTR: Failed to fetch top TR", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
@ -445,7 +447,8 @@ class TetrioService extends DB {
case 502:
case 503:
case 504:
throw P1nkl0bst3rInternalProblem();
developer.log("fetchCutoffs: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode);
return null;
default:
developer.log("fetchCutoffs: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
@ -603,7 +606,7 @@ class TetrioService extends DB {
if (kIsWeb) {
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLMatches", "user": userID});
} else {
url = Uri.https('api.p1nkl0bst3r.xyz', 'tlmatches/$userID');
url = Uri.https('api.p1nkl0bst3r.xyz', 'tlmatches/$userID', {"before": "0", "count": "9000"});
}
try{
@ -611,62 +614,9 @@ class TetrioService extends DB {
switch (response.statusCode) {
case 200:
// that one api returns csv instead of json
List<List<dynamic>> csv = const CsvToListConverter().convert(response.body)..removeAt(0);
List<TetraLeagueAlphaRecord> matches = [];
// parsing data into TetraLeagueAlphaRecord objects
for (var entry in csv){
TetraLeagueAlphaRecord match = TetraLeagueAlphaRecord(
replayId: entry[0].toString(),
ownId: entry[0].toString(), // i gonna disting p1nkl0bst3r entries with it
timestamp: DateTime.parse(entry[1]),
endContext: [
EndContextMulti(
userId: entry[2].toString(),
username: entry[3].toString(),
naturalOrder: 0,
inputs: -1,
piecesPlaced: -1,
handling: Handling(arr: -1, das: -1, sdf: -1, dcd: 0, cancel: true, safeLock: true),
points: entry[4],
wins: entry[4],
secondary: entry[6],
secondaryTracking: [],
tertiary: entry[5],
tertiaryTracking: [],
extra: entry[7],
extraTracking: [],
success: true
),
EndContextMulti(
userId: entry[8].toString(),
username: entry[9].toString(),
naturalOrder: 1,
inputs: -1,
piecesPlaced: -1,
handling: Handling(arr: -1, das: -1, sdf: -1, dcd: 0, cancel: true, safeLock: true),
points: entry[10],
wins: entry[10],
secondary: entry[12],
secondaryTracking: [],
tertiary: entry[11],
tertiaryTracking: [],
extra: entry[13],
extraTracking: [],
success: false
)
],
replayAvalable: false
);
matches.add(match);
}
// trying to dump it to local DB
TetraLeagueAlphaStream fakeStream = TetraLeagueAlphaStream(userId: userID, records: matches);
saveTLMatchesFromStream(fakeStream);
return matches;
TetraLeagueAlphaStream stream = TetraLeagueAlphaStream.fromJson(jsonDecode(response.body)['data']['records'], userID);
saveTLMatchesFromStream(stream);
return stream.records;
case 404:
developer.log("fetchAndSaveOldTLmatches: Probably, history doesn't exist", name: "services/tetrio_crud", error: response.statusCode);
throw TetrioHistoryNotExist();

View File

@ -306,7 +306,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
});
}
return [me, records, states, tlMatches, compareWith, isTracking, news, topTR, recent, sprint, blitz];
return [me, records, states, tlMatches, compareWith, isTracking, news, topTR, recent, sprint, blitz, tlMatches.elementAtOrNull(0)?.timestamp];
}
/// Triggers widgets rebuild
@ -459,6 +459,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
userID: snapshot.data![0].userId,
states: snapshot.data![2],
topTR: snapshot.data![7]?.tr,
lastMatchPlayed: snapshot.data![11],
bot: snapshot.data![0].role == "bot",
guest: snapshot.data![0].role == "anon",
thatRankCutoff: thatRankCutoff,
@ -1084,7 +1085,7 @@ class _TwoRecordsThingy extends StatelessWidget {
if (sprint != null) FinesseThingy(sprint?.endContext.finesse, sprint?.endContext.finessePercentage),
if (sprint != null) LineclearsThingy(sprint!.endContext.clears, sprint!.endContext.lines, sprint!.endContext.holds, sprint!.endContext.tSpins),
if (sprint != null) Text("${sprint!.endContext.inputs} KP • ${f2.format(sprint!.endContext.kps)} KPS"),
Wrap(
if (sprint != null) Wrap(
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 20,
@ -1170,7 +1171,7 @@ class _TwoRecordsThingy extends StatelessWidget {
if (blitz != null) FinesseThingy(blitz?.endContext.finesse, blitz?.endContext.finessePercentage),
if (blitz != null) LineclearsThingy(blitz!.endContext.clears, blitz!.endContext.lines, blitz!.endContext.holds, blitz!.endContext.tSpins),
if (blitz != null) Text("${blitz!.endContext.piecesPlaced} P • ${blitz!.endContext.inputs} KP • ${f2.format(blitz!.endContext.kpp)} KPP • ${f2.format(blitz!.endContext.kps)} KPS"),
Wrap(
if (blitz != null) Wrap(
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 20,
@ -1184,7 +1185,7 @@ class _TwoRecordsThingy extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 1; i < sprintStream.records.length; i++) ListTile(
for (int i = 1; i < blitzStream.records.length; i++) ListTile(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: blitzStream.records[i]))),
leading: Text("#${i+1}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9) ),
title: Text("${NumberFormat.decimalPattern().format(blitzStream.records[i].endContext.score)} points",

View File

@ -49,14 +49,13 @@ class RanksAverages extends State<RankAveragesView> {
child: averages.isEmpty ? const Center(child: Text('Fetching...')) : 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(t.players(n: averages[keys[index]]?[1]["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",
style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey, fontSize: 13)),
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: const TextStyle(fontSize: 28, fontFamily: "Eurostile Round")),
onTap: (){
if (averages[keys[index]]?[1]["players"] > 0) {
Navigator.push(

View File

@ -288,6 +288,13 @@ class SettingsState extends State<SettingsView> {
subtitle: Text(t.aboutAppText(appName: packageInfo.appName, packageName: packageInfo.packageName, version: packageInfo.version, buildNumber: packageInfo.buildNumber)),
trailing: const Icon(Icons.arrow_right)
),
// Wrap(
// alignment: WrapAlignment.center,
// spacing: 8,
// children: [
// TextButton(child: Text("Donate to me"), onPressed: (){},),TextButton(child: Text("Donate to NOT me"), onPressed: (){},),TextButton(child: Text("Donate to someone else"), onPressed: (){},),
// ],
// ),
],
)),
);

View File

@ -58,7 +58,7 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
);
},
icon: const Icon(Icons.compress),
tooltip: t.averages,
tooltip: t.rankAveragesViewTitle,
),
],
),
@ -184,12 +184,12 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)
),
title: Text(allPlayers[index].username, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)),
subtitle: Text(_sortBy == Stats.tr ? "${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].nerdStats.app)} APP, ${f2.format(allPlayers[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(allPlayers[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
style: TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: bigScreen ? null : 12, color: _sortBy == Stats.tr ? Colors.grey : null)),
subtitle: (bigScreen || _sortBy != Stats.tr) ? Text(_sortBy == Stats.tr ? "${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].nerdStats.app)} APP, ${f2.format(allPlayers[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(allPlayers[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
style: TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: bigScreen ? null : 13, color: _sortBy == Stats.tr ? Colors.grey : null)) : null,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("${f2.format(allPlayers[index].rating)} TR", style: TextStyle(fontSize: bigScreen ? 28 : 22)),
Text("${f2.format(allPlayers[index].rating)} TR", style: const TextStyle(fontSize: 28)),
Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: bigScreen ? 48 : 36),
],
),

View File

@ -269,7 +269,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
label: "Sent", higherIsBetter: true),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.recived,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.recived,
label: "Recived", higherIsBetter: true),
label: "Received", higherIsBetter: true),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.attack,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.attack,
label: "Attack", higherIsBetter: true),

View File

@ -126,7 +126,7 @@ class SingleplayerRecord extends StatelessWidget {
LineclearsThingy(record!.endContext.clears, record!.endContext.lines, record!.endContext.holds, record!.endContext.tSpins),
if (record!.endContext.gameType == "40l") Text("${record!.endContext.inputs} KP • ${f2.format(record!.endContext.kps)} KPS"),
if (record!.endContext.gameType == "blitz") Text("${record!.endContext.piecesPlaced} P • ${record!.endContext.inputs} KP • ${f2.format(record!.endContext.kpp)} KPP • ${f2.format(record!.endContext.kps)} KPS"),
Wrap(
if (record != null) Wrap(
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 20,

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
@ -6,14 +8,16 @@ import 'package:tetra_stats/main.dart' show prefs;
import 'package:tetra_stats/utils/numers_formats.dart';
var fDiff = NumberFormat("+#,###.####;-#,###.####");
DateTime seasonEnd = DateTime.utc(2024, 07, 26);
class TLRatingThingy extends StatelessWidget{
final String userID;
final TetraLeagueAlpha tlData;
final TetraLeagueAlpha? oldTl;
final double? topTR;
final DateTime? lastMatchPlayed;
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR});
const TLRatingThingy({super.key, required this.userID, required this.tlData, this.oldTl, this.topTR, this.lastMatchPlayed});
@override
Widget build(BuildContext context) {
@ -23,6 +27,11 @@ class TLRatingThingy extends StatelessWidget{
List<String> formatedTR = f4.format(tlData.rating).split(decimalSeparator);
List<String> formatedGlicko = f4.format(tlData.glicko).split(decimalSeparator);
List<String> formatedPercentile = f4.format(tlData.percentile * 100).split(decimalSeparator);
DateTime now = DateTime.now();
bool beforeS1end = now.isBefore(seasonEnd);
int daysLeft = seasonEnd.difference(now).inDays;
print(max(0, 7 - (lastMatchPlayed != null ? now.difference(lastMatchPlayed!).inDays : 7)));
int safeRD = min(100, (100 + ((tlData.rd! >= 100 && tlData.decaying) ? 7 : max(0, 7 - (lastMatchPlayed != null ? now.difference(lastMatchPlayed!).inDays : 7))) - daysLeft).toInt());
return Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceAround,
@ -83,7 +92,8 @@ class TLRatingThingy extends StatelessWidget{
if (topTR != null) TextSpan(text: " (${f2.format(topTR)} TR)"),
TextSpan(text: "${prefs.getInt("ratingMode") == 1 ? "${f2.format(tlData.rating)} TR • RD: " : "Glicko: ${f2.format(tlData.glicko!)}±"}"),
TextSpan(text: f2.format(tlData.rd!), style: tlData.decaying ? TextStyle(color: tlData.rd! > 98 ? Colors.red : Colors.yellow) : null),
if (tlData.decaying) WidgetSpan(child: Icon(Icons.trending_up, color: tlData.rd! > 98 ? Colors.red : Colors.yellow,), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic)
if (tlData.decaying) WidgetSpan(child: Icon(Icons.trending_up, color: tlData.rd! > 98 ? Colors.red : Colors.yellow,), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
if (beforeS1end) tlData.rd! <= safeRD ? TextSpan(text: " (Safe)", style: TextStyle(color: Colors.greenAccent)) : TextSpan(text: " (> ${safeRD} RD !!!)", style: TextStyle(color: Colors.redAccent))
],
),
),

View File

@ -31,7 +31,8 @@ class TLThingy extends StatefulWidget {
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, this.thatRankCutoff, this.thatRankCutoffGlicko, this.nextRankCutoffGlicko, this.nextRankTarget, this.thatRankTarget});
final DateTime? lastMatchPlayed;
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, this.thatRankCutoff, this.thatRankCutoffGlicko, this.nextRankCutoffGlicko, this.nextRankTarget, this.thatRankTarget, this.lastMatchPlayed});
@override
State<TLThingy> createState() => _TLThingyState();
@ -57,8 +58,8 @@ class _TLThingyState extends State<TLThingy> {
Widget build(BuildContext context) {
final t = Translations.of(context);
String decimalSeparator = f2.symbols.DECIMAL_SEP;
List<String> estTRformated = f2.format(currentTl.estTr!.esttr).split(decimalSeparator);
List<String> estTRaccFormated = intFDiff.format(currentTl.esttracc!).split(".");
List<String> estTRformated = currentTl.estTr != null ? f2.format(currentTl.estTr!.esttr).split(decimalSeparator) : [];
List<String> estTRaccFormated = currentTl.esttracc != null ? intFDiff.format(currentTl.esttracc!).split(".") : [];
if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,));
return LayoutBuilder(builder: (context, constraints) {
bool bigScreen = constraints.maxWidth >= 768;
@ -92,7 +93,7 @@ class _TLThingyState extends State<TLThingy> {
});
},
),
if (currentTl.gamesPlayed >= 10) TLRatingThingy(userID: widget.userID, tlData: currentTl, oldTl: oldTl, topTR: widget.topTR),
if (currentTl.gamesPlayed > 9) TLRatingThingy(userID: widget.userID, tlData: currentTl, oldTl: oldTl, topTR: widget.topTR, lastMatchPlayed: widget.lastMatchPlayed),
if (currentTl.gamesPlayed > 9) TLProgress(
tlData: currentTl,
previousRankTRcutoff: widget.thatRankCutoff,

View File

@ -2,7 +2,7 @@ name: tetra_stats
description: Track your and other player stats in TETR.IO
publish_to: 'none'
version: 1.6.0+20
version: 1.6.1+21
environment:
sdk: '>=3.0.0'

View File

@ -186,7 +186,7 @@
"fromBeginning": "From beginning",
"calc": "Calc",
"calcViewNoValues": "Enter values to calculate the stats",
"rankAveragesViewTitle": "Ranks cutoff and average stats",
"rankAveragesViewTitle": "Ranks cutoffs",
"sprintAndBlitsViewTitle": "40 lines and Blitz averages",
"sprintAndBlitsRelevance": "Relevance: ${date}",
"rank": "Rank",

View File

@ -186,7 +186,7 @@
"fromBeginning": "С начала",
"calc": "Считать",
"calcViewNoValues": "Введите значения, чтобы посчитать статистику",
"rankAveragesViewTitle": "Требования рангов и средние значения",
"rankAveragesViewTitle": "Требования рангов",
"sprintAndBlitsViewTitle": "Средние результаты 40 линий и блица",
"sprintAndBlitsRelevance": "Актуальность: ${date}",
"rank": "Ранг",

View File

@ -80,6 +80,9 @@
font-weight: 500;
font-style: normal;
}
a{
color: aqua;
}
#preloader{
display: flex;
width: fit-content;
@ -111,6 +114,15 @@
padding-top: 8px;
animation: 1s cubic-bezier(.46,.03,.52,.96) infinite alternate breathing;
}
.error{
color: red;
}
#tip{
position: absolute;
bottom: 5%;
color: gray;
text-align: center;
}
@media (max-width: 502px){
#preloader{
flex-direction: column;
@ -130,10 +142,25 @@
<p id="progress">Loading...</p>
</div>
</div>
<p id="tip"></p>
<script>
window.addEventListener('load', function(ev) {
let progress = document.querySelector("#progress");
let preloader = document.querySelector("#preloader");
let tip = document.querySelector("#tip");
const tips = [
// Promoting Tetra Stats "native"
"Want a better perfomance?<br><a href=\"https://github.com/dan63047/TetraStats/releases\">Try out Tetra Stats \"Native\"</a>",
"Imagine a world, where Tetra Stats was written in JS",
"Welcome to fullscreen canvas",
// An actual tips
"You can interact with most objects that have an accent color",
"Like Sheetbot graphs? Go to three dots menu → Settings → Customization",
"Click and hold on line chart graph, then start dragging to zoom in"
];
tip.innerHTML = tips[Math.floor(Math.random() * tips.length)];
try{
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
@ -141,12 +168,18 @@
},
onEntrypointLoaded: async function(engineInitializer) {
console.log(serviceWorkerVersion);
let appRunner = await engineInitializer.initializeEngine();
progress.innerHTML = "Booting...";
let appRunner = await engineInitializer.initializeEngine();
await appRunner.runApp();
preloader.classList.add("hidden");
tip.classList.add("hidden");
}
});
} catch (e){
progress.classList.add("error");
document.getAnimations()[0].cancel();
progress.innerHTML = "fuck: "+e;
}
});
</script>
</body>