<?php

error_reporting(E_ALL);

abstract class Question
{
	protected $text;
	protected $points;
	protected $correctAnswer;
	protected $hint;

	abstract function getAsString(): string;
	abstract function checkAnswer($answer): array;

	// Возвращает вопрос
	public function getQuestionText(): string
	{
		$text = $this->text;
		return $text;
	}

	// Возвращает количество баллов за правильный ответ
	public function getPoints(): int
	{
		$points = $this->points;
		return $points;
	}

	// Возвращает подсказку
	public function getQuestionHint(): string
	{
		$hint = $this->hint;
		return $hint;
	}
}

class MultipleChoiceQuestion extends Question
{
    protected $answers;
    protected $almostCorrectAnswer;

	public function __construct(string $text, float $points, array $answers, string $correctAnswer, string $hint)
	{
		$this->text = $text;
		$this->points = $points;
		$this->answers = $answers;
		$this->correctAnswer = $correctAnswer;
		$this->hint = $hint;
	}

	// Функция добавляющая почти корректный ответ
	public function addAlmostCorrectAnswer($almostCorrectAnswer)
	{
		$this->almostCorrectAnswer = $almostCorrectAnswer;
	}

	// Функция отдающая строку со списком вопросов
	public function getAsString(): string
	{
		$string = "{$this->text}\nВарианты ответов:\n";

		foreach ($this->answers as $letter => $answer) {
			$string = $string."{$letter}. {$answer}\n";
		}

		return $string;
	}

	/* Функция получает на вход ответ. 
	Проверяет его, учитывая почти правильный вариант ответа. 
	Возвращает в массиве результаты проверки */

	public function checkAnswer($answer): array
	{
		$isCorrect = 0;
		$isAlmostCorrect = 0;

		if ($answer == $this->correctAnswer) {
			$isCorrect = 1;
		} elseif ($answer == $this->almostCorrectAnswer) {
			$isAlmostCorrect = 1;
		}

		return array($isCorrect, $isAlmostCorrect);
	}

}

class NumericalQuestion extends Question
{
	protected $deviation;

	function __construct(string $text, string $hint, float $points, float $correctAnswer, float $deviation = 0)
	{
		$this->text = $text;
		$this->points = $points;
		$this->correctAnswer = $correctAnswer;
		$this->deviation = $deviation;
		$this->hint = $hint;
	}

	// Функция возвращает строку со списком вопросов
	public function getAsString(): string
	{
		return "{$this->text}\n";
	}

	/* Функция получает ответ. Проверяет его с учетом возможного отклонения.
	Возвращает в массиве результат проверки. */

	public function checkAnswer($answer): array
	{
		$isCorrect = 0;

		if ($answer == $this->correctAnswer or $this->deviation > abs($this->correctAnswer - $answer)) {
			$isCorrect = 1;
		}

        return array($isCorrect, $isAlmostCorrect = 0);
	}

}

// Функция создающая массив с вопросами
function createQuestions() {

	$questions = [];

	$text = 'Какая планета располагается четвертой по счету от Солнца?';
	$answers = array('a' => 'Венера', 'b' => 'Марс', 'c' => 'Юпитер', 'd' => 'Меркурий');
	$hint = 'Одноименное название носит шоколадный батончик.';
	$q = new MultipleChoiceQuestion($text, 10, $answers, 'b', $hint);

	$questions[] = $q;

	$text = 'Какой город является столицей Великобритании?';
	$answers = array('a' => 'Париж', 'b' => 'Москва', 'c' => 'Нью-Йорк', 'd' => 'Лондон');
	$hint = '%Городнейм% из кэпитал оф грейт британ.';
	$q = new MultipleChoiceQuestion($text, 5, $answers, 'd', $hint);

	$questions[] = $q;

	$text = 'Кто придумал теорию относительности?';
	$answers = array('a' => 'Джон Леннон', 'b' => 'Джим Моррисон', 'c' => 'Альберт Эйнштейн', 'd' => 'Исаак Ньютон');
	$hint = 'Этим парнем был...';
	$q = new MultipleChoiceQuestion($text, 30, $answers, 'c', $hint);
	$q->addAlmostCorrectAnswer('b');

	$questions[] = $q;

	$hint = 'Попробуй еще раз, дружок-пирожок!';
	$q = new NumericalQuestion('Чему равна скорость света в км/с?', $hint, 15, 299792, 210);

	$questions[] = $q;

	$hint = 'Между 2 и 4, подумой.';
	$q = new NumericalQuestion('Чему равно число Пи?', $hint, 30, 3.14, 0.01);

	$questions[] = $q;

	$hint = 'Сычёв, ты отчислен!';
	$q = new NumericalQuestion('В каком году закончилась вторая мировая война?', $hint, 10, 1945);

	$questions[] = $q;

	return $questions;
}

// Функция выводящая список вопросов с вариантами ответов
function printQuestions($questions) {

	$number = 1;

	foreach ($questions as $question) {
		echo "\n{$number}. ";
		echo $question->getAsString();

		$number ++;
	}

}

/* Функция получает массивы вопросов и ответов. Проверяет ответы, 
считает число баллов и печатает вопросы на которые был дан неправильный ответ. */

function checkAnswers($questions, $answers)
{
    if (count($questions) != count($answers)) {
        die("Число ответов и вопросов не совпадает\n");
    }

    $pointsTotal = 0;
    $pointsMax = 0;
    $correctAnswers = 0;

    $totalQuestions = count($questions);

    for ($i = 0; $i < count($questions); $i++) {

        $question = $questions[$i];
        $answer = $answers[$i];

        list($isCorrect, $isAlmostCorrect) = $question->checkAnswer($answer);
        $points = $question->getPoints();

        $pointsMax += $points;

        if ($isCorrect) {

            $correctAnswers ++;
            $pointsTotal += $points;

        } elseif ($isAlmostCorrect) {

        	$correctAnswers ++;
            $pointsTotal += $points / 2;

        } else {

            $number = $i + 1;
            echo "\nНеправильный ответ на вопрос №{$number} ({$question->getQuestionText()})\n";
			echo "\nПодсказка: {$question->getQuestionHint()}\n";

        }

    }

    echo "\nПравильных ответов: {$correctAnswers} из {$totalQuestions}, баллов набрано: $pointsTotal из $pointsMax\n";
}

$questions = createQuestions();
printQuestions($questions);
checkAnswers($questions, array('b', 'd', 'b', 299792, 3.14444, 1944));