"Bad Request", 500 => "Internal Server Error"); if (!isset($codes[$code])) { return; } $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); header($protocol . ' ' . $code . ' ' . $codes[$code]); } } function safe_ceil($value, $precision = 1e-6) { $ceilValue = ceil($value); return (abs($value - $ceilValue) < (1-$precision)) ? $ceilValue : round($value); } function recursive_ksort(&$array, $flags = SORT_REGULAR) { if (!is_array($array)) return false; ksort($array, $flags); foreach ($array as &$arr) { recursive_ksort($arr, $flags); } return true; } function is_integer_like($value) { return ctype_digit($value) || is_int($value); } class ParametersException extends Exception {}; function run($parameters) { $versionClasses = array( '1' => 'ApiPklV1', // RegKlas 2018.11.01 '2' => 'ApiPklV2', // RegKMP 2020.01.01 '3' => 'ApiPklV3', // local BNET 2020.05.01 '_default' => 'ApiPklV3' ); $version = isset($parameters['version']) ? $parameters['version'] : '_default'; $apiClass = isset($versionClasses[$version]) ? $versionClasses[$version] : $versionClasses['_default']; $api = new $apiClass($parameters); if (isset($parameters['tournament_rank']) && $parameters['tournament_rank'] == ApiPkl::RANK_KMP) { $result = $api->calculate_kmp_points(); } else if (isset($parameters['tournament_rank']) && $parameters['tournament_rank'] == ApiPkl::RANK_BNET) { $result = $api->calculate_bridgenet_points(); } else { $result = $api->calculate_points(); } return $result; } class ApiPkl { const RANK_KMP = 101; const RANK_BNET = 102; protected $parameters; function __construct($parameters) { $this->parameters = $parameters; $this->check_parameters($parameters); $this->parameters = $this->parse_parameters($parameters); } function ensure_parameters($params) { foreach ($params as $param) { if (!isset($this->parameters[$param])) { throw new ParametersException('Missing parameter: ' . $param); } if (!is_numeric($this->parameters[$param])) { throw new ParametersException('Parameter: ' . $param . ' is not a numeric value (' . $this->parameters[$param] . ')'); } } } function check_values($parameters, $params) { foreach ($params as $param => $test) { if (!$test($parameters[$param])) { throw new ParametersException('Parameter: ' . $param . ' has incorrect value (' . $parameters[$param] . ')'); } } } function check_parameters() { $this->ensure_parameters(array('type', 'contestants')); $this->check_values($this->parameters, array( 'type' => function($r) { return is_integer_like($r) && intval($r) > 0; }, 'contestants' => function($r) { return is_integer_like($r) && intval($r) > 0; } )); if (isset($this->parameters['players'])) { $this->check_values($this->parameters, array( 'players' => function($r) { return is_integer_like($r) && intval($r) > 0; } )); } if (!isset($this->parameters['manual']) || !isset($this->parameters['manual']['min_points'])) { $this->ensure_parameters(array('title_sum')); $this->check_values($this->parameters, array( 'title_sum' => function($r) { return floatval($r) >= 0; } )); if (!isset($this->parameters['manual']) || !isset($this->parameters['manual']['tournament_weight'])) { $this->ensure_parameters(array('tournament_rank', 'over39_boards')); $this->check_values($this->parameters, array( 'tournament_rank' => function($r) { return is_integer_like($r) && ((intval($r) >= 0 && intval($r) <= 7) || in_array(intval($r), array(ApiPkl::RANK_KMP, ApiPkl::RANK_BNET))); }, 'over39_boards' => function($r) { return is_integer_like($r) && intval($r) >= 0 && intval($r) <= 1; } )); } else { $this->check_values($this->parameters['manual'], array( 'tournament_weight' => function($r) { return is_integer_like($r) && intval($r) > 0; } )); } } else { $this->check_values($this->parameters['manual'], array( 'min_points' => function($r) { return is_integer_like($r) && intval($r) >= 0; } )); } if (isset($this->parameters['manual']) && isset($this->parameters['manual']['players_coefficient'])) { $this->check_values($this->parameters['manual'], array( 'players_coefficient' => function($r) { return floatval($r) >= 0; } )); } } function parse_parameters() { $return = array(); $return['type'] = intval($this->parameters['type']); if ($return['type'] == 3 || $return['type'] > 4) { throw new ParametersException('Parameter: type has incorrect value (' . $return['type'] . ')'); } if ($return['type'] != 2 && isset($this->parameters['tournament_rank']) && $this->parameters['tournament_rank'] == ApiPkl::RANK_KMP) { throw new ParametersException('Parameter: type has incorrect value (' . $return['type'] . ') for KMP tournament'); } if (isset($this->parameters['boards'])) { $return['boards'] = intval($this->parameters['boards']); } $return['contestants'] = intval($this->parameters['contestants']); $return['players'] = isset($this->parameters['players']) ? intval($this->parameters['players']) : intval($this->parameters['contestants']) * $return['type']; $return['title_sum'] = isset($this->parameters['title_sum']) ? floatval($this->parameters['title_sum']) : 0.0; $weights = array( array(1, 2, 4, 5, 7, 10, 15, 25, ApiPkl::RANK_KMP => 0, ApiPkl::RANK_BNET => 1), array(2, 3, 5, 7, 10, 15, 25, 40, ApiPkl::RANK_KMP => 0, ApiPkl::RANK_BNET => 1) // not 2, according to MarcinW ); $return['tournament_weight'] = 0; if (isset($this->parameters['manual']) && isset($this->parameters['manual']['tournament_weight'])) { $return['tournament_weight'] = intval($this->parameters['manual']['tournament_weight']); } else { if (isset($this->parameters['over39_boards']) && isset($this->parameters['tournament_rank'])) { $return['tournament_weight'] = $weights[intval($this->parameters['over39_boards'])][intval($this->parameters['tournament_rank'])]; } } $min_points = array( array(0, 0, 0, 0, 50, 75, 150, 200, ApiPkl::RANK_KMP => 0, ApiPkl::RANK_BNET => 0), array(0, 0, 0, 0, 70, 100, 200, 300, ApiPkl::RANK_KMP => 0, ApiPkl::RANK_BNET => 0) ); $return['min_points'] = (isset($this->parameters['manual']) && isset($this->parameters['manual']['min_points'])) ? intval($this->parameters['manual']['min_points']) : $min_points[intval($this->parameters['over39_boards'])][intval($this->parameters['tournament_rank'])]; $return['players_coefficient'] = (isset($this->parameters['manual']) && isset($this->parameters['manual']['players_coefficient'])) ? floatval($this->parameters['manual']['players_coefficient']) : 0.05; $return['points_cutoffs'] = (isset($this->parameters['manual']) && isset($this->parameters['manual']['points_cutoffs']) && is_array($this->parameters['manual']['points_cutoffs'])) ? $this->parameters['manual']['points_cutoffs'] : array( array(0.0, 1.0), array(0.02, 0.9), array(0.2, 0.2), array(0.5, 0.0) ); recursive_ksort($return['points_cutoffs']); if ($return['points_cutoffs'][0][0] != 0.0) { array_unshift($return['points_cutoffs'], array(0.0, 1.0)); } if ($return['points_cutoffs'][count($return['points_cutoffs'])-1][1] != 0.0) { array_push($return['points_cutoffs'], array(1.0, 0.0)); } foreach ($return['points_cutoffs'] as &$cutoff) { if (($cutoff[0] < 0.0) || ($cutoff[0] > 1.0)) { throw new ParametersException('Cutoff points need to be between 0.0 and 1.0: ' . $cutoff[0]); } $cutoff[0] = floatval($cutoff[0]); if (($cutoff[1] < 0.0) || ($cutoff[1] > 1.0)) { throw new ParametersException('Cutoff values need to be between 1.0 and 0.0: ' . $cutoff[1]); } $cutoff[1] = floatval($cutoff[1]); } unset($cutoff); for ($prev = 0; $prev < count($return['points_cutoffs']) - 1; $prev++) { $next = $prev + 1; if ($return['points_cutoffs'][$prev][0] >= $return['points_cutoffs'][$next][0]) { throw new ParametersException( 'Cutoff points need to be ascending: ' . $return['points_cutoffs'][$prev][0] . ', ' . $return['points_cutoffs'][$next][0]); } if ($return['points_cutoffs'][$prev][1] < $return['points_cutoffs'][$next][1]) { throw new ParametersException( 'Cutoff values need to be non-ascending: ' . $return['points_cutoffs'][$prev][1] . ', ' . $return['points_cutoffs'][$next][1]); } } return $return; } function get_position_percentage_from_position($position, $contestants) { return ($position - 1) / $contestants; } function get_percentage_from_position($position, $contestants, $cutoffs) { $position_percentage = $this->get_position_percentage_from_position($position, $contestants); for ($prev = 0; $prev < count($cutoffs) - 1; $prev++) { $next = $prev + 1; if (($cutoffs[$prev][0] <= $position_percentage) && ($cutoffs[$next][0] >= $position_percentage)) { $result = ($position_percentage - $cutoffs[$prev][0]) * ($cutoffs[$prev][1] - $cutoffs[$next][1]) / ($cutoffs[$prev][0] - $cutoffs[$next][0]) + $cutoffs[$prev][1]; return $result; } } return 0.0; } function calculate_points($min_points=1, $scale_factor=1.0) { $max_points = safe_ceil(max( $this->parameters['min_points'], (1 + 0.25 * ($this->parameters['type'] > 2)) * (max(0.15, $this->parameters['title_sum'] / $this->parameters['players']) * $this->parameters['tournament_weight'] + $this->parameters['players_coefficient'] * $this->parameters['contestants'] * $this->parameters['type']) )); $result = array("sum" => 0, "points" => array()); for ($place = 1; $place <= $this->parameters['contestants']; $place++) { $percentage = $this->get_percentage_from_position($place, $this->parameters['contestants'], $this->parameters['points_cutoffs']); $points = safe_ceil(floatval($max_points) * $percentage * $scale_factor); $points = max($min_points, intval($points)); if ($points > 0) { $result['points'][$place] = $points; $result['sum'] += $this->parameters['type'] * $result['points'][$place]; } } return $result; } function calculate_kmp_points() { $max_points = safe_ceil($this->parameters['contestants'] * 0.5); $min_points = 1; $result = array("sum" => 0, "points" => array(1 => $max_points)); for ($place = 2; $place <= $this->parameters['contestants']; $place++) { $result['points'][$place] = max($min_points, $result['points'][$place-1]-1); $result['sum'] += $this->parameters['type'] * $result['points'][$place]; } if ($this->parameters['title_sum'] / $this->parameters['players'] >= 2.5) { $result['points'][1] += 5; $result['points'][2] += 3; $result['points'][3] += 1; $result['sum'] += $this->parameters['type'] * 8; } return $result; } function calculate_bridgenet_points() { throw new ParametersException('BridgeNET points not supported in this API version'); } } class ApiPklV1 extends ApiPkl {} class ApiPklV2 extends ApiPklV1 { function calculate_kmp_points() { // the line below works only because you can't play > 39 boards in KMP // please kill me if it ever changes $this->parameters['tournament_weight'] = 4; $this->parameters['min_points'] = 0; $intermediate = $this->calculate_points(); $this->parameters['min_points'] = $intermediate['points'][1] + 10; return $this->calculate_points(); } } class ApiPklV3 extends ApiPklV2 { function check_parameters() { if (isset($this->parameters['tournament_rank']) && $this->parameters['tournament_rank'] == ApiPkl::RANK_BNET) { $this->ensure_parameters(array('boards', 'type')); if ($this->parameters['type'] == 4) { throw new ParametersException('Parameter: type has incorrect value (' . $this->parameters['type'] . ') for BridgeNET tournament'); } if (isset($this->parameters['manual'])) { unset($this->parameters['manual']); } } if (isset($this->parameters['boards'])) { $this->check_values($this->parameters, array( 'boards' => function($r) { return is_integer_like($r); } )); $this->parameters['over39_boards'] = strval(intval($this->parameters['boards'] > 39)); } return parent::check_parameters(); } function calculate_bridgenet_points() { $factor = min($this->parameters['boards'] / 27.0, 1.0); return $this->calculate_points(0, $factor); } } ?>