Skip to content

Instantly share code, notes, and snippets.

@halfmanhalfdonut
Last active December 16, 2015 14:29
Show Gist options
  • Save halfmanhalfdonut/5448690 to your computer and use it in GitHub Desktop.
Save halfmanhalfdonut/5448690 to your computer and use it in GitHub Desktop.
NHL94 Stat Extractor (ZSnes)
<?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