<?php
header("Content-Type: text/plain");
mb_internal_encoding('utf-8');

//  ООП. Кошки - мышки

// Константы
const cat = 'Cat';
const mouse = 'Mouse';

// Класс Животного (игрока)
abstract class Animal
{
    // Координаты ХY
    protected $xy = array();
    
    // Кол-во пропускаемых ходов
    protected $restingTime = 0;
    
    // Hp
    protected $hp = 100;
    
    // Генератор животного
    public function __construct($x, $y)
    {
        $this->xy['x'] = $x;
        $this->xy['y'] = $y;
    }
    
    // Выбор наилучшего ВариантаХода (большего веса)
    public function chooseBestMoveVariant($moveVariants)
    {
        // Сортировка ВариантовХода по убыванию веса
        usort ($moveVariants,
            function ($moveVariantA, $moveVariantB ) {
                $weightA = $moveVariantA->getWeight();
                $weightB = $moveVariantB->getWeight();
                if ($weightA==$weightB) return 0;
                return ($weightA<$weightB) ? 1 : -1;                
            });
        return $moveVariants[0];
    }
    
    // Возвращает координаты
    public function getXY()
    {
        return $this->xy;
    }
    // Установка координат
    public function setXY($xy)
    {
        $this->xy['x'] = $xy['x'];
        $this->xy['y'] = $xy['y'];
    }
    // Возвращает время отдыха
    public function getRestingTime()
    {
        return $this->restingTime;
    }
     // Возвращает время отдыха
    public function setRestingTime($moveQuantity)
    {
        $this->restingTime = $moveQuantity;
    }
    // Возвращает время отдыха
    public function getHP()
    {
        return $this->hp;
    }
    // Игровой ход
    abstract function move($animals, $moveVariants);
      
    // Скорость перемещения (клеток за ход)
    abstract function getSpeed();
    
    // Возможность ходить по горизонт, вертикали, диагонали
    //abstract function getMoveAbility();
    
    // Область видимости вокруг себя
    abstract function getVisionLimits();
    
    // Оценка ходов
    abstract function setRating($animals, $moveVariants);
    
    // Исключение занятых ходов
    abstract function excludingNonEmptyMove($animals, $moveVariants);
    
    // Убийство
    public function kill ()
    {
        $this->hp = 0;
    }    
}

class Mouse extends Animal
{    
    // Скорость перемещения (клеток за ход)
    public function getSpeed()
    {
        return 1;
    }
    
    // Область видимости вокруг себя
    public function getVisionLimits()
    {
        return 4;
    }
    // Игровой ход
    public function move($animals, $moveVariants)
    {
        // Исключение занятых ходов
        $moveVariants = $this->excludingNonEmptyMove($animals, $moveVariants);
        // Исключения вариантов хода по вертикали
        $moveVariants = $this->exlusionVerticalMove($moveVariants);
        // Оценка ходов
        $this->setRating($animals, $moveVariants);
        // Выбор наилучшего хода (наибольший вес)
        $bestMoveVariant = $this->chooseBestMoveVariant($moveVariants);
        // Ход
        $this->setXY($bestMoveVariant->getXY());
        
    }
    
    // Оценка ходов
    public function setRating($animals, $moveVariants)
    {    
        // Присваиваем вес
        foreach ($moveVariants as $moveVariant)
        {
            $moveVariant->setWeight(rand(0,10));
        }
    }
    
    // Метод исключения вариантов хода по вертикали
    public function exlusionVerticalMove($moveVariants) {
        
        // У вертикальных ходов координаты X Y равны по модулю, относительно текщего положения животного
        foreach ($moveVariants as $key => $moveVariant) {            
            $moveVariantXY=$moveVariant->getXY();
            // Исключаем из проверки ход на котором стоим
            //echo "an x: {$this->xy['x']} y: {$this->xy['y']}\n";
            //echo "mv x: {$moveVariantXY['x']} y: {$moveVariantXY['y']}\n";
            if ($moveVariantXY['x'] == $this->xy['x'] &&
                $moveVariantXY['y'] == $this->xy['y']) continue;
            // Проверяем остальные
            if (abs($moveVariantXY['x']-$this->xy['x'])==abs($moveVariantXY['y']-$this->xy['y'])) {
                unset($moveVariants[$key]);
            }
        } 
        return $moveVariants;  
    }
    // Исключение занятых ходов
    public function excludingNonEmptyMove($animals, $moveVariants)
    {
        foreach ($moveVariants as $key => $moveVariant) {
            $moveVariantXY=$moveVariant->getXY();
            foreach ($animals as $animal) {
                $animalXY=$animal->getXY();
                // Исключаем из проверки ход на котором стоим
                if ($animalXY['x'] == $this->xy['x'] &&
                    $animalXY['y'] == $this->xy['y']) continue;
                // Проверяем остальные
                if ($moveVariantXY['x']==$animalXY['x'] &&
                    $moveVariantXY['y']==$animalXY['y']) {
                    unset ($moveVariants[$key]);
                }
            }
        }
        return $moveVariants;
    }
}
class Cat extends Animal
{    
    // Скорость перемещения (клеток за ход)
    public function getSpeed()
    {
        return 1;
    }
    
