Last active
May 18, 2023 06:02
-
-
Save brandonros/c3c7bc8632d45f411f38d2c3e04d7682 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
scrape from https://www.espn.com/nba/playbyplay/_/gameId/401547671 with | |
const quarter = document.querySelector('.tabs__wrapper--playByPlay .tabs__list__item--active').innerText | |
const rows = document.querySelectorAll('.playByPlay__tableRow') | |
const plays = [] | |
for (const row of rows) { | |
const index = row.attributes['data-idx'] | |
const time = row.querySelector('.playByPlay__time').innerText | |
const text = row.querySelector('.playByPlay__text').innerText | |
const awayScore = row.querySelector('.playByPlay__score--away').innerText | |
const homeScore = row.querySelector('.playByPlay__score--home').innerText | |
plays.push({ | |
index, | |
quarter, | |
time, | |
text, | |
awayScore, | |
homeScore | |
}) | |
} | |
console.log(JSON.stringify(plays)) | |
*/ | |
import fs from 'fs' | |
const classifyPlay = (teams, players, play) => { | |
// weird useless message? | |
const classifications = [] | |
if (play.text === 'vs.') { | |
classifications.push('unknown') | |
} | |
// free throw | |
if (play.text.indexOf('misses free throw') !== -1) { | |
classifications.push('freeThrow:miss') | |
} | |
if (play.text.indexOf('makes free throw') !== -1) { | |
classifications.push('freeThrow:make') | |
} | |
// rebound | |
if (play.text.indexOf('offensive team rebound') !== -1) { | |
classifications.push('rebound:offensiveTeamRebound') | |
} | |
if (play.text.indexOf('defensive rebound') !== -1) { | |
classifications.push('rebound:defensiveRebound') | |
} | |
if (play.text.indexOf('defensive team rebound') !== -1) { | |
classifications.push('rebound:defensiveTeamRebound') | |
} | |
if (play.text.indexOf('offensive rebound') !== -1) { | |
classifications.push('rebound:offensiveRebound') | |
} | |
// turnover | |
if (play.text.indexOf('lost ball turnover') !== -1) { | |
classifications.push('turnover:lostBallTurnover') | |
} | |
if (play.text.indexOf('bad pass') !== -1) { | |
classifications.push('turnover:badPassTurnover') | |
} | |
if (play.text.indexOf('out of bounds lost ball turnover') !== -1) { | |
classifications.push('turnover:outOfBoundsLostBallTurnover') | |
} | |
if (play.text.indexOf('turnover') !== -1) { | |
classifications.push('turnover') | |
} | |
// foul | |
if (play.text.indexOf('personal foul') !== -1) { | |
classifications.push('foul:personalFoul') | |
} | |
if (play.text.indexOf('shooting foul') !== -1) { | |
classifications.push('foul:shootingFoul') | |
} | |
if (play.text.indexOf('offensive foul') !== -1) { | |
classifications.push('foul:offensiveFoul') | |
} | |
if (play.text.indexOf('loose ball foul') !== -1) { | |
classifications.push('foul:looseBallFoul') | |
} | |
if (play.text.indexOf('personal take foul') !== -1) { | |
classifications.push('foul:personalTakeFoul') | |
} | |
// goaltending | |
if (play.text.indexOf('offensive goaltending') !== -1) { | |
// TODO: turnover? | |
classifications.push('offensiveGoaltending') | |
} | |
if (play.text.indexOf('defensive goaltending') !== -1) { | |
// TODO: turnover? | |
classifications.push('defensiveGoaltending') | |
} | |
// team | |
if (play.text.indexOf('enters the game') !== -1) { | |
classifications.push('team:substitution') | |
} | |
// kicked ball violation | |
if (play.text.indexOf('kicked ball violation') !== -1) { | |
// TODO: turnover? | |
classifications.push('kickedBallViolation') | |
} | |
// timeout | |
if (play.text.indexOf('timeout') !== -1) { | |
classifications.push('team:timeout') | |
} | |
// block | |
if (play.text.indexOf('block') !== -1) { | |
classifications.push('block') | |
} | |
// travel | |
if (play.text.indexOf('traveling') !== -1) { | |
// TODO: turnover? | |
classifications.push('travel') | |
} | |
// challenge | |
if (play.text.indexOf('COACH\'S CHALLENGE') !== -1) { | |
classifications.push('team:challenge') | |
} | |
// jump ball | |
if (play.text.match(/(.*) vs\. (.*) \(.* gains possession\)/)) { | |
classifications.push('jumpBall') | |
} | |
// assist | |
if (play.text.indexOf('assist') !== -1) { | |
classifications.push('assist') | |
} | |
// steal | |
if (play.text.indexOf('steal') !== -1) { | |
classifications.push('steal') | |
} | |
// shot | |
if (play.text.indexOf('misses') !== -1) { | |
classifications.push('miss') | |
} | |
if (play.text.indexOf('makes tip shot') !== -1) { | |
classifications.push('shot:makeTipShot') | |
} | |
if (play.text.indexOf('makes') !== -1) { | |
classifications.push('make') | |
} | |
if (play.text.indexOf('layup') !== -1) { | |
classifications.push('shot:layup') | |
} | |
if (play.text.indexOf('three point jumper') !== -1) { | |
classifications.push('shot:threePointJumper') | |
} | |
if (play.text.indexOf('three pointer') !== -1) { | |
classifications.push('shot:threePointer') | |
} | |
if (play.text.indexOf('three point pullup jump shot') !== -1) { | |
classifications.push('shot:threePointPullupJumpShot') | |
} | |
if (play.text.indexOf('three point shot') !== -1) { | |
classifications.push('shot:threePointShot') | |
} | |
if (play.text.indexOf('two point shot') !== -1) { | |
classifications.push('shot:twoPointShot') | |
} | |
if (play.text.indexOf('driving floating jump shot') !== -1) { | |
classifications.push('shot:drivingFloatingJumpShot') | |
} | |
if (play.text.indexOf('pullup jump shot') !== -1) { | |
classifications.push('shot:pullupJumpShot') | |
} | |
// players | |
for (const player of players) { | |
if (play.text.indexOf(player) !== -1) { | |
classifications.push(`player:${player}`) | |
} | |
} | |
// teams | |
for (const team of teams) { | |
if (play.text.indexOf(team) !== -1) { | |
classifications.push(`team:${team}`) | |
} | |
} | |
// time | |
if (play.text.indexOf('End of Game') !== -1) { | |
classifications.push('time:endOfGame') | |
} | |
if (play.text.indexOf('End of the') !== -1) { | |
classifications.push('time:endOfQuarter') | |
} | |
if (play.quarter === '1st') { | |
classifications.push('time:q1') | |
classifications.push('time:h1') | |
} | |
if (play.quarter === '2nd') { | |
classifications.push('time:q2') | |
classifications.push('time:h1') | |
} | |
if (play.quarter === '3rd') { | |
classifications.push('time:q3') | |
classifications.push('time:h2') | |
} | |
if (play.quarter === '4th') { | |
classifications.push('time:q4') | |
classifications.push('time:h2') | |
} | |
return classifications | |
} | |
const players = [ | |
'Robert Williams III', | |
'Payton Pritchard', | |
'Max Strus', | |
'Marcus Smart', | |
'Malcolm Brogdon', | |
'Kyle Lowry', | |
'Kevin Love', | |
'Bam Adebayo', | |
'Caleb Martin', | |
'Gabe Vincent', | |
'Jaylen Brown', | |
'Max Strus', | |
'Malcon Brogdon', | |
'Cody Zeller', | |
'Al Horford', | |
'Jimmy Butler', | |
'Derrick White', | |
'Jayson Tatum', | |
'Duncan Robinson', | |
] | |
const teams = [ | |
'Heat', | |
'Celtics' | |
] | |
const categoryMap = { | |
steals: [ | |
'steal' | |
], | |
rebounds: [ | |
'rebound:defensiveRebound', | |
'rebound:defensiveTeamRebound', | |
'rebound:offensiveRebound', | |
'rebound:offensiveTeamRebound' | |
], | |
freeThrows: [ | |
'freeThrow:make', | |
'freeThrow:miss' | |
], | |
assists: [ | |
'assist', | |
], | |
blocks: [ | |
'block', | |
], | |
fouls: [ | |
'foul:looseBallFoul', | |
'foul:offensiveFoul', | |
'foul:personalFoul', | |
'foul:personalTakeFoul', | |
'foul:shootingFoul' | |
], | |
shots: [ | |
'shot:drivingFloatingJumpShot', | |
'shot:layup', | |
'shot:makeTipShot', | |
'shot:pullupJumpShot', | |
'shot:threePointJumper', | |
'shot:threePointPullupJumpShot', | |
'shot:threePointShot', | |
'shot:threePointer', | |
'shot:twoPointShot' | |
], | |
turnovers: [ | |
'turnover', | |
'turnover:badPassTurnover', | |
'turnover:lostBallTurnover', | |
'turnover:outOfBoundsLostBallTurnover' | |
], | |
miscellaneous: [ | |
'offensiveGoaltending', | |
'kickedBallViolation', | |
'defensiveGoaltending', | |
'travel' | |
] | |
} | |
const categories = Object.keys(categoryMap) | |
const plays = JSON.parse(await fs.promises.readFile('./plays.json', 'utf8')) | |
for (const play of plays) { | |
play.classifications = classifyPlay(teams, players, play) | |
if (play.classifications.length === 0) { | |
// panic on unknown | |
console.log(play) | |
throw new Error('TODO') | |
} | |
play.playerClassifications = play.classifications.filter(classification => classification.indexOf('player:') !== -1) | |
play.teamClassifications = play.classifications.filter(classification => classification.indexOf('team:') !== -1) | |
play.timeClassifications = play.classifications.filter(classification => classification.indexOf('time:') !== -1) | |
//console.log(play) | |
} | |
const jimmyPlays = plays.filter(play => { | |
return play.playerClassifications.find(playerClassification => playerClassification === 'player:Jimmy Butler') | |
}) | |
const results = {} | |
for (const play of jimmyPlays) { | |
for (const classification of play.classifications) { | |
for (const category of categories) { | |
const classificationIsInCategory = categoryMap[category].indexOf(classification) !== -1 | |
if (classificationIsInCategory) { | |
if (!results[category]) { | |
results[category] = [] | |
} | |
const isPlayAlreadyInCategory = results[category].find(categorizedPlay => JSON.stringify(categorizedPlay) === JSON.stringify(play)) | |
if (isPlayAlreadyInCategory === undefined) { | |
results[category].push(play) | |
} | |
} | |
} | |
} | |
} | |
console.log(JSON.stringify(results, undefined, 2)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment