Last active
December 16, 2015 14:29
-
-
Save halfmanhalfdonut/5448690 to your computer and use it in GitHub Desktop.
NHL94 Stat Extractor (ZSnes)
This file contains hidden or 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
<?php | |
namespace app\extractors; | |
use \Exception; | |
class NHL94GameStatExtractor { | |
/* Hex locations for stats */ | |
# AA - time in seconds | |
# BB - period where 00, 40, 80, C0 are periods | |
# CC - Player, home starts at 00, away at 80 | |
# DD - penalty -- see penalty map for descriptions | |
const PENALTY_SUMMARY = 8785; | |
const HOME_POWER_PLAY_GOALS = 9045; | |
const HOME_POWER_PLAY = 9049; | |
const HOME_PENALTY_CONVERTED = 9053; # this stat is confusing | |
const HOME_PENALTY_TIMES = 9057; # and so is this one! | |
const HOME_ATTACK_ZONE = 9061; # and 9062 -- 9061 is seconds, 9062 is a 4:16s modifier (256 seconds) | |
const HOME_POWER_PLAY_TIME = 9065; # and 9066 | |
const HOME_POWER_PLAY_SHOTS = 9069; | |
const HOME_SHORTHANDED_GOALS = 9077; | |
const HOME_P1_SHOTS = 9085; | |
const HOME_P2_SHOTS = 9089; | |
const HOME_P3_SHOTS = 9093; | |
const HOME_OT_SHOTS = 9097; | |
const HOME_SHOTS = 9101; # total | |
const HOME_P1_GOALS = 9105; | |
const HOME_P2_GOALS = 9109; | |
const HOME_P3_GOALS = 9113; | |
const HOME_OT_GOALS = 9117; | |
const HOME_GOALS = 9121; | |
const HOME_FACEOFFS_WON = 9125; | |
const HOME_BODY_CHECKS = 9161; | |
const HOME_PASSES_RECEIVED = 9165; | |
const HOME_PASSES = 9169; | |
const HOME_BREAKAWAYS = 9173; | |
const HOME_BREAKAWAY_GOALS = 9177; | |
const HOME_PENALTY_SHOTS = 9181; | |
const HOME_PENALTY_SHOT_GOALS = 9185; | |
const HOME_ONE_TIMERS = 9193; | |
const HOME_ONE_TIMER_GOALS = 9197; | |
const HOME_TEAM = 10411; | |
const VISITOR_POWER_PLAY_GOALS = 9047; | |
const VISITOR_POWER_PLAY = 9051; | |
const VISITOR_PENALTIES = 9055; | |
const VISITOR_PENALTY_TIME = 9059; | |
const VISITOR_ATTACK_ZONE = 9063; # and 9064 | |
const VISITOR_POWER_PLAY_TIME = 9067; # and 9068 | |
const VISITOR_POWER_PLAY_SHOTS = 9071; | |
const VISITOR_SHORTHANDED_GOALS = 9079; | |
const VISITOR_P1_SHOTS = 9087; | |
const VISITOR_P2_SHOTS = 9091; | |
const VISITOR_P3_SHOTS = 9095; | |
const VISITOR_OT_SHOTS = 9099; | |
const VISITOR_SHOTS = 9103; | |
const VISITOR_P1_GOALS = 9107; | |
const VISITOR_P2_GOALS = 9111; | |
const VISITOR_P3_GOALS = 9115; | |
const VISITOR_OT_GOALS = 9119; | |
const VISITOR_GOALS = 9123; | |
const VISITOR_FACEOFFS_WON = 9127; | |
const VISITOR_BODY_CHECKS = 9163; | |
const VISITOR_PASSES_RECEIVED = 9167; | |
const VISITOR_PASSES = 9171; | |
const VISITOR_BREAKAWAYS = 9175; | |
const VISITOR_BREAKAWAY_GOALS = 9179; | |
const VISITOR_PENALTY_SHOTS = 9183; | |
const VISITOR_PENALTY_SHOT_GOALS = 9187; | |
const VISITOR_ONE_TIMERS = 9195; | |
const VISITOR_ONE_TIMER_GOALS = 9199; | |
const VISITOR_TEAM = 10413; | |
# AA BB CC DD EE FF GG | |
# AA - Time in seconds | |
# BB - Period | |
# CC - Team Status- HOME- 00 SH 2, 01 SH, 02 EV, 03 PP, 04 PP 2; AWAY- 80 SH 2, 81 SH, 82 EV, 83 PP, 84 PP 2 | |
# DD - Scoring player 00-18 | |
# EE - Assist 1 (FF if none) | |
# FF - Assist 2 (FF if none) | |
const SCORING_SUMMARY = 15695; | |
protected $penaltyMap = array( | |
20 => 'Roughing', | |
24 => 'Charging', | |
26 => 'Slashing', | |
28 => 'Roughing', | |
30 => 'Cross Check', | |
32 => 'Hooking', | |
34 => 'Tripping', | |
36 => 'Penalty Shot', | |
38 => 'Interference', | |
40 => 'Holding' | |
); | |
protected $periodMap = array( | |
0 => '1st', | |
1 => '1st', | |
64 => '2nd', | |
65 => '2nd', | |
128 => '3rd', | |
129 => '3rd', | |
192 => 'OT', | |
193 => 'OT', | |
12 => 'OT' | |
); | |
protected $stats = array(); | |
public function __construct($filename) { | |
if (empty($filename)) { | |
throw new Exception("ERROR: No file associated."); | |
} | |
$state = fopen('data:text/plain,'.urlencode($filename), 'rb'); | |
/* Grab these in order of appearance */ | |
$stats['penalty_summary'] = json_encode($this->getPenaltySummary($state)); | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_POWER_PLAY_GOALS); | |
$stats['home_pp_goals'] = $stmp['home']; | |
$stats['visitor_pp_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_POWER_PLAY); | |
$stats['home_pp'] = $stmp['home']; | |
$stats['visitor_pp'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorTimeStats($state, self::HOME_ATTACK_ZONE); | |
$stats['home_attack_zone'] = $stmp['home']; | |
$stats['visitor_attack_zone'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorTimeStats($state, self::HOME_POWER_PLAY_TIME); | |
$stats['home_pp_time'] = $stmp['home']; | |
$stats['visitor_pp_time'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_POWER_PLAY_SHOTS); | |
$stats['home_pp_shots'] = $stmp['home']; | |
$stats['visitor_pp_shots'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_SHORTHANDED_GOALS); | |
$stats['home_sh_goals'] = $stmp['home']; | |
$stats['visitor_sh_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_SHOTS); | |
$stats['home_shots'] = $stmp['home']; | |
$stats['visitor_shots'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_P1_GOALS); | |
$stats['home_p1_goals'] = $stmp['home']; | |
$stats['visitor_p1_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_P2_GOALS); | |
$stats['home_p2_goals'] = $stmp['home']; | |
$stats['visitor_p2_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_P3_GOALS); | |
$stats['home_p3_goals'] = $stmp['home']; | |
$stats['visitor_p3_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_OT_GOALS); | |
$stats['home_ot_goals'] = $stmp['home']; | |
$stats['visitor_ot_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_GOALS); | |
$stats['home_goals'] = $stmp['home']; | |
$stats['visitor_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_FACEOFFS_WON); | |
$stats['home_faceoffs_won'] = $stmp['home']; | |
$stats['visitor_faceoffs_won'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_BODY_CHECKS); | |
$stats['home_body_checks'] = $stmp['home']; | |
$stats['visitor_body_checks'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_PASSES_RECEIVED); | |
$stats['home_passes_received'] = $stmp['home']; | |
$stats['visitor_passes_received'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_PASSES); | |
$stats['home_passes'] = $stmp['home']; | |
$stats['visitor_passes'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_BREAKAWAYS); | |
$stats['home_breakaways'] = $stmp['home']; | |
$stats['visitor_breakaways'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_BREAKAWAY_GOALS); | |
$stats['home_breakaway_goals'] = $stmp['home']; | |
$stats['visitor_breakaway_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_PENALTY_SHOTS); | |
$stats['home_penalty_shots'] = $stmp['home']; | |
$stats['visitor_penalty_shots'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_PENALTY_SHOT_GOALS); | |
$stats['home_penalty_shot_goals'] = $stmp['home']; | |
$stats['visitor_penalty_shot_goals'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_ONE_TIMERS); | |
$stats['home_one_timers'] = $stmp['home']; | |
$stats['visitor_one_timers'] = $stmp['visitor']; | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_ONE_TIMER_GOALS); | |
$stats['home_one_timer_goals'] = $stmp['home']; | |
$stats['visitor_one_timer_goals'] = $stmp['visitor']; | |
/* Our DB is 1-index, the game is 0-index. Add one to get the right team! */ | |
$stmp = $this->getHomeVisitorStats($state, self::HOME_TEAM); | |
$stats['home_team'] = $stmp['home'] + 1; | |
$stats['visitor_team'] = $stmp['visitor'] + 1; | |
$stats['scoring_summary'] = json_encode($this->getScoringSummary($state)); | |
/* That's all we need for now */ | |
fclose($state); | |
$this->stats = $stats; | |
} | |
private function getHomeVisitorStats($state, $offset) { | |
$stats = []; | |
fseek($state, $offset); | |
$stats['home'] = $this->getPart($state); | |
fseek($state, $offset + 2); | |
$stats['visitor'] = $this->getPart($state); | |
return $stats; | |
} | |
private function getHomeVisitorTimeStats($state, $offset) { | |
$stats = []; | |
fseek($state, $offset); | |
$temp = $this->getPart($state); # first part -- actual seconds | |
$temp2 = $this->getPart($state); # second part -- modifier, seconds * 256 | |
$stats['home'] = $temp + ($temp2 * 256); | |
fseek($state, $offset + 2); | |
$temp = $this->getPart($state); # first part | |
$temp2 = $this->getPart($state); # second part! | |
$stats['visitor'] = $temp + ($temp2 * 256); | |
return $stats; | |
} | |
# AA - time in seconds | |
# BB - period where 00, 40, 80, C0 are periods | |
# CC - Player, home starts at 00, away at 80 | |
# DD - penalty -- see penalty map for descriptions | |
private function getPenaltySummary($state) { | |
$summaries = array(); | |
$originalOffset = self::PENALTY_SUMMARY; | |
# 65 possible penalties | |
for ($i = 0; $i < 65; $i++) { | |
fseek($state, $originalOffset + ($i * 4)); | |
$summaryTP = $this->getSummaryTimeAndPeriod($state); | |
$time = $summaryTP['time']; | |
$period = $summaryTP['period']; | |
$player = $this->getPart($state); | |
$penalty = $this->getPart($state); | |
$team = 'home'; | |
# If we have no penalty, stop | |
if ($penalty == 0) { | |
break; | |
} | |
if ($player > 30) { | |
$team = 'visitor'; | |
$player -= 128; # to set it back to 0 | |
} | |
$s = $i + 1; | |
$summaries[] = array( | |
"time" => $time, | |
"period" => $this->periodMap[$period], | |
"player" => $player, | |
"penalty" => $this->penaltyMap[$penalty], | |
"team" => $team | |
); | |
} | |
return $summaries; | |
} | |
# AA BB CC DD EE FF GG | |
# AA - Time in seconds | |
# BB - Period | |
# CC - Team Status- HOME- 00 SH 2, 01 SH, 02 EV, 03 PP, 04 PP 2; AWAY- 80 SH 2, 81 SH, 82 EV, 83 PP, 84 PP 2 | |
# DD - Scoring player 00-18 | |
# EE - Assist 1 (FF if none) | |
# FF - Assist 2 (FF if none) | |
private function getScoringSummary($state) { | |
$summaries = array(); | |
# 100 possible goals | |
for ($i = 0; $i < 100; $i++) { | |
fseek($state, self::SCORING_SUMMARY + ($i * 6)); | |
$summaryTP = $this->getSummaryTimeAndPeriod($state); | |
$time = $summaryTP['time']; | |
$period = $summaryTP['period']; | |
$status = $this->getPart($state); | |
$scorer = $this->getPart($state); | |
$assistant1 = $this->getPart($state); | |
$assistant2 = $this->getPart($state); | |
$team = 'home'; | |
# No more scores? | |
if ($time == 0 && $period == 0 && $i > 0) { | |
break; | |
} | |
if ($status >= 16) { | |
$team = 'visitor'; | |
$status -= 128; # to set it back to 0 | |
} | |
$summaries[] = array( | |
"time" => $time, | |
"period" => $this->periodMap[$period], | |
"status" => $status, | |
"scorer" => $scorer, | |
"assistant1" => $assistant1, | |
"assistant2" => $assistant2, | |
"team" => $team | |
); | |
} | |
return $summaries; | |
} | |
/* | |
* Grab the Time and Period | |
* they're in a goofy order | |
* | |
* @return array(time, period) | |
*/ | |
private function getSummaryTimeAndPeriod($state) { | |
# This part is weird, you have to grab 2 in a row, then move them around. | |
$time = bin2hex(fread($state, 1)); | |
$period = bin2hex(fread($state, 1)); | |
return array( | |
'time' => hexdec(substr($period, -1) . $time), | |
'period' => hexdec(substr($period, 0)) | |
); | |
} | |
private function getPart($state) { | |
return hexdec(bin2hex(fread($state, 1))); | |
} | |
/** | |
* Grab the stats after they're parsed. | |
* | |
* @return array | |
*/ | |
public function getStats() { | |
return $this->stats; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment