9.9 KiB
🎯 Taekwondo Scoring System – Scoring Engine Guide
Version: 1.0
Last Updated: July 2025
Target Developer: .NET Android (MAUI/Xamarin) Developer
Purpose: Detailed specification of the Scoring Engine — the logic that validates, awards, and logs points in real-time
📌 Table of Contents
- 1. Overview
- 2. World Taekwondo (WT) Scoring Rules
- 3. Inputs to the Scoring Engine
- 4. Point Validation Logic
- 5. Timing & Synchronization
- 6. Spinning Kick Detection
- 7. Gam-jeom (Penalty) Handling
- 8. Manual Override (Emergency Mode)
- 9. Score Fusion & Conflict Resolution
- 10. C# Scoring Engine Implementation
- 11. Integration with Display System
- 12. Best Practices
1. Overview
The Scoring Engine is the core logic module that decides when a point is awarded.
It runs on the Android tablet (score control unit) and:
- Receives impact data from ESP32 sensors
- Receives votes from corner referee joysticks
- Applies WT rules
- Validates timing and technique
- Awards points automatically
- Logs all events
❗ No automatic scoring — even with sensors, referee validation is required.
2. World Taekwondo (WT) Scoring Rules
| Technique | Points |
|---|---|
| Kick to trunk (hogu) | 1 |
| Kick to head | 3 |
| Spinning kick to trunk | 4 |
| Spinning kick to head | 5 |
| Punch to trunk | 1 (rare) |
| Gam-jeom (penalty) on opponent | +1 to opponent |
✅ Conditions for a Valid Point
- Impact must be legal (correct surface, no grabbing)
- Force must exceed threshold
- At least 3 out of 4 corner referees must press within 1 second
- For spinning kicks: rotation > 180° confirmed by MPU6050
- No manual input from center referee
3. Inputs to the Scoring Engine
The engine receives two types of input:
📥 1. Sensor Data (from ESP32)
{
"type": "impact",
"device": "head_blue",
"force": 900,
"spinning": true,
"time": 1712345678901
}
📥 2. Referee Votes (from ESP32 Joysticks)
{
"type": "button",
"device": "corner_2",
"pressed": true,
"time": 1712345678905
}
All timestamps use
DateTime.UtcNow.TicksorEnvironment.TickCount.
4. Point Validation Logic
The engine checks for valid scoring events by correlating sensor impact with referee votes.
✅ Valid Point Conditions
| Technique | Required Votes | Notes |
|---|---|---|
| Trunk Kick | ≥3 corner refs | Within 1 sec of impact |
| Head Kick | ≥3 corner refs | Same |
| Spinning Kick | ≥3 votes + spinning:true |
Award 4 or 5 pts |
| Punch | ≥3 votes + force > threshold | Rarely scored |
🔄 Validation Flow
1. Sensor detects impact → record time T
2. Wait 1 second for referee votes
3. Count votes within [T-100ms, T+1000ms]
4. If ≥3 votes → award points
5. Broadcast to scoreboard
✅ Use a sliding window to handle timing jitter.
5. Timing & Synchronization
⏱️ Timing Window
- Referee button presses must occur within ±1 second of impact
- Use UTC ticks for precision:
long now = DateTime.UtcNow.Ticks;
🕒 Window Logic
bool IsInWindow(long impactTime, long voteTime)
{
long windowStart = impactTime - TimeSpan.TicksPerSecond / 10; // -100ms (early press)
long windowEnd = impactTime + TimeSpan.TicksPerSecond; // +1000ms
return voteTime >= windowStart && voteTime <= windowEnd;
}
🔄 Debouncing
- Ignore repeated impacts from same sensor within 500ms
- Prevents double-counting
if (lastImpactTime[device] + 5000000 > now) // 500ms in ticks
return; // Too soon
6. Spinning Kick Detection
Spinning kicks require rotation > 180° detected by MPU6050.
📊 Sensor Data
{"type":"impact","device":"head_red","force":850,"spinning":true,"time":...}
🧠 Detection Logic (on ESP32)
- Use gyroscope (Z-axis) to measure rotation
- If angular change > 180° during kick → set
spinning: true
The tablet scoring engine trusts this flag — no re-calculation.
7. Gam-jeom (Penalty) Handling
Penalties are manually awarded by the scorekeeper.
✅ Rules
- 10 Gam-jeoms = 1 point to opponent
- But in practice, each Gam-jeom adds 1 point immediately
🖥️ UI
- Button:
Gam-jeom (Red)orGam-jeom (Blue) - On tap:
AddPenalty("red"); // Adds 1 to blue's score BroadcastScoreUpdate();
📡 JSON Event
{
"type": "penalty",
"player": "red",
"reason": "Grabbing",
"time": 1712345679000
}
8. Manual Override (Emergency Mode)
For system failures, the scorekeeper can manually award points.
🔐 Access
- Hidden button (e.g., long-press "Settings")
- Requires PIN (optional)
🎯 Manual Actions
| Button | Effect |
|---|---|
+1 |
Add 1 to red or blue |
+3 |
Add 3 (head kick) |
+5 |
Add 5 (spinning head kick) |
Undo Last |
Revert last point |
Use sparingly — logs all manual actions.
9. Score Fusion & Conflict Resolution
🔄 Fusion Logic
The engine fuses sensor data and referee votes into a single event.
public class ImpactEvent
{
public string Device { get; set; } // head_red, hogu_blue
public long Time { get; set; }
public int Force { get; set; }
public bool Spinning { get; set; }
public List<string> Voters { get; set; } // corner_1, corner_3
}
void ProcessImpact(ImpactEvent impact)
{
var validVotes = GetVotesInRange(impact.Time);
if (validVotes.Count >= 3)
{
AwardPoints(impact, validVotes);
}
}
🛑 Conflict Resolution
| Scenario | Action |
|---|---|
| 2 votes only | No point |
| 3+ votes but wrong target | No point |
| Force too low | Ignore |
| Duplicate impact | Debounce (500ms) |
10. C# Scoring Engine Implementation
public class ScoringEngine
{
private Dictionary<string, long> _lastImpactTime = new();
private List<ImpactData> _impacts = new();
private List<RefereeVote> _votes = new();
private int _redScore = 0, _blueScore = 0;
private int _redFouls = 0, _blueFouls = 0;
public void AddImpact(string device, int force, bool spinning, long time)
{
// Debounce
if (_lastImpactTime.ContainsKey(device) &&
time - _lastImpactTime[device] < 5000000) // 500ms
return;
_lastImpactTime[device] = time;
_impacts.Add(new ImpactData { Device = device, Force = force, Spinning = spinning, Time = time });
CheckForValidPoint(device, force, spinning, time);
}
public void AddRefereeVote(string corner, long time)
{
_votes.Add(new RefereeVote { Corner = corner, Time = time });
}
private void CheckForValidPoint(string device, int force, bool spinning, long impactTime)
{
// Force threshold
if ((device.Contains("hogu") && force < 500) ||
(device.Contains("head") && force < 400)) return;
// Get votes within ±1 sec
var votes = _votes
.Where(v => IsInWindow(impactTime, v.Time))
.Select(v => v.Corner)
.ToList();
if (votes.Count < 3) return;
// Determine attacker
string attacker = device.Contains("_red") ? "red" : "blue";
int points = CalculatePoints(device, spinning);
// Award
if (attacker == "red") _redScore += points;
else _blueScore += points;
// Log and broadcast
LogEvent($"✅ +{points} to {attacker} (Voters: {string.Join(",", votes)})");
BroadcastScoreUpdate();
}
private int CalculatePoints(string device, bool spinning)
{
if (device.Contains("head"))
return spinning ? 5 : 3;
else
return spinning ? 4 : 1;
}
private bool IsInWindow(long impactTime, long voteTime)
{
long start = impactTime - 100000; // -100ms
long end = impactTime + 10000000; // +1000ms
return voteTime >= start && voteTime <= end;
}
public void AddPenalty(string player)
{
if (player == "red") _blueScore++;
else _redScore++;
BroadcastScoreUpdate();
}
private void BroadcastScoreUpdate()
{
var json = new {
mode = "score",
red = new { score = _redScore, fouls = _redFouls },
blue = new { score = _blueScore, fouls = _blueFouls },
timer = GetCurrentTime()
};
// Send to Android TV via TCP
}
private void LogEvent(string message)
{
// Write to event log
}
}
// Data Classes
public class ImpactData { public string Device; public int Force; public bool Spinning; public long Time; }
public class RefereeVote { public string Corner; public long Time; }
11. Integration with Display System
The scoring engine broadcasts updates to the Android TV scoreboard.
📡 Message Format (TCP to Port 5001)
{
"mode": "score",
"red": { "score": 8, "fouls": 1 },
"blue": { "score": 5, "fouls": 2 },
"timer": "01:45",
"last_point": "Head Kick +3"
}
🔄 Trigger Events
- Point awarded
- Penalty given
- Round change
- Match start/end
12. Best Practices
| Practice | Why |
|---|---|
Use DateTime.UtcNow.Ticks |
High precision, no timezone issues |
| Debounce sensor inputs | Prevent double-counting |
| Log all events | Debugging and auditing |
| Validate force thresholds | Prevent false positives |
| Require 3/4 referee votes | WT compliance |
| Keep manual override limited | Prevent abuse |
| Broadcast immediately | Real-time display |