#Undisputed Bot Reference
File: und.py · Lines: 1456 · Game: Undisputed Boxing
#Overview
The Undisputed bot is the most complex bot, featuring:
- OCR-based fighter selection using Tesseract
- Fuzzy text matching using RapidFuzz
- Screen template matching for state detection
- Pixel-based damage stats reading during round breaks
- Color-based winner detection after fights
- Auto-focus loop running every 1 second
#Global Variables
| Variable | Type | Description |
|---|---|---|
current_red |
str |
Red corner fighter name |
current_blue |
str |
Blue corner fighter name |
weight |
str |
Weight class (1-10) |
match_winner |
str|None |
Winner: 'player1', 'player2', or 'DRAW' |
last_queue_id |
int|None |
Server-provided match ID |
match_score_sent |
bool |
Whether match-score was emitted this match |
awaiting_score_emit |
bool |
Gate: block start until score is sent |
stats_captured |
bool |
Whether break-time stats were captured |
screen_comparator |
ScreenComparator |
Template matching instance |
#Functions
#print_and_emit(message, level) ACTIVE
Print to console + emit to /logs namespace.
#log_to_file(message) ACTIVE
Write to und.log if USE_LOG_FILE=True, else print.
#focus_window_by_exact_title(title, quiet) ACTIVE
Focus game window via pywinauto. Connects to Undisputed.exe.
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
str |
— | Window title |
quiet |
bool |
False |
Suppress log output |
#_auto_focus_loop() ACTIVE
Background thread that calls focus_window_by_exact_title('Undisputed', quiet=True) every 1 second.
#_start_auto_focus_if_needed() ACTIVE
Start the auto-focus thread if not already running.
#OCR Functions
#read_first_name(frame, away) ACTIVE
OCR read fighter's first name from screen.
| Parameter | Type | Default | Description |
|---|---|---|---|
frame |
np.ndarray |
— | Screenshot as numpy array |
away |
bool |
False |
If True, read from away corner (x offset +1230) |
ROI Coordinates: (231, 863) to (455, 890) for home
#read_last_name(frame, away) ACTIVE
OCR read fighter's last name.
ROI: (221, 890) to (530, 933) for home, (1366, 890) to (1706, 930) for away
#read_player_name(frame, away) ACTIVE
OCR read fighter's full player name.
ROI: (206, 941) to (485, 968) for home, (1424, 946) to (1728, 971) for away
#read_p1_stuck(frame) ACTIVE
OCR check if game is stuck at P1 selection screen.
ROI: (1077, 470) to (1202, 573)
#compare_text_fuzzy(text1, text2, threshold) ACTIVE
Compare two texts using multiple RapidFuzz algorithms and return best score.
| Parameter | Type | Default | Description |
|---|---|---|---|
text1 |
str |
— | First text |
text2 |
str |
— | Second text |
threshold |
int |
80 |
Minimum match score |
Algorithms used: fuzz.ratio, fuzz.partial_ratio, fuzz.token_sort_ratio, fuzz.WRatio
Returns: (is_match: bool, best_score: float)
#Fight Setup
#startMatch1() ACTIVE
Complete fighter selection flow:
- Weight Selection: Maps weight class (1-10) to number of
Key.2presses - Red Corner Selection: Loop OCR + fuzzy match → navigate with
Key.right - Blue Corner Selection: Same process for away corner
Special OCR Corrections:
JOE LOUIS→ matches against'LoYIg'fallbackREGIS PROGAIS→ matches against'PROGCRAIS'SAUL ALVAREZ→ matches against'SAGL'or'SAOL'ERIC ESCH→ matches against'BRIG'MUHAMMAD ALI→ excludes'64'variant
#_start_sequence_from_payload(data, source_label) ACTIVE
Unified start sequence handler (used by both on_new_match and on_start_match).
Flow:
- Clear flags → Stop memory thread
- Start OBS stream (with retry up to 5x)
- Parse payload (player names, weight, match ID)
- Navigate game menus (15s wait → Press F → Press R)
- P1 stuck detection
- Call
startMatch1()for fighter selection - Set difficulty → Confirm fight
- Start
memory_listener_thread()
#Screen State Handlers
#on_screen_changed(screen_name, similarity) ACTIVE
Main screen state callback. Handles:
| Screen | Action |
|---|---|
'start' |
Reset match_is_started flag |
'winniewinner' |
Trigger winner detection flow |
'none' |
Reset team names |
#Winner Detection
#detect_color_name(x, y) ACTIVE
Detect winner side by pixel color at coordinates.
| Color (Hex) | Side |
|---|---|
#8d0000 (Red) |
player1 |
#0d60c1 (Blue) |
player2 |
#2a2a2a (Grey) |
DRAW |
Uses Euclidean color distance for matching.
#get_pixel_color(x, y) INTERNAL
Get hex color of pixel at screen coordinates.
#hex_to_rgb(hex_color) INTERNAL
Convert hex color to RGB tuple.
#color_distance(c1, c2) INTERNAL
Calculate Euclidean distance between two RGB colors.
#Damage Stats (Break Time)
#get_bar_percent(start_x, end_x, y_pos, avoid_hex, tolerance) ACTIVE
Scan pixel bar to calculate fill percentage.
- Home bars: scan left-to-right
- Away bars: scan right-to-left
- Skips pixels matching background color (
#010101)
#get_box_percent_inverse(points, avoid_hex, tolerance) ACTIVE
Calculate percentage of active (non-black) pixels from a set of coordinate points.
#get_all_screen_stats() ACTIVE
Read all damage stats from screen:
| Stat | Home Coords | Away Coords |
|---|---|---|
| Head | 170→251 @ y=651 |
1750→1670 @ y=651 |
| Body | 170→251 @ y=689 |
1750→1670 @ y=689 |
| L Swell | 170→251 @ y=798 |
1750→1670 @ y=798 |
| R Swell | 170→251 @ y=835 |
1750→1670 @ y=835 |
| L Cuts | Box at 4 points @726 | Box at 4 points @726 |
| R Cuts | Box at 4 points @762 | Box at 4 points @762 |
#log_stats_to_file(data) ACTIVE
Append stats to match_stats_log.json (JSON Lines format) for debugging.
#Memory Thread
#memory_listener_thread() ACTIVE
Main loop running every 0.5 seconds:
- Read
gameTimeandcurrentRound - During break time (gameTime == 0): capture screen stats
- Read boxer scorecard data from memory
- Detect match start when
gameTime > 0 - Emit
match-datawith round scores
#Socket Events (Emitted)
| Event | Payload | When |
|---|---|---|
waiting |
{uuid, minutes} |
Bot ready |
myid |
{id: uuid} |
Device identification |
start-match |
{uuid, game_time} |
Match started |
home-away |
{uuid, player_1, player_2} |
Fighter names |
team-full |
{uuid, player_1, player_2} |
Fighter data |
match-data |
Score/round data | Every 0.5s |
round-break-stats |
{uuid, round, stats} |
During round break |
winner-detected |
{uuid, winner, total_rounds, game_time, lastQueueId} |
Winner screen detected |
match-score |
Full scorecard data | After winner detection |
#Socket Events (Received)
| Event | Action |
|---|---|
new-match |
Start new match via _start_sequence_from_payload() |
starting-match |
Same as new-match |
pong |
Log pong response |