Kovaaks

Kovaaks is a "game" that allows to directly practice mouse control in 3d fps games. There are hundreds of different mini-games to practice with, each having a different focus.

This guide is best for getting perspective or understanding how and why to use Kovaaks

File Names

Kovaaks has a great feature in which it saves every mini-game's stats to a csv. The format of the csv's names is as follows

<scenario name> - <Challenge or Freeplay> - YYYY.MM.DD-HH.MM.SS Stats.csv

Example:

Tile Frenzy - Challenge - 2020.12.14-08.46.00 Stats.csv

Some of the scenario names also have dashes so just checking against the first dash will not work

Tile Frenzy - Strafing - 03 - Challenge - 2020.12.14-08.34.31 Stats.csv

The Data

Each file has 4 parts:

  • List of all Kills
  • Weapon, shots, hits, damage done, damage possible
  • Overall Stats and info
  • Info about settings (input lag, fps, sens, FOV, etc)

import pandas as pd
import matplotlib.pyplot as plt
from urllib.request import urlopen
from io import StringIO
import plotly.express as px
from IPython.display import HTML

Each part of the data has different formats and headers. Here are the Headers/keys in python

keys_kills=["Date","Kill #","Timestamp","Bot","Weapon","TTK","Shots","Hits","Accuracy","Damage Done","Damage Possible","Efficiency","Cheated"]
keys_weapon=["Date","Weapon","Shots","Hits","Damage Done","Damage Possible"]
keys_info=["Date","Kills","Deaths","Fight Time","Avg TTK","Damage Done","Damage Taken","Midairs","Midaired","Directs","Directed","Distance Traveled","Score","Scenario","Hash","Game Version","Challenge Start","Input Lag","Max FPS (config)","Sens Scale","Horiz Sens","Vert Sens","FOV","Hide Gun","Crosshair","Crosshair Scale","Crosshair Color","Resolution","Avg FPS","Resolution Scale"]
keys_info_no_colon=["Resolution","Avg FPS","Resolution Scale"]

#HELPERS

def split_format_file(section, output, date):
    split_section = section.split('\n')
#     if output == "":
#         output = split_section[0]
    # TODO: Add date to each line
    for i in range(len(split_section[1:])):
        if split_section[i+1][-1] == ',':
            split_section[i+1] = split_section[i+1][:-1]
        split_section[i+1] = date + "," + split_section[i+1]
    section = '\n'.join(split_section[1:])
    output = output + '\n' + section
    return output


def format_info(info, output, date):
    info_lines = info.split('\n')
    data = []
    for key in keys_info:
        if key == "Date":
            found_key = True
            data.append(date)
        else:
            found_key = False
        for line in info_lines:
            if any(key in line for key in keys_info_no_colon):
                split_line = line.split(',')
                if len(split_line) > 1:
                    if split_line[0] == key:
                        found_key = True
                        data.append(split_line[1])
            else:
                split_line = line.split(':', 1)
                if len(split_line) > 1:
                    if split_line[0] == key:
                        found_key = True
                        data.append(split_line[1][1:])
        if not found_key:
            data.append('')
    output = output + '\n' + ','.join(data)
    return output
# Current online directory for my stats 
stat_dir = "https://jprier.github.io/stats/"
stat_filenames_url = "https://jprier.github.io/stats/filenames.txt"

stat_filenames = urlopen(stat_filenames_url).read().decode('utf-8').split('\n')

kills = ','.join(keys_kills)
weapon = ','.join(keys_weapon)
info = ','.join(keys_info)

for filename in stat_filenames:
    # TODO: parse filename for challenge name and date
    try:
        filename = filename.replace(' ', '%20')
        file = urlopen(stat_dir + filename).read().decode('utf-8').split('\n\n')
        if len(file) > 1:
            date = filename.split('%20')[-2]
            # TODO: Add challenge name and date to each as columns
            kills = split_format_file(file[0], kills, date)

            # file[1] --> df_weapon
            weapon = split_format_file(file[1], weapon, date)

            # file[2,3] --> df_info
            info = format_info(file[2]+"\n"+file[3], info, date)
            
    except Exception as err:
        print(err)
        
df_kills = pd.read_csv(StringIO(kills), sep=",")
df_weapons = pd.read_csv(StringIO(weapon), sep=",")
df_info = pd.read_csv(StringIO(info), sep=",")

df_kills["Date"] = pd.to_datetime(df_kills.Date, format='%Y.%m.%d-%H.%M.%S')#df_kills["Date"].dt.strftime("%Y.%d.%m-%H.%M.%S")
df_weapons["Date"] = pd.to_datetime(df_weapons.Date, format='%Y.%m.%d-%H.%M.%S')#df_weapons["Date"].dt.strftime("%Y.%d.%m-%H.%M.%S")
df_info["Date"] = pd.to_datetime(df_info.Date, format='%Y.%m.%d-%H.%M.%S')#df_info["Date"].dt.strftime("%Y.%d.%m-%H.%M.%S")

Visualizing the Data

scenarios = df_info['Scenario'].unique()
scenario, scenarios = scenarios[0], scenarios[1:]

df_info_max = df_info.loc[df_info['Scenario'] == scenario].resample('D')['Score'].agg(['max'])
df_info_max['Scenario'] = scenario

for scenario in scenarios:
    df_info_max_scenario = df_info.loc[df_info['Scenario'] == scenario].resample('D')['Score'].agg(['max'])
    df_info_max_scenario = df_info_max_scenario[df_info_max_scenario['max'].notna()]
    if df_info_max_scenario.size > 3:
        df_info_max_scenario['Scenario'] = scenario
        df_info_max = df_info_max.append(df_info_max_scenario)
    
with pd.option_context('display.max_rows', 10, 'display.max_columns', None):
    display(df_info_max)

fig = px.line(df_info_max, x=df_info_max.index, y="max", color='Scenario')
fig1 = px.scatter(df_info, x=df_info.index, y="Score", trendline='lowess', color='Scenario')