import 'dart:math'; import 'dart:typed_data'; import 'package:vector_math/vector_math_64.dart'; import 'tetrio.dart'; // I want to implement those fancy TWC stats // So, i'm going to read replay for things int biggestSpikeFromReplay(events){ int previousIGEeventFrame = -1; int spikeCounter = 0; int biggestSpike = 0; for (var event in events){ if (event["type"] == "ige" && event["data"]["data"]["type"] == "interaction"){ if (previousIGEeventFrame.isNegative){ spikeCounter = event['data']['data']['data']['amt']; biggestSpike = spikeCounter; }else{ if (event['data']['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']; } } return biggestSpike; } class Garbage{ // charsys where??? late int sent; late int recived; late int attack; late int cleared; Garbage({ required this.sent, required this.recived, required this.attack, required this.cleared }); Garbage.fromJson(Map json){ sent = json['sent']; recived = json['received']; attack = json['attack']; cleared = json['cleared']; } Garbage.toJson(){ // наху надо } Garbage operator + (Garbage other){ return Garbage(sent: sent + other.sent, recived: recived + other.recived, attack: attack + other.attack, cleared: cleared + other.cleared); } } class RawReplay{ String id; Uint8List asBytes; String asString; RawReplay(this.id, this.asBytes, this.asString); } class ReplayStats{ late int seed; late int linesCleared; late int piecesPlaced; late int inputs; late int holds; late int score; late int topCombo; late int topBtB; late int topSpike; late int tspins; late double roundLength; // in seconds late Clears clears; late Garbage garbage; late Finesse finesse; double get finessePercentage => finesse.perfectPieces / piecesPlaced; ReplayStats({ required this.seed, required this.linesCleared, required this.piecesPlaced, required this.inputs, required this.holds, required this.score, required this.topCombo, required this.topBtB, required this.topSpike, required this.tspins, required this.roundLength, required this.clears, required this.garbage, required this.finesse, }); ReplayStats.fromJson(Map json, int inputTopSpike, int framesLength){ seed = json['seed']; linesCleared = json['lines']; piecesPlaced = json['piecesplaced']; inputs = json['inputs']; holds = json['holds']; score = json['score']; topCombo = json['topcombo']; topBtB = json['topbtb']; topSpike = inputTopSpike; tspins = json['tspins']; roundLength = framesLength / 60; clears = Clears.fromJson(json['clears']); garbage = Garbage.fromJson(json['garbage']); finesse = Finesse.fromJson(json['finesse']); } double get kpp => inputs / piecesPlaced; double get kps => inputs / roundLength; double get spp => score / piecesPlaced; ReplayStats.createEmpty(){ seed = -1; linesCleared = 0; piecesPlaced = 0; inputs = 0; holds = 0; score = 0; topCombo = 0; topBtB = 0; topSpike = 0; tspins = 0; roundLength = 0.0; clears = Clears(singles: 0, doubles: 0, triples: 0, quads: 0, pentas: 0, allClears: 0, tSpinZeros: 0, tSpinSingles: 0, tSpinDoubles: 0, tSpinTriples: 0, tSpinPentas: 0, tSpinQuads: 0, tSpinMiniZeros: 0, tSpinMiniSingles: 0, tSpinMiniDoubles: 0); garbage = Garbage(sent: 0, recived: 0, attack: 0, cleared: 0); finesse = Finesse(combo: 0, faults: 0, perfectPieces: 0); } ReplayStats operator + (ReplayStats other){ return ReplayStats(seed: -1, linesCleared: linesCleared + other.linesCleared, piecesPlaced: piecesPlaced + other.piecesPlaced, inputs: inputs + other.inputs, holds: holds + other.holds, score: score + other.score, topCombo: max(topCombo, other.topCombo), topBtB: max(topBtB, other.topBtB), topSpike: max(topSpike, other.topSpike), tspins: tspins + other.tspins, roundLength: roundLength + other.roundLength, clears: clears + other.clears, garbage: garbage + other.garbage, finesse: finesse + other.finesse ); } } class AggregateStats{ double apm; double pps; double vs; late NerdStats nerdStats; late EstTr estTr; late Playstyle playstyle; double spp; double kpp; double kps; AggregateStats(this.apm, this.pps, this.vs, this.spp, this.kpp, this.kps){ 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); } } class ReplayData{ late String id; late Map rawJson; late List endcontext; late List timeWeightedStats; late List> stats; late List totalStats; late List> roundWinners; late List roundLengths; // in frames late int totalLength; // in frames ReplayData({ required this.id, required this.endcontext, required this.stats, required this.totalStats, required this.roundWinners, required this.roundLengths, required this.totalLength }){ rawJson = {}; } ReplayData.fromJson(Map json){ rawJson = json; id = json['_id']; endcontext = [EndContextMulti.fromJson(json['endcontext'][0]), EndContextMulti.fromJson(json['endcontext'][1])]; roundLengths = []; totalLength = 0; stats = []; roundWinners = []; int roundID = 0; List APMmultipliedByWeights = [0, 0]; List PPSmultipliedByWeights = [0, 0]; List VSmultipliedByWeights = [0, 0]; List SPPmultipliedByWeights = [0, 0]; List KPPmultipliedByWeights = [0, 0]; List 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 roundLength = max(round['replays'][0]['frames'], round['replays'][1]['frames']); roundLengths.add(roundLength); totalLength = totalLength + max(round['replays'][0]['frames'], round['replays'][1]['frames']); APMmultipliedByWeights[0] += endcontext[0].secondaryTracking[roundID]*roundLength; APMmultipliedByWeights[1] += endcontext[1].secondaryTracking[roundID]*roundLength; PPSmultipliedByWeights[0] += endcontext[0].tertiaryTracking[roundID]*roundLength; PPSmultipliedByWeights[1] += endcontext[1].tertiaryTracking[roundID]*roundLength; 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']]); 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; SPPmultipliedByWeights[1] += playerTwo.spp*roundLength; KPPmultipliedByWeights[0] += playerOne.kpp*roundLength; KPPmultipliedByWeights[1] += playerTwo.kpp*roundLength; KPSmultipliedByWeights[0] += playerOne.kps*roundLength; KPSmultipliedByWeights[1] += playerTwo.kps*roundLength; stats.add([playerOne, playerTwo]); totalStats[0] = totalStats[0] + playerOne; totalStats[1] = totalStats[1] + playerTwo; roundID ++; } timeWeightedStats = [ AggregateStats(APMmultipliedByWeights[0]/totalLength, PPSmultipliedByWeights[0]/totalLength, VSmultipliedByWeights[0]/totalLength, SPPmultipliedByWeights[0]/totalLength, KPPmultipliedByWeights[0]/totalLength, KPSmultipliedByWeights[0]/totalLength), AggregateStats(APMmultipliedByWeights[1]/totalLength, PPSmultipliedByWeights[1]/totalLength, VSmultipliedByWeights[1]/totalLength, SPPmultipliedByWeights[1]/totalLength, KPPmultipliedByWeights[1]/totalLength, KPSmultipliedByWeights[1]/totalLength) ]; } Map toJson(){ final Map data = {}; data['_id'] = id; data['endcontext'] = [endcontext[0].toJson(), endcontext[1].toJson()]; data['data'] = []; for(var round in rawJson['data']) { List eventsPlayerOne = round['replays'][0]['events']; List eventsPlayerTwo = round['replays'][1]['events']; eventsPlayerOne.removeWhere((v) => (v['type'] == 'ige' && v['data']['data']['type'] != 'interaction') || (v['type'] != 'end' && v['type'] != 'ige')); eventsPlayerTwo.removeWhere((v) => (v['type'] == 'ige' && v['data']['data']['type'] != 'interaction') || (v['type'] != 'end' && v['type'] != 'ige')); data['data'].add({'board': round['board'], 'replays': [ {'frames': round['replays'][0]['frames'], 'events': eventsPlayerOne}, {'frames': round['replays'][1]['frames'], 'events': eventsPlayerTwo} ]}); } return data; } } // can't belive i have to implement that difficult shit List readEventList(Map json){ List events = []; int id = 0; for (var event in json['data'][0]['replays'][0]['events']){ int frame = event["frame"]; EventType type = EventType.values.byName(event['type']); switch (type) { case EventType.start: events.add(Event(id, frame, type)); break; case EventType.full: events.add(EventFull(id, frame, type, DataFull.fromJson(event["data"]))); break; case EventType.targets: events.add(EventTargets(id, frame, type, Targets.fromJson(event["data"]))); break; case EventType.keydown: events.add(EventKeyPress(id, frame, type, Keypress( KeyType.values.byName(event['data']['key']), event['data']['subframe'], false) )); break; case EventType.keyup: events.add(EventKeyPress(id, frame, type, Keypress( KeyType.values.byName(event['data']['key']), event['data']['subframe'], true) )); break; case EventType.end: event.add(EventEnd(id, frame, type, EndData(event['data']['reason'], DataFull.fromJson(event['data']['export'])))); break; case EventType.ige: // TODO: Handle this case. case EventType.exit: events.add(Event(id, frame, type)); } id++; } return []; } enum EventType { start, end, full, keydown, keyup, targets, ige, exit } enum KeyType { moveLeft, moveRight, softDrop, rotateCCW, rotateCW, rotate180, hardDrop, hold, chat, exit, retry } class Event{ int id; int frame; EventType type; //dynamic data; Event(this.id, this.frame, this.type); @override String toString(){ return "E#$id f#$frame: $type"; } } class Keypress{ KeyType key; double subframe; bool released; Keypress(this.key, this.subframe, this.released); } class EventKeyPress extends Event{ Keypress data; EventKeyPress(super.id, super.frame, super.type, this.data); } class Targets{ String? id; int? frame; String? type; List? data; Targets(this.id, this.frame, this.type, this.data); Targets.fromJson(Map 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 IGE{ int id; int frame; String type; int amount; IGE(this.id, this.frame, this.type, this.amount); } class EventIGE extends Event{ IGE data; 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; bool locked; Hold(this.piece, this.locked); } class DataFullOptions{ int? version; bool? seedRandom; int? seed; double? g; int? stock; int? gMargin; double? gIncrease; double? garbageMultiplier; int? garbageMargin; double? garbageIncrease; int? garbageCap; double? garbageCapIncrease; int? garbageCapMax; int? garbageHoleSize; String? garbageBlocking; // TODO: enum bool? hasGarbage; int? locktime; int? garbageSpeed; int? forfeitTime; int? are; int? areLineclear; bool? infiniteMovement; int? lockresets; bool? allow180; bool? btbChaining; bool? allclears; bool? clutch; bool? noLockout; String? passthrough; int? boardwidth; int? boardheight; Handling? handling; int? boardbuffer; DataFullOptions.fromJson(Map json){ version = json["version"]; seedRandom = json["seed_random"]; seed = json["seed"]; g = json["g"]; stock = json["stock"]; gMargin = json["gmargin"]; gIncrease = json["gincrease"]; garbageMultiplier = json["garbagemultiplier"]; garbageCapIncrease = json["garbagecapincrease"]; garbageCapMax = json["garbagecapmax"]; garbageHoleSize = json["garbageholesize"]; garbageBlocking = json["garbageblocking"]; hasGarbage = json["hasgarbage"]; locktime = json["locktime"]; garbageSpeed = json["garbagespeed"]; forfeitTime = json["forfeit_time"]; are = json["are"]; areLineclear = json["lineclear_are"]; infiniteMovement = json["infinitemovement"]; lockresets = json["lockresets"]; allow180 = json["allow180"]; btbChaining = json["b2bchaining"]; allclears = json["allclears"]; clutch = json["clutch"]; noLockout = json["nolockout"]; passthrough = json["passthrough"]; boardwidth = json["boardwidth"]; boardheight = json["boardheight"]; handling = Handling.fromJson(json["handling"]); boardbuffer = json["boardbuffer"]; } } class DataFullStats { double? seed; int? lines; int? levelLines; int? levelLinesNeeded; int? inputs; int? holds; int? score; int? zenLevel; int? zenProgress; int? level; int? combo; int? currentComboPower; int? topCombo; int? btb; int? topbtb; int? tspins; int? piecesPlaced; Clears? clears; Garbage? garbage; int? kills; Finesse? finesse; DataFullStats.fromJson(Map json){ seed = json["seed"]; lines = json["lines"]; levelLines = json["level_lines"]; levelLinesNeeded = json["level_lines_needed"]; inputs = json["inputs"]; holds = json["holds"]; score = json["score"]; zenLevel = json["zenlevel"]; zenProgress = json["zenprogress"]; level = json["level"]; combo = json["combo"]; currentComboPower = json["currentcombopower"]; topCombo = json["topcombo"]; btb = json["btb"]; topbtb = json["topbtb"]; tspins = json["tspins"]; piecesPlaced = json["piecesplaced"]; clears = Clears.fromJson(json["clears"]); garbage = Garbage.fromJson(json["garbage"]); kills = json["kills"]; finesse = Finesse.fromJson(json["finesse"]); } } class DataFullGame { List>? board; List? bag; double? g; bool? playing; Hold? hold; String? piece; Handling? handling; DataFullGame.fromJson(Map json){ board = json["board"]; bag = json["bag"]; hold = Hold(json["hold"]["piece"], json["hold"]["locked"]); g = json["g"]; handling = Handling.fromJson(json["handling"]); } } class DataFull{ bool? successful; String? gameOverReason; int? fire; DataFullOptions? options; DataFullStats? stats; DataFullGame? game; DataFull.fromJson(Map json){ successful = json["successful"]; gameOverReason = json["gameoverreason"]; fire = json["fire"]; options = DataFullOptions.fromJson(json["options"]); stats = DataFullStats.fromJson(json["stats"]); game = DataFullGame.fromJson(json["game"]); } } class EventFull extends Event{ DataFull data; EventFull(super.id, super.frame, super.type, this.data); } class TetrioRNG{ late double _t; TetrioRNG(int seed){ _t = seed % 2147483647; if (_t <= 0) _t += 2147483646; } int next(){ _t = 16807 * _t % 2147483647; return _t.toInt(); } double nextFloat(){ return (next() - 1) / 2147483646; } List shuffleList(List array){ int length = array.length; if (length == 0) return []; for (; --length > 0;){ int swapIndex = ((nextFloat()) * (length + 1)).toInt(); Tetromino tmp = array[length]; array[length] = array[swapIndex]; array[swapIndex] = tmp; } return array; } } enum Tetromino{ Z, L, O, S, I, J, T, garbage, empty } List tetrominoes = [Tetromino.Z, Tetromino.L, Tetromino.O, Tetromino.S, Tetromino.I, Tetromino.J, Tetromino.T]; List>> 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)] ], [ // 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)] ], [ // 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)] ], [ // 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)] ], [ // 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)] ], [ // 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)] ], [ // 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)] ] ]; List spawnPositionFixes = [Vector2(1, 1), Vector2(1, 1), Vector2(0, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1)]; const Map garbage = { "single": 0, "double": 1, "triple": 2, "quad": 4, "penta": 5, "t-spin": 0, "t-spin single": 2, "t-spin double": 4, "t-spin triple": 6, "t-spin quad": 10, "t-spin penta": 12, "t-spin mini": 0, "t-spin mini single": 0, "t-spin mini double": 1, "allclear": 10 }; int btbBonus = 1; double btbLog = 0.8; double comboBonus = 0.25; int comboMinifier = 1; double comboMinifierLog = 1.25; List comboTable = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5];