TetraStats/lib/data_objects/tetrio_multiplayer_replay.dart

617 lines
17 KiB
Dart

import 'dart:convert';
import 'dart:math';
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<String, dynamic> 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 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<String, dynamic> 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 ReplayData{
late String id;
late Map<dynamic, dynamic> rawJson;
late List<EndContextMulti> endcontext;
late List<List<ReplayStats>> stats;
late List<ReplayStats> totalStats;
late List<List<String>> roundWinners;
late List<int> 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<String, dynamic> json){
rawJson = json;
id = json['_id'];
endcontext = [EndContextMulti.fromJson(json['endcontext'][0]), EndContextMulti.fromJson(json['endcontext'][1])];
roundLengths = [];
totalLength = 0;
stats = [];
roundWinners = [];
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;
roundLengths.add(max(round['replays'][0]['frames'], round['replays'][1]['frames']));
totalLength = totalLength + max(round['replays'][0]['frames'], round['replays'][1]['frames']);
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']);
stats.add([playerOne, playerTwo]);
totalStats[0] = totalStats[0] + playerOne;
totalStats[1] = totalStats[1] + playerTwo;
}
}
Map<String, dynamic> toJson(){
final Map<String, dynamic> data = <String, dynamic>{};
data['_id'] = id;
data['endcontext'] = [endcontext[0].toJson(), endcontext[1].toJson()];
data['data'] = [];
for(var round in rawJson['data']) {
List<dynamic> eventsPlayerOne = round['replays'][0]['events'];
List<dynamic> 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<Event> readEventList(Map<dynamic, dynamic> json){
List<Event> 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:
// TODO
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:
// TODO: Handle this case.
case EventType.ige:
// TODO: Handle this case.
case EventType.exit:
// TODO: Handle this case.
}
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 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 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<String, dynamic> 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<String, dynamic> 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<List<String?>>? board;
List<String>? bag;
double? g;
bool? playing;
Hold? hold;
String? piece;
Handling? handling;
DataFullGame.fromJson(Map<String, dynamic> 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<String, dynamic> 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<Tetromino> shuffleList(List<Tetromino> 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<Tetromino> tetrominoes = [Tetromino.Z, Tetromino.L, Tetromino.O, Tetromino.S, Tetromino.I, Tetromino.J, Tetromino.T];
List<List<List<Vector2>>> 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<Vector2> spawnPositionFixes = [Vector2(1, 1), Vector2(1, 1), Vector2(0, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1)];
const Map<String, double> 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<int> comboTable = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5];