├── .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 | 
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 | 
86 |
87 | ### Accuracy:
88 |
89 | 
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 |
--------------------------------------------------------------------------------