convertPointsToShots = function(dataPointsClean) {
let data = [];
let currentMatch = -1;
let currentSetsPl1, currentSetsPl2, currentGamesPl1, currentGamesPl2, currentPointsPl1, currentPointsPl2;
let point, currentPoint, rally, pos, event, prevEvent, isPlayer1Shot, shouldSavePoint;
for (let p of dataPointsClean) {
shouldSavePoint = true;
if (p.matchId == currentMatch) {
currentPoint += 1;
} else {
currentMatch = p.matchId;
currentPoint = 1;
currentSetsPl1 = 0;
currentSetsPl2 = 0;
currentGamesPl1 = 0;
currentGamesPl2 = 0;
currentPointsPl1 = 0;
currentPointsPl2 = 0;
}
point = {
matchId: p.matchId,
pointNumber: currentPoint,
setsPlayer1: currentSetsPl1,
setsPlayer2: currentSetsPl2,
gamesPlayer1: currentGamesPl1,
gamesPlayer2: currentGamesPl2,
pointsPlayer1: currentPointsPl1,
pointsPlayer2: currentPointsPl2,
tiebreakPointNumber: p.tiebreakPointNumber,
isPlayer1Serving: !!p.isPlayer1Serving,
isPlayer1Winner: !!p.isPlayer1Winner,
isGameWinner: !!p.isGameWinner,
isSetWinner: !!p.isSetWinner,
//rally1: p.rally1,
//rally2: p.rally2,
events: []
};
// decode rally1+rally2 into events (shots)
if ((typeof p.rally1 !== 'string') || (p.rally1.length == 0)) {
//return 'missing rally1';
return rally + ' - ' + pos + ' - missing_rally1 - ' + p.matchId + ' - ' + currentPoint;
}
// save penalties and points with missing shot data
if (['S', 'R', 'P', 'Q'].includes(p.rally1[0])) {
if ((p.rally1.length > 1) || p.rally2) {
//return 'wrong entry for penalties or points with missing shot data';
return rally + ' - ' + pos + ' - penalties_no_data - ' + p.matchId + ' - ' + currentPoint;
}
switch (p.rally1[0]) {
case 'S':
point.events.push({ _eventName: 'server_point' });
shouldSavePoint = false;
break;
case 'R':
point.events.push({ _eventName: 'returner_point' });
shouldSavePoint = false;
break;
case 'P':
point.events.push({ _eventName: 'server_penalty' });
shouldSavePoint = false;
break;
case 'Q':
point.events.push({ _eventName: 'returner_penalty' });
shouldSavePoint = false;
break;
}
} else {
rally = p.rally1;
isPlayer1Shot = !!p.isPlayer1Serving;
pos = 0;
// capture 1st serve event
event = readServe(p);
if (typeof event === 'object') {
point.events.push(event);
} else {
return event;
}
// if 1st serve resulted in a fault - go to 2nd serve
if (event._eventName == 'serve_fault') {
rally = p.rally2;
pos = 0;
// capture 2nd serve event
//console.log([p.matchId, currentPoint]);
event = readServe(p);
if (typeof event === 'object') {
point.events.push(event);
} else {
return event;
}
}
// if 2nd serve resulted in a fault - double fault; otherwise, capture rally
if (event._eventName != 'serve_fault') {
// capture serve win outcome
if (['*', '#'].includes(rally[pos])) {
point.events.push({
_eventName: 'ace',
isUnreturnable: (rally[pos] == '#')
});
} else {
// capture rally shots
while ((pos < rally.length) && !['*', 'n', 'w', 'd', 'x', '!', 'C', 'e', '@', '#'].includes(rally[pos])) {
isPlayer1Shot = !isPlayer1Shot;
event = readShot(p);
if (typeof event === 'object') {
point.events.push(event);
} else {
return event;
}
}
// capture rally result
event = {
isPlayer1: !!isPlayer1Shot
};
if (['*', 'n', 'w', 'd', 'x', '!', 'C', 'e'].includes(rally[pos])) {
switch (rally[pos]) {
case '*':
event._eventName = 'winner';
break;
case 'n':
event.errorType = 'net';
break;
case 'w':
event.errorType = 'wide';
break;
case 'd':
event.errorType = 'deep';
break;
case 'x':
event.errorType = 'wide_deep';
break;
case '!':
event.errorType = 'shank';
event.isShank = true;
break;
case 'C':
//event._eventName = 'lost_challenge';
event._eventName = 'winner'; // lumping together for simplicity
event.hasLostChallenge = true;
break;
case 'e':
event.errorType = 'unknown';
shouldSavePoint = false;
break;
}
pos += 1;
}
if (pos < rally.length) {
// capture if error was forced or unforced
switch (rally[pos]) {
case '@':
event._eventName = 'error_unforced';
break;
case '#':
event._eventName = 'error_forced';
break;
}
}
if (event.hasOwnProperty('_eventName')) {
point.events.push(event);
} else if (event.hasOwnProperty('errorType')) {
event._eventName = 'error_unknown';
point.events.push(event);
} else {
shouldSavePoint = false;
//return 'wrong rally result';
//return rally + ' - ' + pos + ' - rally_result - ' + p.matchId + ' - ' + currentPoint;
}
}
}
}
// save point sequence
if (shouldSavePoint) {
data.push(point);
}
// update sets, games and points for the next point
if (p.isSetWinner) {
if (p.isPlayer1Winner) {
currentSetsPl1 += 1;
} else {
currentSetsPl2 += 1;
}
currentGamesPl1 = 0;
currentGamesPl2 = 0;
currentPointsPl1 = 0;
currentPointsPl2 = 0;
} else if (p.isGameWinner) {
if (p.isPlayer1Winner) {
currentGamesPl1 += 1;
} else {
currentGamesPl2 += 1;
}
currentPointsPl1 = 0;
currentPointsPl2 = 0;
} else {
if (p.isPlayer1Winner) {
currentPointsPl1 += 1;
} else {
currentPointsPl2 += 1;
}
}
}
function readServe(p) {
// seed the event
let ev = {
isPlayer1: !!isPlayer1Shot
};
// skip let serves
while (rally[pos] == 'c') {
pos += 1;
}
let posServeStart = pos;
while (['4', '5', '6', '0', '+', '^', ';', 'n', 'w', 'd', 'x', 'g', '!', 'V', 'e'].includes(rally[pos])) {
switch (rally[pos]) {
case '4':
ev._eventName = 'serve_wide';
break;
case '5':
ev._eventName = 'serve_body';
break;
case '6':
ev._eventName = 'serve_middle';
break;
case '0':
ev._eventName = 'serve_unknown';
shouldSavePoint = false;
break;
case '+':
ev.courtPosition = 'approach';
break;
case '^':
ev.isDropVolley = true;
break;
case ';':
ev.hasClippedNet = true;
break;
case 'n':
ev.faultType = 'net';
break;
case 'w':
ev.faultType = 'wide';
break;
case 'd':
ev.faultType = 'deep';
break;
case 'x':
ev.faultType = 'wide-deep';
break;
case 'g':
ev.faultType = 'foot';
break;
case '!':
ev.faultType = 'shank';
ev.isShank = true;
break;
case 'V':
ev.faultType = 'time';
break;
case 'e':
ev.faultType = 'unknown';
break;
}
pos += 1;
}
if (pos == posServeStart) {
//return 'cannot read serve';
return rally + ' - ' + pos + ' - serve - ' + p.matchId + ' - ' + currentPoint;
}
if (ev.hasOwnProperty('faultType')) {
ev._eventName = 'serve_fault';
}
return ev;
}
function readShot(p) {
// seed the event
let ev = {
isPlayer1: !!isPlayer1Shot
};
// capture shot type
switch (rally[pos]) {
case 'f':
ev._eventName = 'forehand';
break;
case 'b':
ev._eventName = 'backhand';
break;
case 'r':
ev._eventName = 'f_slice';
break;
case 's':
ev._eventName = 'b_slice';
break;
case 'v':
ev._eventName = 'f_volley';
break;
case 'z':
ev._eventName = 'b_volley';
break;
case 'o':
ev._eventName = 'f_smash';
break;
case 'p':
ev._eventName = 'b_smash';
break;
case 'u':
ev._eventName = 'f_drop';
break;
case 'y':
ev._eventName = 'b_drop';
break;
case 'l':
ev._eventName = 'f_lob';
break;
case 'm':
ev._eventName = 'b_lob';
break;
case 'h':
ev._eventName = 'f_half_volley';
break;
case 'i':
ev._eventName = 'b_half_volley';
break;
case 'j':
ev._eventName = 'f_swing_volley';
break;
case 'k':
ev._eventName = 'b_swing_volley';
break;
case 't':
ev._eventName = 'trick';
break;
case 'q':
ev._eventName = 'unknown';
shouldSavePoint = false;
break;
default:
//return 'cannot read shot type';
return rally + ' - ' + pos + ' - shot_type - ' + p.matchId + ' - ' + currentPoint;
}
pos += 1;
// capture court position, special situations, direction of the shot and return depth
while (['+', '-', '=', ';', '^', '!', '1', '2', '3', '7', '8', '9', '0'].includes(rally[pos])) {
switch (rally[pos]) {
case '+':
ev.courtPosition = 'approach';
break;
case '-':
ev.courtPosition = 'net';
break;
case '=':
ev.courtPosition = 'baseline';
break;
case ';':
ev.hasClippedNet = true;
break;
case '^':
ev.isDropVolley = true;
break;
case '!':
ev.isShank = true;
break;
case '1':
ev.shotDirection = 'forehand';
break;
case '2':
ev.shotDirection = 'middle';
break;
case '3':
ev.shotDirection = 'backhand';
break;
case '7':
ev.returnDepth = 'short';
break;
case '8':
ev.returnDepth = 'middle';
break;
case '9':
ev.returnDepth = 'deep';
break;
case '0':
if (ev.hasOwnProperty('shotDirection')) {
ev.returnDepth = 'unknown';
} else {
ev.shotDirection = 'unknown';
}
break;
}
pos += 1;
}
return ev;
}
return data;
}