    // Возможность ходить по горизонт, вертикали, диагонали
    public function getMoveAbility()
    {
        return array(true, true, true);
    }
    // Область видимости вокруг себя
    public function getVisionLimits()
    {
        return null;
    }
    
    
    // Игровой ход
    public function move($animals, $moveVariants)
    {
        // Проверка кол-ва пропускаемых ходов
        if ($this->getRestingTime()>0 ) {
            // Уменьшаем счетчик пропускаемых ходов
            $this->setRestingTime($this->getRestingTime()-1);
            return;
        }
        if ($this->getRestingTime() > 0) 
        // Исключение занятых ходов
        $moveVariants = $this->excludingNonEmptyMove($animals, $moveVariants);
        // Оценка ходов
        $this->setRating($animals, $moveVariants);
        // Выбор наилучшего хода (наибольший вес)
        $bestMoveVariant = $this->chooseBestMoveVariant($moveVariants);
        // Проверка съедания мыши
        $eatenMouse = $this->checkEatenMouse($animals, $bestMoveVariant);
        if ($eatenMouse) $this->eatMouse($eatenMouse);
        // Ход
        $this->setXY($bestMoveVariant->getXY());

    }
    
    // Оценка ходов
    public function setRating($animals, $moveVariants)
    {
        // Находим всех мышей
        $mouses = array_filter($animals,
                    function ($animal) {
                        return (get_class($animal) == mouse);
                    });
        foreach ($moveVariants as $moveVariant)
        {
            $moveVariant->setWeight(rand(0,10));
            
            // Если в клетке мышка, то такой ход +10 баллов
            foreach ($mouses as $mouse) {
                $mouseXY = $mouse->getXY();
                $moveVariantXY = $moveVariant->getXY();              
                
                if ($mouseXY['x']==$moveVariantXY['x'] &&
                    $mouseXY['y']==$moveVariantXY['y']) {
                    $moveVariant->setWeight(100); //$moveVariant->getWeight() + 10
                }
            }            
        }
    }
    // Исключение занятых ходов
    public function excludingNonEmptyMove($animals, $moveVariants)
    {
        foreach ($moveVariants as $key => $moveVariant) {
            $moveVariantXY=$moveVariant->getXY();
            // Исключаем только кошек
            $cats = array_filter($animals,
                    function ($animal) {
                        return (get_class($animal) == cat);
                    });
            foreach ($cats as $cat) {
                $catXY=$cat->getXY();
                // Исключаем из проверки ход на котором стоим
                if ($catXY['x'] == $this->xy['x'] &&
                    $catXY['y'] == $this->xy['y']) continue;
                // Проверяем остальные 
                if ($moveVariantXY['x']==$catXY['x'] &&
                    $moveVariantXY['y']==$catXY['y']) {
                    unset ($moveVariants[$key]);
                }
            }
        }
        return $moveVariants;
    }
    // Проверка съедания мыши
    public function checkEatenMouse($animals, $bestMoveVariant)
    {
        $moveVariantXY=$bestMoveVariant->getXY();
        // Исключаем только кошек
        $mouses = array_filter($animals,
                    function ($animal) {
                        return (get_class($animal) == mouse);
                    });        
        foreach ($mouses as $mouse) {
            $mouseXY = $mouse->getXY();
            if ($mouseXY['x'] == $moveVariantXY['x'] &&
                $mouseXY['y'] == $moveVariantXY['y']) {
                return $mouse;
            }
        }
    }
    
    // Поедание мыши
    public function eatMouse ($mouse)
    {
        $mouse->kill();
        $this->setRestingTime(2);        
    }    
}

