├── .gitattributes ├── .gitignore ├── CSGOPredictor ├── MainApp.py ├── README.md ├── READMEDocs ├── CalibrationPlot.png ├── ConfusionMatrix.png ├── Intro.gif └── workflow.png ├── exceptions.py ├── gamestate_integration_CSGOPredictor.cfg ├── gsi_pinger.py ├── gui.py ├── listener.py ├── requirements.txt ├── snapshot_parser.py └── welcome.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | logs/* 3 | DataforSimplifiedModel.csv 4 | MainApp - Debug Version.py 5 | predictions.txt 6 | Modeling SimplifiedLR in Python.py 7 | snapshot_formatter_debugger.py 8 | snapshot_parser debugger.py 9 | test.py 10 | Time_Delay.py 11 | BugLogs/* 12 | HTTPrequest.py 13 | latestMainApp.txt 14 | test2.py 15 | .vscode/settings.json 16 | TaskList.md 17 | CSGOPredictor Intro Video.mp4 18 | READMEDocs/CSGOPredictor Gif V2.mp4 19 | READMEDocs/SteamIntro.gif 20 | READMEDocs/SteamLogo.png 21 | -------------------------------------------------------------------------------- /CSGOPredictor: -------------------------------------------------------------------------------- 1 | {"meta": "lr", "classes_": [0, 1], "coef_": [[-0.04334473225031668, 0.9703836416509347, -0.014593494262953196, 0.011466440364647999, 0.0003911163850693754, -0.31229495636022864, 0.38819634683056525, -0.004285125770594866, 0.0034914060531245085, -0.003283788444827394, 0.0030181524180385175, -0.14968084536483667, 0.13921647554717362, 0.10986193747413393, -0.08166232185492707, -0.20886458711309094, -0.02853230133464324, -0.2729735954338973, 0.11603553570755379, 0.025210236852656196, -0.06512589316817023, -0.07272204519418585, 0.39570083889536783, -0.0846870614542674]], "intercept_": [0.14747069886968905], "n_iter_": [2222], "params": {"C": 1.0, "class_weight": null, "dual": false, "fit_intercept": true, "intercept_scaling": 1, "l1_ratio": null, "max_iter": 3000, "multi_class": "auto", "n_jobs": null, "penalty": "l2", "random_state": null, "solver": "lbfgs", "tol": 0.0001, "verbose": 0, "warm_start": false}} -------------------------------------------------------------------------------- /MainApp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author: d-roho 4 | """ 5 | import pywintypes 6 | import sys 7 | import time 8 | from gsi_pinger import pingerfunc 9 | from win32gui import GetWindowText, GetForegroundWindow 10 | 11 | 12 | def current_window(): 13 | 14 | # Returns name of current window for listener.py 15 | window = GetWindowText(GetForegroundWindow()) 16 | return window 17 | 18 | 19 | def welcome_message(): 20 | 21 | # Display Welcome Message unless disabled by "-w" arg 22 | if "-w" not in sys.argv: 23 | f = open('welcome.txt', 'r') 24 | print(''.join([line for line in f])) 25 | print("CSGOPredictor Launched Successfully!") 26 | if "-p" in sys.argv: 27 | print("PAUSE-AND-PLAY = DISABLED") 28 | else: print("PAUSE-AND-PLAY = ENABLED") 29 | if "delay" in sys.argv: 30 | delay_index = sys.argv.index("delay") + 1 31 | delay_value = float(sys.argv[delay_index]) 32 | print("DELAY VALUE = " + str(delay_value) + " seconds") 33 | else: print("DELAY VALUE = 0 seconds") 34 | time.sleep(1.5) 35 | 36 | 37 | def change_dir(): 38 | 39 | """Changes working directory to match location of this python file""" 40 | import os 41 | 42 | # Changing working directory 43 | dir_path = os.path.dirname(os.path.realpath(__file__)) 44 | os.chdir(str(dir_path)) 45 | print("current working directory is - " + str(dir_path)) 46 | return dir_path 47 | 48 | 49 | def import_model(): 50 | 51 | """Imports trained Logistic Regression model""" 52 | import sklearn_json as skljson 53 | 54 | # Importing Model 55 | print("Importing Model...") 56 | model = skljson.from_json("CSGOPredictor") 57 | print("Model Imported Successfully!") 58 | return model 59 | 60 | 61 | def check_for_match_start(): 62 | 63 | """Pauses script till useful data is being recorded' 64 | to generate predictions""" 65 | test = {} 66 | print("Waiting for CS:GO to be launched...") 67 | while len(test.keys()) == 0: 68 | test = pingerfunc() 69 | print("CS:GO has been launched!") 70 | print("Waiting for Match to begin...") 71 | 72 | while test.get("allplayers") is None: 73 | test = pingerfunc() 74 | while test.get("map").get("phase") == 'warmup': 75 | test = pingerfunc() 76 | 77 | print("Match has Begun!") 78 | 79 | 80 | def match_start_check_postlaunch(): 81 | 82 | """Same as check_for_match_start(), but used in cases where 83 | CS:GO is known to have already been launched""" 84 | test = {} 85 | while True: 86 | try: 87 | test = pingerfunc() 88 | if len(test.get("allplayers").keys()) == 0: 89 | time.sleep(1) 90 | test = pingerfunc() 91 | else: 92 | return False 93 | except AttributeError: 94 | time.sleep(1) 95 | continue 96 | 97 | while True: 98 | try: 99 | test = pingerfunc() 100 | if test.get("map").get("phase") == 'warmup': 101 | time.sleep(1) 102 | test = pingerfunc() 103 | else: 104 | return False 105 | except AttributeError: 106 | time.sleep(1) 107 | continue 108 | 109 | 110 | def parse_and_predict(): 111 | 112 | """The main loop that parses logs and runs the predictive model. 113 | Returns probability prediction of round outcome""" 114 | window = current_window() 115 | welcome_message() 116 | change_dir() 117 | model = import_model() 118 | check_for_match_start() 119 | 120 | from snapshot_parser import exception_handler, snapshot_formatter, snapshot_arrayfier 121 | import exceptions 122 | from listener import pause_detector, pause_screen 123 | pause_counter = 0 124 | delay_req = False 125 | if "delay" in sys.argv: 126 | delay_req = True 127 | delay_index = sys.argv.index("delay") + 1 128 | delay_value = float(sys.argv[delay_index]) 129 | 130 | while True: 131 | try: 132 | if delay_req is True: 133 | time.sleep(delay_value) 134 | # Checking is a Pause request was initiated in previous loop 135 | if "-p" not in sys.argv: 136 | from listener import raise_pause_screen # imports latest value 137 | if raise_pause_screen is True: # Checks if pause was requested 138 | pause_screen() 139 | 140 | # Pinging for latest snapshot 141 | snapshot = None 142 | while snapshot is None: 143 | snapshot = pingerfunc() 144 | 145 | snapshot = exception_handler(snapshot) 146 | 147 | # formatting snapshot 148 | snapshot_formatted = snapshot_formatter(snapshot) 149 | 150 | # parsing snapshot to create \attributes lists for predictive model 151 | predictors = snapshot_arrayfier(snapshot_formatted) 152 | 153 | """Prediction""" 154 | 155 | """Freeze Time - Making Time prediction attribute default to 115 sec 156 | during freezetime. This makes it so that the low time_left 157 | during freezetime doesn't skew prediction towards Ts""" 158 | if snapshot_formatted["phase_countdowns"].get("phase") == "freezetime": 159 | predictors[4] = 115 160 | 161 | # Running model with predictors 162 | pred_nested = model.predict_proba([predictors]) 163 | pred = pred_nested[0] # converts list from nested to unnested 164 | pred = list(pred) # converts numpy array to list 165 | for i in range(2): # decimal -> %, and round values 166 | pred[i] = round(pred[i]*100, 2) 167 | 168 | """Default Predictions - These are scenarios in which the winner of 169 | the round has been decided (or is a virtual certainty) which the 170 | predictive model is not able to account for when making prediction 171 | """ 172 | # Round Over 173 | if snapshot_formatted["round"]["phase"] == "over": 174 | if snapshot_formatted["round"].get("win_team") == "T": 175 | pred = [0, 100] 176 | if snapshot_formatted["round"].get("win_team") == "CT": 177 | pred = [100, 0] 178 | 179 | """Virtual Round Win - Scenarios in which a team cannot lose, 180 | but the round is still live""" 181 | 182 | # Bomb Timer < 5 seconds 183 | if snapshot_formatted["phase_countdowns"].get("phase") == "bomb": 184 | if float(snapshot_formatted["phase_countdowns"].get("phase_ends_in")) < 5.0: 185 | pred = [0, 100] 186 | 187 | """Time to Defuse > Time left in Round - cant do this with 188 | existing info, solution may be possible (more info from GSI?)""" 189 | 190 | # Bomb Planted, All Ts dead - enough time to defuse - same as above 191 | 192 | print(pred) 193 | with open('predictions.txt', 'a') as fh: # writes predictions to txt file 194 | fh.write(str(pred)+'\n') 195 | 196 | # Check for Pause request (every 10 loops unless delay is specified) 197 | if "-p" not in sys.argv: 198 | if GetWindowText(GetForegroundWindow()) == window: 199 | if delay_req is True: # when delay, check for pause after each loop 200 | pause_detector() 201 | elif pause_counter % 10 == 0: 202 | pause_detector() 203 | pause_counter += 1 204 | 205 | # Exceptions 206 | except exceptions.EmptyServer: 207 | 208 | """Raised by program when no players are found in the server. 209 | Forces program to wait till at least one player is detected""" 210 | print("Server is empty. Program will automatically resume once at least one player joins the server.") 211 | time.sleep(1) 212 | print("Waiting...") 213 | match_start_check_postlaunch() 214 | print("Player(s) Detected!") 215 | time.sleep(1) 216 | continue 217 | 218 | except exceptions.MatchNotStarted: 219 | 220 | """Raised by program when player is not spectating a match. 221 | Usually occurs when user goes into, then exits a match""" 222 | print("You are not currently spectating a Match. Program will automatically resume when you begin spectating.") 223 | time.sleep(1) 224 | print("Waiting...") 225 | match_start_check_postlaunch() 226 | print("Match has Begun!") 227 | time.sleep(1) 228 | continue 229 | 230 | except exceptions.WarmUp: 231 | 232 | # Raised by program when match being spectated is in warm up mode 233 | print("Match is in Warm Up Phase. Predictions will begin after Warm Up.") 234 | time.sleep(1) 235 | print("Waiting...") 236 | check_for_match_start() # For unknown reason, match_start_check_postlaunch() doesnt work here 237 | time.sleep(1) 238 | continue 239 | 240 | except KeyError: 241 | 242 | """A catch-all exception which restarts the loop. Should not occur. 243 | Please report on GitHub if found.""" 244 | print("KeyError. Restarting loop.") 245 | print("This should not occur. Please raise a Ticket on GitHub!") 246 | time.sleep(5) 247 | continue 248 | 249 | except KeyboardInterrupt: 250 | 251 | """Catches Command Terminal keyboard interrupts. Helps exit program smoothly. 252 | Without this, program raises several errors.""" 253 | print("Exiting Program") 254 | time.sleep(0.5) 255 | try: 256 | sys.exit() 257 | except: 258 | sys.exit() 259 | 260 | print("While loop in parse_and_predict somehow broken. This should not occur, please report on GitHub") 261 | time.sleep(5) 262 | sys.exit() 263 | 264 | 265 | if __name__ == '__main__': 266 | parse_and_predict() 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | ![]() 8 | 9 | # Quick Start Guide 10 | ## [CLICK ON ME FOR VIDEO GUIDE!](https://www.youtube.com/watch?v=ndZOmqIZvSs) 11 | 1. Install Python 3 (preferably latest version) 12 | * Install `wheel` package - `pip install wheel` if not installed 13 | 2. Clone repo 14 | 3. run `pip install -r requirements.txt` in repo directory to install required packages 15 | 4. copy `gamestate_integration_CSGOPredictor.cfg` file to `Steam\steamapps\common\Counter-Strike Global Offensive\csgo\cfg` 16 | 5. run `python MainApp.py` plus any arguments of your choice in command terminal or equivalent from location of this repo 17 | 18 | _Optional - run `gui.py` in another terminal while `MainApp.py` is running to display dynamic prediction bar_ 19 | 20 | ***The program will begin making predictions once you begin spectating a match in CS:GO.*** 21 | 22 | ## Requirements 23 | * Python 3 (preferably latest version) 24 | * `wheel` python package 25 | * All packages in `requirements.txt` (install wheel before requirements.txt) 26 | 27 | ***(And Counter-Strike: Global Offensive, of course!)*** 28 | 29 | ## Features 30 | 31 | ### ★ Prediction 32 | 33 | The program print the live round prediction in the terminal. Prediction format is `[CT Win%, T Win%]` 34 | * The program also writes each prediction to `predictions.txt` in repo directory. 35 | 36 | ### ★ Pause-and-Play 37 | 38 | ***Hold `Esc` key while terminal window is active to pause the program!*** (currently only works when run using Anaconda) 39 | 40 | ### ★ GUI 41 | 42 | Run `gui.py` in another terminal while `MainApp.py` is running to display dynamic prediction bar 43 | 44 | ### ★ Command Line Arguments 45 | 46 | * -w = disable Welcome Message 47 | * -p = disable Pause-and-Play functionality 48 | * delay X = delay predictions by X seconds 49 | 50 | # CSGOPredictor - An Overview 51 | 52 | ### ***CSGOPredictor is a python program that generates live round winner predictions of CS:GO Competitive matches.*** 53 | 54 | ## How it works 55 | ![Workflow](https://github.com/d-roho/CSGOPredictor/blob/main/READMEDocs/workflow.png) 56 | 57 | ### A simple 3 step process 58 | 59 | 1. When a match is live, *snapshots* of the the round in play, containing large amounts of precise data on round & players' status, are generated & captured using the `gsi_pinger` module through CS:GO's in-built Game State Integration functionality. 60 | 61 | 2. Each snapshot is cleaned and parsed using the `snapshot_parser` module, resulting in the creation of an array of 23 attributes to be used by the predictive model to generate probability predictions. Attributes include: 62 | * Round Data - `Map`, `Time Left`, `Bomb Plant Status` 63 | * Player Data - `T/CT Players Alive`, `T/CT Total Health`, `Weapons`, `Utility` 64 | 65 | 3. Finally, `MainApp.py` runs the pre-trained Logistic Regression model to generate probability prediction for round at that particular point in the round. 66 | * Prediction is in the form of an ordered duo of probabilities - first for CT win % and second for T win %. 67 | * Example: `[79.21, 20.79]`, indicating a `79.2%` win probability for CTs & `20.8%` win probability for Ts 68 | * The prediction is printed in the terminal as well as written to a text file in the parent directory (for use by other applications, such as `gui.py` which displays the predictions as a dynamic bar chart) 69 | 70 | ## Metrics 71 | 72 | The Predictive Model used in this program is 73 | * a Logistic Regression model 74 | * trained on this [dataset](https://www.kaggle.com/datasets/christianlillelund/csgo-round-winner-classification), which contains 122,411 snapshots of from high level tournament play in 2019 and 2020. 75 | * The dataset is a pre-processed version of the dataset released by [SkyBox.gg](skybox.gg) as a part of their [AI hackathon](https://skybox.gg/blog/csgo-predictions-showcased-at-blast-premier). 76 | * The model was trained on all 122k+ snapshots, with 90+ attributes used out of the 97 present in the dataset. Some of the attributes were combined to create 23 final attributes which the model uses to make predictions. 77 | 78 | ### Calibration Plot: 79 | _A calibration plot is a line-and-scatter plot which compares the observed probabilities of an event 80 | versus the predicted probabilities. A well calibrated predictor is one where results that are 81 | predicted with an X% probability do indeed occur X% of the time._ 82 | 83 | *As the primary function of this program is to generate accurate probabilities, callibration is the key metric for success (not accuracy)* 84 | 85 | ![Calibration Plot](https://github.com/d-roho/CSGOPredictor/blob/main/READMEDocs/CalibrationPlot.png) 86 | 87 | ### Accuracy: 88 | 89 | ![Confusion Matrix](https://github.com/d-roho/CSGOPredictor/blob/main/READMEDocs/ConfusionMatrix.png) 90 | 91 | 92 | 93 | # Acknowledgements: 94 | 95 | 1. [Christian Lillelund](https://www.kaggle.com/christianlillelund) and [Skybox.gg](https://Skybox.gg) for data used to train the predictive model 96 | 2. [mdhedelund](https://github.com/mdhedelund) for their [CSGO-GSI](https://github.com/mdhedelund/CSGO-GSI) Repo, which was used in this repo's `gsi_pinger` module to interface with CS:GO's GSI 97 | 3. [mlrequest](https://github.com/mlrequest) for their [sklearn-json package](https://github.com/mlrequest/sklearn-json), used to import the predictive model in `MainApp.py` 98 | 99 | ### **Special Thanks to My Research Guide [Dr. Deepak Joy Mampilly](https://christuniversity.irins.org/profile/98201), and my Business Analytics professors [Dr. Kumar Chandar S](https://christuniversity.irins.org/profile/118264) & [Dr Manu K S](https://christuniversity.irins.org/profile/115783), for their guidance & support.** 100 | -------------------------------------------------------------------------------- /READMEDocs/CalibrationPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-roho/CSGOPredictor/cf4d4790f6f9aea34c53f957c0a87c1a39553558/READMEDocs/CalibrationPlot.png -------------------------------------------------------------------------------- /READMEDocs/ConfusionMatrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-roho/CSGOPredictor/cf4d4790f6f9aea34c53f957c0a87c1a39553558/READMEDocs/ConfusionMatrix.png -------------------------------------------------------------------------------- /READMEDocs/Intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-roho/CSGOPredictor/cf4d4790f6f9aea34c53f957c0a87c1a39553558/READMEDocs/Intro.gif -------------------------------------------------------------------------------- /READMEDocs/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d-roho/CSGOPredictor/cf4d4790f6f9aea34c53f957c0a87c1a39553558/READMEDocs/workflow.png -------------------------------------------------------------------------------- /exceptions.py: -------------------------------------------------------------------------------- 1 | # List of Custom Exceptions used to capture known potential errors\t 2 | 3 | 4 | class Error(Exception): 5 | # Base class for other exceptions 6 | pass 7 | 8 | 9 | class EmptyServer(Error): 10 | # Raised when server has a population of zero 11 | pass 12 | 13 | 14 | class MatchNotStarted(Error): 15 | # Raised when user is not currently spectating a Match (usually occurs when they've just exited out of a match) 16 | pass 17 | 18 | 19 | class WarmUp(Error): 20 | # Raised when match is in "Warm Up" phase (usually occurs when user changes from one match to another (which is in Warm Up phase)) 21 | pass 22 | -------------------------------------------------------------------------------- /gamestate_integration_CSGOPredictor.cfg: -------------------------------------------------------------------------------- 1 | "GSI for CSGO Predictor App by d-roho" 2 | { 3 | "uri" "http://127.0.0.1:3000" 4 | "timeout" "1.1" 5 | "buffer" "0.1" 6 | "throttle" "0.5" 7 | "heartbeat" "30.0" 8 | "auth" 9 | { 10 | "token" "odM6BOq8stAsOpRJK4hb" 11 | } 12 | "data" 13 | { 14 | "map" "1" 15 | "round" "1" 16 | "allplayers_id" "1" 17 | "allplayers_state" "1" 18 | "allplayers_match_stats" "1" 19 | "allplayers_weapons" "1" 20 | "phase_countdowns" "1" 21 | } 22 | } -------------------------------------------------------------------------------- /gsi_pinger.py: -------------------------------------------------------------------------------- 1 | """gsi_pinger is used by the MainApp to ping CSGO for data in the form of a 2 | snapshot of the round being spectated, for use by the predictive model. 3 | This is done through the use of its Gamestate Intergration functionality.""" 4 | 5 | 6 | def pingerfunc(): 7 | 8 | """The primary loop that opens a server, pings CSGO for a single snapshot, 9 | closes the server and makes snapshot data available to MainApp.""" 10 | from http.server import BaseHTTPRequestHandler, HTTPServer 11 | import json 12 | global snapshot 13 | snapshot = None 14 | 15 | class GSIServer(HTTPServer): 16 | def __init__(self, server_address, token, RequestHandler): 17 | self.auth_token = token 18 | 19 | super(GSIServer, self).__init__(server_address, RequestHandler) 20 | 21 | class RequestHandler(BaseHTTPRequestHandler): 22 | def do_POST(self): 23 | global snapshot 24 | length = int(self.headers['Content-Length']) 25 | body = self.rfile.read(length).decode('utf-8') 26 | payload = json.loads(body) 27 | 28 | # Ignore unauthenticated payloads 29 | if not self.authenticate_payload(payload): 30 | return None 31 | 32 | snapshot = payload 33 | exit() 34 | 35 | self.send_header('Content-type', 'text/html') 36 | self.send_response(200) 37 | self.end_headers() 38 | 39 | def authenticate_payload(self, payload): 40 | 41 | # Checks payload auth token against the token specified below 42 | if 'auth' in payload and 'token' in payload['auth']: 43 | return payload['auth']['token'] == server.auth_token 44 | else: 45 | return False 46 | 47 | server = GSIServer(('localhost', 3000), 'odM6BOq8stAsOpRJK4hb', RequestHandler) 48 | 49 | try: 50 | server.serve_forever() 51 | except: 52 | server.server_close() 53 | return snapshot 54 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | """gui.py creates an updating matplotlib bar chart displaying predictions 2 | when run alongside MainApp""" 3 | 4 | 5 | def gui_main_loop(): 6 | 7 | # The Main Loop that generates/updates the Prediction Bar Chart till stopped 8 | import os 9 | from file_read_backwards import FileReadBackwards 10 | import matplotlib.pyplot as plt 11 | import matplotlib.animation as animation 12 | import matplotlib as mpl 13 | 14 | mpl.rcParams['toolbar'] = 'None' 15 | fig = plt.figure() 16 | ax = fig.add_subplot(1, 1, 1) 17 | # changing working directory 18 | dir_path = os.path.dirname(os.path.realpath(__file__)) 19 | os.chdir(str(dir_path)) 20 | print("current directory is - " + str(dir_path)) 21 | 22 | def animate(i): 23 | CT = [0] 24 | T = [0] 25 | 26 | with FileReadBackwards("predictions.txt") as file: 27 | pred = None 28 | iterator = 0 29 | for line in file: 30 | if iterator < 1: 31 | pred = str(line) 32 | iterator += 1 33 | else: 34 | break 35 | pred = pred[1:-1] 36 | pred = pred.split(", ", 1) 37 | pred[0] = float(pred[0]) 38 | pred[1] = float(pred[1]) 39 | CT[0] = pred[0] 40 | T[0] = pred[1] 41 | X = "Preds" 42 | 43 | ax.clear() 44 | ax.barh(X, CT, color="b") 45 | ax.barh(X, T, left=CT, color="orange") 46 | ax.set_yticklabels([]) 47 | ax.set_xticklabels([]) 48 | 49 | teams = ["CT\n", "T\n"] 50 | 51 | iterator = 0 52 | for bar in ax.containers: 53 | lab = teams[iterator] + str(pred[iterator])[:5] + "%" 54 | labels = [lab] 55 | ax.bar_label(bar, labels=labels, label_type='center', fontsize=16, color="w", fontweight='bold') 56 | iterator += 1 57 | 58 | 59 | ani = animation.FuncAnimation(fig, animate, interval=1000) 60 | plt.show() 61 | 62 | if __name__ == "__main__": 63 | gui_main_loop() 64 | -------------------------------------------------------------------------------- /listener.py: -------------------------------------------------------------------------------- 1 | """Module that enables Pause-and-Play functionality. Hold Esc Key to Pause!""" 2 | 3 | from pynput.keyboard import Key, Listener 4 | from threading import Timer 5 | import time 6 | import sys 7 | 8 | """This variable is imported & used in MainApp's parse_and_predict() loop 9 | to detect if a Pause request was initiated""" 10 | global raise_pause_screen 11 | raise_pause_screen = False 12 | 13 | 14 | def on_press(key): 15 | 16 | # Defines the action to be done when Esc Key is held down 17 | if key == Key.esc: 18 | global raise_pause_screen 19 | print("pausing...") 20 | raise_pause_screen = True 21 | 22 | 23 | def pause_detector(): 24 | 25 | """Runs the Listener, which looks out for Esc Key press. 26 | When Esc detected, runs on_press()""" 27 | with Listener( 28 | on_press=on_press) as l: 29 | Timer(0.01, l.stop).start() 30 | l.join() 31 | 32 | 33 | def pause_screen(): 34 | 35 | """Temporarily pauses program, allowing users to either resume or exit altogether. 36 | Executed when pause request detected by pause_detector""" 37 | global raise_pause_screen 38 | raise_pause_screen = False 39 | print("Predictions Paused - Hit Enter to resume or enter 'q' to exit program: ") 40 | while True: 41 | try: 42 | response = input() 43 | if response == 'q': 44 | print("quitting...") 45 | time.sleep(0.5) 46 | sys.exit() 47 | elif response == "": 48 | print("resuming...") 49 | time.sleep(0.5) 50 | break 51 | else: 52 | print("invalid input. try again") 53 | except EOFError: 54 | pass 55 | except KeyboardInterrupt: 56 | print("quitting program") 57 | sys.exit() 58 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pywin32 2 | sklearn_json 3 | pynput 4 | file_read_backwards 5 | matplotlib 6 | -------------------------------------------------------------------------------- /snapshot_parser.py: -------------------------------------------------------------------------------- 1 | """Imports""" 2 | import exceptions 3 | 4 | 5 | def exception_handler(snapshot): 6 | 7 | # Checks to see if snapshot is valid. See exceptions.py for details 8 | if snapshot.get("allplayers") is None: 9 | raise exceptions.MatchNotStarted 10 | if snapshot.get("map").get("phase") == 'warmup': 11 | raise exceptions.WarmUp 12 | if len(snapshot["allplayers"].keys()) == 0: 13 | raise exceptions.EmptyServer 14 | return snapshot 15 | 16 | 17 | def snapshot_formatter(ssoriginal): 18 | 19 | # Converts Player names to Generic names for easy dict access 20 | ss1 = ssoriginal 21 | playernames = list(ss1["allplayers"].keys()) 22 | ss1_ap = ss1["allplayers"] 23 | for i in range(len(playernames)): 24 | ss1_ap[str("player" + str(i+1))] = ss1_ap.pop(str(playernames[i])) 25 | return ss1 26 | 27 | 28 | def snapshot_arrayfier(snapshot_formatted): 29 | 30 | """Creating list of attributes for prediction 31 | 32 | # # Order of attributes - 33 | # # 'map', 'bomb_planted','ct_score', 't_score', 'time_left', 34 | # # 'ct_players_alive','t_players_alive', 'ct_health', 't_health', 35 | # # 'ct_armor', 't_armor','ct_pistols_special', 't_pistols_special', 36 | # # 't_pistols_standard', 'ct_pistols_standard', 'ct_primaries_force', 37 | # # 't_primaries_force', 'ct_primaries_fullbuy', 't_primaries_fullbuy', 38 | # # 'ct_grenades', 't_grenades', 'ct_helmets', 't_helmets', 'ct_defuse_kits' 39 | 40 | # # Encoded Lables:Original Values 41 | # # round_winner = {'CT': 0, 'T': 1} 42 | # # map = {'de_cache': 0, 'de_dust2': 1, 'de_inferno': 2, 'de_mirage': 3, 43 | # 'de_nuke': 4, 'de_overpass': 5, 'de_train': 6, 'de_vertigo': 7} 44 | # # bomb_planted = {False: 0, True: 1} 45 | """ 46 | 47 | snap = snapshot_formatted 48 | predictors = [ 49 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 51 | ] 52 | 53 | # adding var0 - map 54 | map_codes = { 55 | 'de_cache': 0, 'de_dust2': 1, 'de_inferno': 2, 'de_mirage': 3, 56 | 'de_nuke': 4, 'de_overpass': 5, 'de_train': 6, 'de_vertigo': 7, 'de_ancient': 4 57 | } 58 | map_string = str(snap["map"]["name"]) 59 | if map_codes.get(map_string) is None: 60 | predictors[0] = 1 # Default Map set to de_dust2 (most balanced map) 61 | else: 62 | predictors[0] = map_codes.get(map_string) 63 | 64 | # adding var1 - bomb_planted 65 | if "bomb" in snap["round"].keys(): 66 | predictors[1] = 1 67 | 68 | # adding var2,3 - scores 69 | predictors[2] = snap["map"]["team_ct"]["score"] 70 | predictors[3] = snap["map"]["team_t"]["score"] 71 | 72 | # adding var4 - time_left 73 | predictors[4] = int(float(snap["phase_countdowns"]["phase_ends_in"])) 74 | 75 | # adding var 5,6,7,8 - players alive and team health 76 | counter_t = 0 77 | counter_ct = 0 78 | health_t = 0 79 | health_ct = 0 80 | for i in range(len(snap["allplayers"].keys())): 81 | if snap["allplayers"][str("player" + str(i + 1))]["state"]["health"] > 0: 82 | if snap["allplayers"][str("player" + str(i + 1))]["team"] == "T": 83 | counter_t += 1 84 | health_t += snap["allplayers"][str("player" + str(i + 1))]["state"]["health"] 85 | if snap["allplayers"][str("player" + str(i + 1))]["team"] == "CT": 86 | counter_ct += 1 87 | health_ct += snap["allplayers"][str("player" + str(i + 1))]["state"]["health"] 88 | 89 | predictors[5] = counter_ct 90 | predictors[6] = counter_t 91 | predictors[7] = health_ct 92 | predictors[8] = health_t 93 | 94 | # adding var 9,10 - armor 95 | armor_t = 0 96 | armor_ct = 0 97 | 98 | for i in range(len(snap["allplayers"].keys())): 99 | if snap["allplayers"][str("player" + str(i + 1))]["state"]["armor"] > 0: 100 | if snap["allplayers"][str("player" + str(i + 1))]["team"] == "T": 101 | armor_t += snap["allplayers"][str("player" + str(i + 1))]["state"]["armor"] 102 | if snap["allplayers"][str("player" + str(i + 1))]["team"] == "CT": 103 | armor_ct += snap["allplayers"][str("player" + str(i + 1))]["state"]["armor"] 104 | 105 | predictors[9] = armor_ct 106 | predictors[10] = armor_t 107 | 108 | # adding vars 11 to 20 - weapons and grenades 109 | ct_pistols_special = 0; t_pistols_special = 0 110 | pistols_special = ["weapon_cz75auto", "weapon_elite", 'weapon_r8revolver', 'weapon_deagle', 'weapon_fiveseven', 111 | 'weapon_p250', 'weapon_tec9'] 112 | t_pistols_standard = 0; ct_pistols_standard = 0 113 | pistols_standard = ['weapon_usps', 'weapon_glock', 'weapon_hkp2000'] 114 | ct_primaries_force = 0; t_primaries_force = 0 115 | primaries_force = ['weapon_bizon', 'weapon_famas', 'weapon_galilar', 'weapon_mac10', 'weapon_mag7', 'weapon_mp5sd', 116 | 'weapon_mp7', 'weapon_mp9', 'weapon_negev', 'weapon_nova', 'weapon_p90', 'weapon_sawedoff', 117 | 'weapon_ssg08', 'weapon_ump45', 'weapon_xm1014', ] 118 | ct_primaries_fullbuy = 0; t_primaries_fullbuy = 0 119 | primaries_fullbuy = ['weapon_ak47', 'weapon_aug', 'weapon_awp', 'weapon_g3sg1', 'weapon_m249', 'weapon_m4a1s', "weapon_m4a1", 120 | "weapon_m4a1_silencer", 'weapon_m4a4', 'weapon_scar20', 'weapon_sg556'] 121 | ct_grenades = 0; t_grenades = 0 122 | grenades = ["weapon_hegrenade", "weapon_frag_grenade", "weapon_flashbang", "weapon_smokegrenade", "weapon_decoy", 123 | "weapon_molotov", "weapon_incgrenade", ] 124 | ignore = ["weapon_knife", "weapon_knife_t", "weapon_c4"] 125 | 126 | for i in range(len(snap["allplayers"].keys())): 127 | player = str("player" + str(i + 1)) 128 | if snap["allplayers"][player]["team"] == "T": 129 | for iterator in range(len(list(snap["allplayers"][player]["weapons"].keys()))): 130 | weapon = str("weapon_" + str(iterator)) 131 | if snap["allplayers"][player]["weapons"][weapon]["name"] in ignore: 132 | continue 133 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in pistols_special: 134 | t_pistols_special += 1 135 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in pistols_standard: 136 | t_pistols_standard += 1 137 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in primaries_force: 138 | t_primaries_force += 1 139 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in primaries_fullbuy: 140 | t_primaries_fullbuy += 1 141 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in grenades: 142 | t_grenades += 1 143 | 144 | elif snap["allplayers"][player]["team"] == "CT": 145 | for iterator in range(len(list(snap["allplayers"][player]["weapons"].keys()))): 146 | weapon = str("weapon_" + str(iterator)) 147 | if snap["allplayers"][player]["weapons"][weapon]["name"] in ignore: 148 | continue 149 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in pistols_special: 150 | ct_pistols_special += 1 151 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in pistols_standard: 152 | ct_pistols_standard += 1 153 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in primaries_force: 154 | ct_primaries_force += 1 155 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in primaries_fullbuy: 156 | ct_primaries_fullbuy += 1 157 | elif snap["allplayers"][player]["weapons"][weapon]["name"] in grenades: 158 | ct_grenades += 1 159 | 160 | weapons_count = [ct_pistols_special, t_pistols_special, t_pistols_standard, ct_pistols_standard, 161 | ct_primaries_force, t_primaries_force, ct_primaries_fullbuy, t_primaries_fullbuy, ct_grenades, t_grenades] 162 | predictors[11:21] = weapons_count[0:10] 163 | 164 | # adding vars 21,22 - helmets 165 | helmets_t = 0 166 | helmets_ct = 0 167 | 168 | for i in range(len(snap["allplayers"].keys())): 169 | player = str("player" + str(i + 1)) 170 | if snap["allplayers"][player]["state"]["helmet"] is True: 171 | if snap["allplayers"][player]["team"] == "T": 172 | helmets_t += 1 173 | if snap["allplayers"][player]["team"] == "CT": 174 | helmets_ct += 1 175 | 176 | predictors[21] = helmets_ct 177 | predictors[22] = helmets_t 178 | 179 | # adding var23 - defuse kits 180 | # # data not transmitted, check it out later 181 | 182 | return predictors 183 | -------------------------------------------------------------------------------- /welcome.txt: -------------------------------------------------------------------------------- 1 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&GPPPB&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@#7: :~5@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@#: G@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 4 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@J J@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 5 | @@@@@@@@@@@@@@@@@@@@@@@@@&BBP. ~Y@@G@@@@@@@@@@@@@@@@@G#@@@@@@@@@@@@@@@@@@ 6 | @@@@@@@@@@@@@@@@@@@@@@#J~: JGJ7.^7?????????????7J??#&&&&#&&@@@@@@@@@@ 7 | @@@@@@@@@@@@@@@@@@@@@B: .... .7^JPPP5?JY&@@@@@@@@@ 8 | @@@@@@@@@@@@@@@@@@@@@~ .J#&&&&&&@@@@@@@@@@@@@@@@@@@@ 9 | @@@@@@@@@@@@@@@@@@@@B :#@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 10 | @@@@@@@@@@@@@@@@@@@@G P@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 11 | @@@@@@@@@@@@@@@@@@##P Y#@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 12 | @@@@@@@@@@@@@@@@@G ^@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 13 | @@@@@@@@@@@@@@@@@5 ^J#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 14 | @@@@@@@@@@@@@@@@@? ^@@@&#B#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 15 | @@@@@@@@@@@@@@@@@?: ^@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 16 | @@@@@@@@@@@@@@@@@#^ !@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 17 | @@@@@@@@@@@@@@@@@# J@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 18 | @@@@@@@@@@@@@@@@@@~ 7??&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 19 | @@@@@@@@@@@@@@@@@@Y !G@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 20 | @@@@@@@@@@@@@@@@@Y: 7&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 21 | @@@@@@@@@@@@@@@@@^ ~&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 22 | @@@@@@@@@@@@@@@@@P. ^B@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 23 | @@@@@@@@@@@@@@@@@G. ?P^. .#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 24 | @@@@@@@@@@@@@@@@@~ P@@@&Y !&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 25 | @@@@@@@@@@@@@@@@&: 7@@@@@@P~ :P@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 26 | @@@@@@@@@@@@@@@@&. ~&@@@@@@@@5^: 7@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 27 | @@@@@@@@@@@@@@@@? !@@@@@@@@@@@@@? P@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 28 | @@@@@@@@@@@@@@#? P@@@@@@@@@@@@&! ^@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 29 | @@@@@@@@@@@@@G: ?@@@@@@@@@@@@@P ^@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 30 | @@@@@@@@@@@@@^ .J@@@@@@@@@@@@@@&: ^@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 31 | @@@@@@@@@@@@P :P@@@@@@@@@@@@@@@&: #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 32 | @@@@@@@@@@@B. .#@@@@@@@@@@@@@@@@@? 7&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 33 | @@@@@@@@@@@B^ ~Y@@@@@@@@@@@@@@@@@@@J B@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 34 | @@@@@@@@@@@B. 7@@@@@@@@@@@@@@@@@@@@@5 P@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 35 | @@@@@@@@@@&: .&@@@@@@@@@@@@@@@@@@@@&^ ?&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 36 | @@@@@@@@@@? ^@@@@@@@@@@@@@@@@@@@@@? .!JG@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 37 | @@@@@@@@@&: Y@@@@@@@@@@@@@@@@@@@@5?J5?!~~~^^!&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 38 | @@@@@@@@@@GPPPPPB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 39 | 40 | _____ _____ _____ ____ _____ _____ ______ _____ _____ _____ _______ ____ _____ 41 | / ____|/ ____| / ____|/ __ \ | __ \| __ \| ____| __ \_ _/ ____|__ __/ __ \| __ \ 42 | | | | (___(_) | __| | | | | |__) | |__) | |__ | | | || || | | | | | | | |__) | 43 | | | \___ \ | | |_ | | | | | ___/| _ /| __| | | | || || | | | | | | | _ / 44 | | |____ ____) || |__| | |__| | | | | | \ \| |____| |__| || || |____ | | | |__| | | \ \ 45 | \_____|_____(_)\_____|\____/ |_| |_| \_\______|_____/_____\_____| |_| \____/|_| \_\ 46 | --------------------------------------------------------------------------------