/**
  * Gets the average number of runs conceded per over
  * @param int $overs
  * @param int $runs
  * @return float
  */
 public static function BowlingEconomy($overs, $runs)
 {
     if ($overs > 0 and !is_null($runs)) {
         $overs_in_decimal = StoolballStatistics::OversToBalls($overs) / 8;
         return round($runs / $overs_in_decimal, 2);
     } else {
         return null;
     }
 }
 private function BuildMatchBowlingAverageData(array $bowling)
 {
     $data = "";
     $total_performances = count($bowling);
     $show_every_second_performance = $total_performances > $this->max_x_axis_scale_points;
     for ($i = 0; $i < $total_performances; $i++) {
         $performance = $bowling[$i];
         /* @var $performance Bowling */
         # Sample the performances plotted to prevent the scale getting too cluttered
         if (!$show_every_second_performance or $i % 2 === 0) {
             if (strlen($data)) {
                 $data .= ",";
             }
             if (is_null($performance->GetRunsConceded()) or $performance->GetWickets() === 0) {
                 $data .= "null";
             } else {
                 $data .= StoolballStatistics::BowlingAverage($performance->GetRunsConceded(), $performance->GetWickets());
             }
         }
     }
     return $data;
 }
 /**
  * If recently-added batting/bowling records are not reflected in statistics, run this method
  * @return void
  */
 public function RecalculateStatistics()
 {
     # Gather batting stats
     $this->total_innings = 0;
     $this->all_scores = array();
     $this->scores_out = array();
     $this->scores_not_out = array();
     $this->scores_with_balls_faced = array();
     $this->balls_faced = array();
     $this->batting_best = null;
     $this->batting_average = null;
     $this->batting_strike_rate = null;
     $this->batting_50 = 0;
     $this->batting_100 = 0;
     $this->how_out = array();
     foreach ($this->batting as $batting) {
         /* @var $batting Batting */
         if ($batting->GetHowOut() != Batting::DID_NOT_BAT) {
             $this->total_innings++;
         }
         if (!array_key_exists($batting->GetHowOut(), $this->how_out)) {
             $this->how_out[$batting->GetHowOut()] = 0;
         }
         $this->how_out[$batting->GetHowOut()]++;
         if (!is_null($batting->GetRuns())) {
             $this->all_scores[] = $batting->GetRuns();
             if ($this->IsNotOut($batting->GetHowOut())) {
                 $this->scores_not_out[] = $batting->GetRuns();
             } else {
                 $this->scores_out[] = $batting->GetRuns();
             }
             if ($batting->GetRuns() >= 50) {
                 $this->batting_50++;
             }
             if ($batting->GetRuns() >= 100) {
                 $this->batting_100++;
             }
             if (is_null($this->batting_best) or $batting->GetRuns() > intval($this->batting_best) or $batting->GetRuns() == intval($this->batting_best) && $this->IsNotOut($batting->GetHowOut())) {
                 $this->batting_best = $batting->GetRuns();
                 if ($this->IsNotOut($batting->GetHowOut())) {
                     $this->batting_best .= "*";
                 }
             }
             if (!is_null($batting->GetBallsFaced())) {
                 $this->scores_with_balls_faced[] = $batting->GetRuns();
                 $this->balls_faced[] = $batting->GetBallsFaced();
             }
         }
     }
     $this->not_outs = array_key_exists(Batting::NOT_OUT, $this->how_out) ? $this->how_out[Batting::NOT_OUT] : 0;
     if (count($this->all_scores)) {
         # retired is not out for purpose of average
         $retired = array_key_exists(Batting::RETIRED, $this->how_out) ? $this->how_out[Batting::RETIRED] : 0;
         $retired_hurt = array_key_exists(Batting::RETIRED_HURT, $this->how_out) ? $this->how_out[Batting::RETIRED_HURT] : 0;
         $total_innings_for_average = $this->total_innings - $this->not_outs - $retired - $retired_hurt;
         if ($total_innings_for_average > 0) {
             $this->batting_average = round(array_sum($this->all_scores) / $total_innings_for_average, 2);
         }
         # Work out the ranges scores are in
         $this->WorkOutScoreSpread();
     }
     if (count($this->scores_with_balls_faced)) {
         $total_balls_faced = array_sum($this->balls_faced);
         if ($total_balls_faced) {
             $this->batting_strike_rate = round(array_sum($this->scores_with_balls_faced) / $total_balls_faced * 100, 2);
         }
     }
     # Gather bowling stats
     $this->bowling_innings = 0;
     $this->balls = null;
     $this->overs = null;
     $this->maidens = null;
     $this->runs_against = null;
     $this->wickets_taken = 0;
     $this->wickets_taken_for_analysis = 0;
     $this->bowling_economy = null;
     $this->bowling_average = null;
     $this->bowling_strike_rate = null;
     $this->bowling_best = null;
     $best_wickets = 0;
     $best_runs = 0;
     $this->bowling_5 = 0;
     foreach ($this->bowling as $bowling) {
         /* @var $bowling Bowling */
         $this->bowling_innings += 1;
         if (!is_null($bowling->GetOvers())) {
             # Initialise balls, since we now know there are some recorded
             if (is_null($this->balls)) {
                 $this->balls = 0;
             }
             # Work out how many balls were bowled - have to assume 8 ball overs, even though on rare occasions that may not be the case
             $this->balls += StoolballStatistics::OversToBalls($bowling->GetOvers());
         }
         if (!is_null($bowling->GetMaidens())) {
             # Initialise maidens now we know the stat has been recorded
             if (is_null($this->maidens)) {
                 $this->maidens = 0;
             }
             # Add these maidens to total bowled
             $this->maidens += $bowling->GetMaidens();
         }
         if (!is_null($bowling->GetRunsConceded())) {
             # Initialise runs, since we now know there are some recorded
             if (is_null($this->runs_against)) {
                 $this->runs_against = 0;
             }
             # Add these runs to the total
             $this->runs_against += $bowling->GetRunsConceded();
         }
         if (!is_null($bowling->GetWickets())) {
             $this->wickets_taken += $bowling->GetWickets();
             if ($bowling->GetWickets() >= 5) {
                 $this->bowling_5++;
             }
             # Record wickets separately for average and strike rate, because otherwise wickets recorded on batting card
             # with no overs recorded on bowling card distort those statistics.
             if (!is_null($bowling->GetRunsConceded())) {
                 $this->wickets_taken_for_average += $bowling->GetWickets();
             }
             if (!is_null($bowling->GetOvers())) {
                 $this->wickets_taken_for_strike_rate += $bowling->GetWickets();
             }
         }
         if (!is_null($bowling->GetWickets())) {
             if ($bowling->GetWickets() > $best_wickets or $bowling->GetWickets() == $best_wickets and (!is_null($bowling->GetRunsConceded()) and (is_null($best_runs) or $bowling->GetRunsConceded() < $best_runs))) {
                 $best_wickets = $bowling->GetWickets();
                 $best_runs = $bowling->GetRunsConceded();
             }
         }
     }
     if (!is_null($this->balls) and $this->balls > 0 and !is_null($this->runs_against)) {
         $this->overs = StoolballStatistics::BallsToOvers($this->balls);
         $this->bowling_economy = StoolballStatistics::BowlingEconomy($this->overs, $this->runs_against);
     }
     if ($this->wickets_taken > 0) {
         $this->bowling_best = "{$best_wickets}/{$best_runs}";
     }
     if ($this->wickets_taken_for_average > 0 and !is_null($this->runs_against)) {
         $this->bowling_average = StoolballStatistics::BowlingAverage($this->runs_against, $this->wickets_taken_for_average);
     }
     if ($this->wickets_taken_for_strike_rate > 0 and $this->balls > 0) {
         $this->bowling_strike_rate = StoolballStatistics::BowlingStrikeRate($this->overs, $this->wickets_taken_for_strike_rate);
     }
 }
 /**
  * Creates a bowling scorecard for one innings with as much data as is available
  * @param Match $match
  * @param bool $is_home_innings
  * @return void
  */
 private function CreateBowlingCard(Match $match, $is_home_innings)
 {
     $overs_data = $is_home_innings ? $match->Result()->AwayOvers() : $match->Result()->HomeOvers();
     $bowling_data = $is_home_innings ? $match->Result()->AwayBowling() : $match->Result()->HomeBowling();
     $bowling_team = $is_home_innings ? $match->GetAwayTeam() : $match->GetHomeTeam();
     # First, bowler's figures
     if ($bowling_data->GetCount()) {
         $bowling_table = new XhtmlTable();
         $bowling_table->SetCaption(htmlentities($bowling_team->GetName() . "'s bowlers", ENT_QUOTES, "UTF-8", false));
         $bowling_table->SetCssClass("bowling scorecard");
         $over_header = new XhtmlCell(true, '<abbr title="Overs" class="small">Ov</abbr><span class="large">Overs</span>');
         $over_header->SetCssClass("numeric");
         $maidens_header = new XhtmlCell(true, '<abbr title="Maiden overs" class="small">Md</abbr><abbr title="Maiden overs" class="large">Mdns</abbr>');
         $maidens_header->SetCssClass("numeric");
         $runs_header = new XhtmlCell(true, "Runs");
         $runs_header->SetCssClass("numeric");
         $wickets_header = new XhtmlCell(true, '<abbr title="Wickets" class="small">Wk</abbr><abbr title="Wickets" class="large">Wkts</abbr>');
         $wickets_header->SetCssClass("numeric");
         $economy_header = new XhtmlCell(true, '<abbr title="Economy" class="small">Econ</abbr><span class="large">Economy</span>');
         $economy_header->SetCssClass("numeric");
         $average_header = new XhtmlCell(true, '<abbr title="Average" class="small">Avg</abbr><span class="large">Average</span>');
         $average_header->SetCssClass("numeric");
         $strike_header = new XhtmlCell(true, '<abbr title="Strike rate" class="small">S/R</abbr><span class="large">Strike rate</span>');
         $strike_header->SetCssClass("numeric");
         $bowling_headings = new XhtmlRow(array("Bowler", $over_header, $maidens_header, $runs_header, $wickets_header, $economy_header, $average_header, $strike_header));
         $bowling_headings->SetIsHeader(true);
         $bowling_table->AddRow($bowling_headings);
         $bowling_data->ResetCounter();
         while ($bowling_data->MoveNext()) {
             $bowling = $bowling_data->GetItem();
             /* @var $bowling Bowling */
             $player = new XhtmlCell(false, '<a property="schema:name" rel="schema:url" href="' . htmlentities($bowling->GetPlayer()->GetPlayerUrl(), ENT_QUOTES, "UTF-8", false) . '">' . htmlentities($bowling->GetPlayer()->GetName(), ENT_QUOTES, "UTF-8", false) . '</a>');
             $player->AddCssClass("bowler");
             $player->AddAttribute("typeof", "schema:Person");
             $player->AddAttribute("about", $bowling->GetPlayer()->GetLinkedDataUri());
             $over_data = new XhtmlCell(false, htmlentities($bowling->GetOvers(), ENT_QUOTES, "UTF-8", false));
             $over_data->SetCssClass("numeric");
             $maidens_data = new XhtmlCell(false, htmlentities($bowling->GetMaidens(), ENT_QUOTES, "UTF-8", false));
             $maidens_data->SetCssClass("numeric");
             $runs_data = new XhtmlCell(false, htmlentities($bowling->GetRunsConceded(), ENT_QUOTES, "UTF-8", false));
             $runs_data->SetCssClass("numeric");
             $wickets_data = new XhtmlCell(false, htmlentities($bowling->GetWickets(), ENT_QUOTES, "UTF-8", false));
             $wickets_data->SetCssClass("numeric");
             $economy = StoolballStatistics::BowlingEconomy($bowling->GetOvers(), $bowling->GetRunsConceded());
             $economy_data = new XhtmlCell(false, is_null($economy) ? "&#8211;" : htmlentities($economy, ENT_QUOTES, "UTF-8", false));
             $economy_data->SetCssClass("numeric");
             $average = StoolballStatistics::BowlingAverage($bowling->GetRunsConceded(), $bowling->GetWickets());
             $average_data = new XhtmlCell(false, is_null($average) ? "&#8211;" : htmlentities($average, ENT_QUOTES, "UTF-8", false));
             $average_data->SetCssClass("numeric");
             $strike = StoolballStatistics::BowlingStrikeRate($bowling->GetOvers(), $bowling->GetWickets());
             $strike_data = new XhtmlCell(false, is_null($strike) ? "&#8211;" : htmlentities($strike, ENT_QUOTES, "UTF-8", false));
             $strike_data->SetCssClass("numeric");
             $bowling_row = new XhtmlRow(array($player, $over_data, $maidens_data, $runs_data, $wickets_data, $economy_data, $average_data, $strike_data));
             $bowling_row->GetFirstCell()->SetCssClass("bowler");
             $bowling_table->AddRow($bowling_row);
         }
         $this->AddControl($bowling_table);
     }
     # Now over-by-over
     if ($overs_data->GetCount()) {
         $overs_table = new XhtmlTable();
         $overs_table->SetCaption(htmlentities($bowling_team->GetName() . "'s bowling, over-by-over", ENT_QUOTES, "UTF-8", false));
         $overs_table->SetCssClass("scorecard bowling-scorecard bowling overs");
         $balls_header = new XhtmlCell(true, "Balls");
         $balls_header->SetCssClass("numeric");
         $wides_header = new XhtmlCell(true, "Wides");
         $wides_header->SetCssClass("numeric");
         $no_balls_header = new XhtmlCell(true, "No balls");
         $no_balls_header->SetCssClass("numeric");
         $runs_in_over_header = new XhtmlCell(true, "Runs");
         $runs_in_over_header->SetCssClass("numeric");
         $total_header = new XhtmlCell(true, "Total");
         $total_header->SetCssClass("numeric");
         $overs_headings = new XhtmlRow(array("Bowler", $balls_header, $wides_header, $no_balls_header, $runs_in_over_header, $total_header));
         $overs_headings->SetIsHeader(true);
         $overs_table->AddRow($overs_headings);
         $overs_data->ResetCounter();
         $total = 0;
         while ($overs_data->MoveNext()) {
             $bowling = $overs_data->GetItem();
             /* @var $bowling Over */
             $player = new XhtmlCell(false, '<a property="schema:name" rel="schema:url" href="' . htmlentities($bowling->GetPlayer()->GetPlayerUrl(), ENT_QUOTES, "UTF-8", false) . '">' . htmlentities($bowling->GetPlayer()->GetName(), ENT_QUOTES, "UTF-8", false) . '</a>');
             $player->AddCssClass("bowler");
             $player->AddAttribute("typeof", "schema:Person");
             $player->AddAttribute("about", $bowling->GetPlayer()->GetLinkedDataUri());
             $balls_data = new XhtmlCell(false, htmlentities($bowling->GetBalls(), ENT_QUOTES, "UTF-8", false));
             $balls_data->SetCssClass("numeric");
             $wides_data = new XhtmlCell(false, htmlentities($bowling->GetWides(), ENT_QUOTES, "UTF-8", false));
             $wides_data->SetCssClass("numeric");
             $no_balls_data = new XhtmlCell(false, htmlentities($bowling->GetNoBalls(), ENT_QUOTES, "UTF-8", false));
             $no_balls_data->SetCssClass("numeric");
             $runs_in_over_data = new XhtmlCell(false, htmlentities($bowling->GetRunsInOver(), ENT_QUOTES, "UTF-8", false));
             $runs_in_over_data->SetCssClass("numeric");
             $total += $bowling->GetRunsInOver();
             $total_data = new XhtmlCell(false, htmlentities($total, ENT_QUOTES, "UTF-8", false));
             $total_data->SetCssClass("numeric");
             $bowling_row = new XhtmlRow(array($player, $balls_data, $wides_data, $no_balls_data, $runs_in_over_data, $total_data));
             $bowling_row->GetFirstCell()->SetCssClass("bowler");
             $overs_table->AddRow($bowling_row);
         }
         $this->AddControl($overs_table);
     }
 }