// Класс Животные (Игроки)
class Animals
{
    private $animals = array();
    
    public function __construct ($mousesQuantity, $catsQuantity)
    {
        // Создание мышей
        for ($mousesQuantity; $mousesQuantity > 0; $mousesQuantity--) {
            // Генераруем случайные координаты
            $xy = $this->newCoordinat();
            $this->animals[] = new Mouse($xy['x'], $xy['y']);
        }
        // Создание кошек
        for ($catsQuantity; $catsQuantity > 0; $catsQuantity--) {
            $xy = $this->newCoordinat();
            $this->animals[] = new Cat($xy['x'], $xy['y']);
        }
    }
    public function __clone()
    {
        $animalsClones = array();

        foreach ($this->animals as $animal) {
            $animalsClones[] = clone $animal;
        }
        // Заменяем массив животных массивом клонов
        $this->animals = $animalsClones;
    }
    
    // Запуск цикла ходов для всех животных
    public function move()
    {
        foreach ($this->animals as $animal) {
            // Генерация всех возможных ВариантовХода
            $moveVariants = fabricMoveVariants::createMoveVariants($animal);            
            $animal->move($this->animals, $moveVariants);
        }
        // Чистка убитых животных
        $this->cleanUpDeadAnimals();
    }
    
    // Убираем с поля убитых животных
    private function cleanUpDeadAnimals()
    {
        $liveAnimal = array_filter($this->animals,
                        function ($animal) {
                            return ($animal->getHp()>0);
                        });
        $this->animals = $liveAnimal;
    }
    
    // Функция генерации координат
    private function newCoordinat()
    {
        $xy = array();
        $xy['x'] = rand(1, fieldSize);
        $xy['y'] = rand(1, fieldSize);
        
        return  $xy;
    }
    // Уничтожение животного
    public function killAnimal($killingAnimal)
    {
        foreach ($animals as $key => $animal) {
            if ($animal === $killingAnimal) unset ($animals[$key]);
        }
    }
    
    public function getAnimalsAsArray ()
    {
        return $this->animals;
    }
}

// Фабрика Вариантов хода
class fabricMoveVariants {
    
    static public function createMoveVariants ($animal)
    {
        // Создание новых ВариантовХода
        $moveVariants = array();
        $animalXY = $animal->getXY();
        $animalSpeed = $animal->getSpeed();
        
        // Координаты начала (верхний левый угол)
        $animalSpeed = $animal->getSpeed();
        do {
            $start['x'] = $animalXY['x']-$animalSpeed;
            $animalSpeed--;
        } while ($start['x']<=0);
        
        $animalSpeed = $animal->getSpeed();
        do {
            $start['y'] = $animalXY['y']-$animalSpeed;
            $animalSpeed--;
        } while ($start['y']<=0);
        // Координаты конца (нижний правый угол)
        $animalSpeed = $animal->getSpeed();
        do {
            $end['x'] = $animalXY['x']+$animalSpeed;
            $animalSpeed--;
        } while ($end['x']>fieldSize);
        
        $animalSpeed = $animal->getSpeed();
        do {
            $end['y'] = $animalXY['y']+$animalSpeed;
            $animalSpeed--;
        } while ($end['y']>fieldSize);
        
        // Генерация ходов        
        $y = $start['y'];
        while ($y<=$end['y']) {
            $x = $start['x'];
            while ($x<=$end['x']) {
                $moveVariants[] = new MoveVariant($x, $y);    
                $x++;
            }
            $y++;
        }
        return $moveVariants;
    }
}

// Класс ВариантХода
class MoveVariant
{
    // Координаты
    private $xy = array();
    // Вес
    private $weight=0;
        
    public function __construct ($moveX, $moveY)
    {
        $this->xy['x'] = $moveX;
        $this->xy['y'] = $moveY;
    }
    
    // Возвращает координаты
    public function getXY()
    {
        return $this->xy;
    }
    public function setWeight($weight)
    {
        $this->weight = $weight;
    }
    public function getWeight()
    {
        return $this->weight;
    }
}

// Кдасс ИгровойРаунд
class GameRaund
{
    private $animals;   // Объект Животные
    private $raundNum;  // Номер раунда
    
    public function __construct ($animals)
    {        
        $this->animals = clone $animals;
    }
    // Представление раунда в виде псевдографики (массива строк)
    public function getGameRoundAsGraphics()
    {
        $gameField = array(); // поле игры, содержит строки из клеток
        $fieldSize = fieldSize;
        // Массив аватаров Животных
        $avatars = array(0 => 'm', 1 => 'K');
        
        // Формируем поле из точек
        // Повторяем для каждой строки
        for ($fieldSize; $fieldSize>0; $fieldSize--) {
            // Заполняем строку "."
            $str = str_repeat(".", fieldSize);
            // Добавляем строку в массив
            $gameField[] = $str;
        }
        // Получаем всех Жиаотных массивом
        $animals = $this->animals->getAnimalsAsArray();
        
        // Проходим по массиву животных
        foreach ($animals as $key => $animal) {
            // Узнаем координаты каждого персонажа
            $xy = $animal->getXY();
            
            // Заменяем соответствующую точку в соответствующей строке индексом мышки
            $str = $gameField[$xy['y']-1];
            // Мышь обозначаем носером
            if (get_class($animal)==mouse) {
                $str = substr_replace($str, $key+1, $xy['x']-1, 1);  
            } else {
                // Кошку буквой "K" или "@" если спит
                if ($animal->getRestingTime()>0) {
                     $str = substr_replace($str, "@", $xy['x']-1, 1); 
                } else {
                    $str = substr_replace($str, "K", $xy['x']-1, 1); 
                }
                
            }
            $gameField[$xy['y']-1] = $str;
        }
        /*
        // Добавляем инфу
        foreach ($animals as $key => $animal) {
            $xy = $animal->getXY();
            $str = $gameField[$key] . " мышь: " . $key . " x: " . $xy['x'] . " y: " . $xy['y'];
            $gameField[$key] = $str;
        }*/
        
        return $gameField;
    }
    
    public function getAnimals ()
    {
        return $this->animals;
    }
}

// Класс ГеймСет (Игра)
class GameSet
{
    private $raunds = array();
    private $animals;
    
    public function __construct ($mousesQuantity, $catsQuantity)
    {
        $this->animals = new Animals ($mousesQuantity, $catsQuantity);
        // Начальный раунд (раунд: 0)
        $this->raunds[] = new GameRaund($this->animals);
    }
    
    // Новый игровой раунд
    public function newRaund($raundNum)
    {
        // Ход животных
        $this->animals->move();
        // Новый раунд номер $raundNum
        $this->raunds[] = new GameRaund($this->animals);
    }
    
    // Печать раунда
    public function printGameRound($raundNum)
    {
        // Получаем раунд в виде псевдографики (массива строк)
        $printingRaund = $this->raunds[$raundNum]->getGameRoundAsGraphics();
        // Узнаем че по кошкам (мышам)
        $animals = $this->raunds[$raundNum]->getAnimals()->getAnimalsAsArray();
        $cats = array_filter($animals,
                    function ($animal) {
                        return (get_class($animal) == cat);
                    });
        $mouses = array_filter($animals,
                    function ($animal) {
                        return (get_class($animal) == mouse);
                    });
        $catsNum = count($cats);
        $mousesNum = count($mouses);
        
        // Выводим на экран массив построчно
        foreach ($printingRaund as $key => $str) {            
            if ($key == 0) {
                echo $str . "    " . "Ход: " . "{$raundNum}\n";
            } else if ($key == 1) {
                echo $str . "    " . "Кошек: " . "$catsNum\n";
            } else if ($key == 2) {
                echo $str . "    " . "Мышек: " . "$mousesNum\n";
            } else {
                echo "{$str}\n";
            }            
        }
        echo "\n";
    }
    
    public function getRaund($raundNum)
    {
        return $this->raunds[$raundNum];
    }
}  

// Создание новой игры
function createNewGame ()
{
    // Вводные данные для создания новой игры
    $raundsQuantity = 20;
    $mousesQuantity = 4;
    $catsQuantity = 2;
    $fieldSize = 19;
        
    define('fieldSize', $fieldSize);
    
    // Создание новой игры (раунд: 0)    
    $gameSet= new GameSet($mousesQuantity, $catsQuantity);
    // Вывести раунд на печать
    $gameSet->printGameRound(0);
    
    // Цикл раундов (начиная с первого)
    for ($raundNum=1; $raundNum<=$raundsQuantity; $raundNum++) {
        // Новый раунд
        $gameSet->newRaund($raundNum);
        // Вывести раунд на печатьs
        $gameSet->printGameRound($raundNum);;
    }
}
createNewGame();
?>