<?php
const PLACEHOLDER = " .";
const CAT = " K";
/** Класс Игра */
class Game
{
/** @var Field игровое поле */
private $field;
/** @var array массив животных */
private $animals;
/** @var int кол-во ходов */
private $numOfMoves;
/**
* Конструктор класса Игра
* @param Animal $animals,... Объекты-животные через запятую
* @param Field $field Игровое поле
* @param int $numOfMoves сколько ходов нужно отобразить
*/
public function __construct(Field $field, int $numOfMoves, Animal ...$animals){
$this->animals = $animals;
$this->field = $field;
$this->numOfMoves = $numOfMoves;
}
/** запустить игру */
public function play(){
$this->getStats();
$this->field->displayField();
echo "\n";
for($i = 0; $i < $this->numOfMoves; $i++){
foreach ($this->animals as $activeAnimal) {
$result = $activeAnimal->go($this->field);
if ($result > 0) {
foreach ($this->animals as $key => $passiveAnimal) {
if ($result == $passiveAnimal->getPosition() and $activeAnimal !== $passiveAnimal) {
unset($this->animals[$key]); }
}
}
}
$this->getStats();
$this->field->displayField();
echo "\n";
}
}
/** сколько зверей в игре */
private function getStats(){
$uniqNames = [];
$stats = [];
foreach ($this->animals as $animal) {
}
foreach ($stats as $key => &$value) {
foreach ($this->animals as $animal) {
$value++;
}
}
}
foreach ($stats as $key => $value) {
echo "$key: $value ";
}
echo "\n";
}
}
/** Класс Игровое поле */
class Field
{
/** @var int кол-во столбцов(ширина) */
private $cols;
/** @var array игровое поле */
private $field;
/**
* @param int $rows кол-во строк
* @param int $cols кол-во столбцов
* @param string $placeholder вид клеточки
*/
public function __construct(int $rows, int $cols, string $placeholder)
{
$this->field = array_fill(1, $rows * $cols, $placeholder); $this->rows = $rows;
$this->cols = $cols;
}
/** отобразить поле */
public function displayField(){
$i = 1;
foreach ($this->field as $value) {
if ($i == $this->cols) {
echo $value . "\n";
$i = 1;
} else {
echo $value;
$i++;
}
}
}
/**
* вернуть значение позиции поля
* @param int $position координата
* @return string
*/
public function read(int $position):string {
if ($position < 1 or $position > $this->getSize()) {
throw new OutOfRangeException("Выход за пределы игрового поля!\n");
} else {
return $this->field[$position];
}
}
/**
* записать значение в позицию
* @param int $position координата
* @param string $value значение
* @return string
*/
public function write(int $position, string $value) {
if ($position < 1 or $position > $this->getSize()) {
throw new OutOfRangeException("Выход за пределы игрового поля!\n");
} else {
$this->field[$position] = $value;
}
}
/**
* получить ширину поля
* @return int
*/
public function getWidth():int {
return $this->cols;
}
/**
* получить размер поля (кол-во клеток)
* @return int
*/
public function getSize():int {
return count($this->field); }
}
/** Класс Животное */
abstract class Animal
{
/** варианты сторон для шага */
const DOWN = 1;
const UP = 2;
const RIGHT = 3;
const LEFT = 4;
const NORTHWEST = 5;
const NORTHEAST = 6;
const SOUTHWEST = 7;
const SOUTHEAST = 8;
const STAND = 9;
const ALL = 10;
const AROUND = 11;
/** @var int позиция на поле */
protected $position;
/** @var string символ животного */
protected $symbol;
public function __construct(string $symbol, Field $field)
{
$this->symbol = $symbol;
$this->generatePosition($field);
}
/** Возвращает позицию */
public function getPosition():int {
return $this->position;
}
/**
* Генерирует начальную позицию
* @param Field $field Объект-Поле
*/
private function generatePosition(Field $field):void {
$position = rand(1, $field->getSize()); if (!($field->read($position) == PLACEHOLDER)) {
$this->generatePosition($field);
} else {
$field->write($position, $this->symbol);
$this->position = $position;
}
}
/**
* Проверяет cвободен ли путь, не занятали сама клеточка и клеточки в ее направлении
* @param callable $isMe калбек ф-я
* @param Field $field игровое поле
* @param int $move ход
* @param int $step длина шага
* @param int $way направление хода(если шаг больше 1)
* @return bool
*/
protected function isOccupied(callable $isMe, Field $field, int $move, int $step = 1, int $way = 0):bool {
if ($isMe($field, $move)) {
return true;
} elseif ($step == 1 and $way == 0) {
return false;
} else {
$position = $this->position;
$stepsToGoal = --$step;
for ($i = 1; $i <= $stepsToGoal; $i++) {
$result = $this->generateMoves($isMe, $field, $position, 1, $way);
if ($result == false) {
return true;
} else {
$position = $result[0];
}
}
return false;
}
}
/**
* Универсальная ф-я для ген-ии возможных ходов
* @param Field $field поле
* @param callable $isMe калбек ф-я определяет есть ли на клеточке сородич, принимает объект-поле и ход, возвращает true/false
* @param int $position позиция животного
* @param int $step величина шага
* @param int $ways,... направления для шага через запятую, принимает константы
* @return array
*/
protected function generateMoves(callable $isMe, Field $field, int $position, int $step, int ...$ways){
if ($ways[0] == self::ALL) {
$ways = [1, 2, 3, 4, 5, 6, 7, 8, 9];
}
if ($ways[0] == self::AROUND) {
$ways = [1, 2, 3, 4, 5, 6, 7, 8];
}
}
$moves = [];
//если шаг = 1
if ($step == 1) {
foreach ($ways as $way) {
if ($way == self::DOWN) {
if (!($position + $field->getWidth() > $field->getSize())) { //Ход вниз
$move = $position + $field->getWidth();
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::UP){
if (!($position - $field->getWidth() < 1)) { //Ход вверх
$move = $position - $field->getWidth();
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::RIGHT){
if (!(($position % $field->getWidth()) == 0)) { //Ход вправо
$move = $position + $step;
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::LEFT){
if (!((($position - 1) % $field->getWidth()) == 0)) { //Ход влево
$move = $position - $step;
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::NORTHWEST){
if (!(((($position - 1) % $field->getWidth()) == 0) or ($position - $field->getWidth() < 1))) { // диагональ влево вверх
$move = $position - ($field->getWidth() + $step);
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::NORTHEAST){
if (!((($position % $field->getWidth()) == 0) or ($position - $field->getWidth() < 1))) { //диагональ вправо вверх
$move = $position - ($field->getWidth() - $step);
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::SOUTHWEST){
if (!(((($position - 1) % $field->getWidth()) == 0) or ($position + $field->getWidth() > $field->getSize()))) { //диаг влево вниз
$move = $position + ($field->getWidth() - $step);
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::SOUTHEAST){
if (!((($position % $field->getWidth() == 0) or ($position + $field->getWidth() > $field->getSize())))) { //диаг вправо вниз
$move = $position + ($field->getWidth() + $step);
if (!$this->isOccupied($isMe, $field, $move)) {
$moves[] = $move;
}
}
}
if ($way == self::STAND) {
$moves[] = $position;
}
}
} else { //если шаг больше 1
foreach ($ways as $way) {
if ($way == self::DOWN) {
if (!($position + ($field->getWidth() * $step) > $field->getSize())) { //Ход вниз
$move = $position + ($field->getWidth() * $step);
if (!$this->isOccupied($isMe, $field,$move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::UP){
if (!($position - ($field->getWidth() * $step) < 1)) { //Ход вверх
$move = $position - ($field->getWidth() * $step);
if (!$this->isOccupied($isMe, $field, $move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::RIGHT){
if (!(($position % $field->getWidth()) == 0)) { //Ход вправо
$partOfStep = $step;
$partOfStep--;
while ($partOfStep > 0) {
if ((($position + $step) - $partOfStep) % $field->getWidth() == 0) {
continue 2;
} else {
$partOfStep--;
}
}
$move = $position + $step;
if (!$this->isOccupied($isMe, $field, $move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::LEFT){
if (!((($position - 1) % $field->getWidth()) == 0)) { //Ход влево
$partOfStep = $step - 2;
while ($partOfStep >= 0) {
if ((($position - $step) + $partOfStep) % $field->getWidth() == 0) {
continue 2;
} else {
$partOfStep--;
}
}
$move = $position - $step;
if (!$this->isOccupied($isMe, $field, $move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::NORTHWEST){
if (!(((($position - 1) % $field->getWidth()) == 0) or ($position - ($field->getWidth() * $step) < 1))) { // диагональ влево вверх
$partOfStep = $step - 2;
while ($partOfStep >= 0) {
if ((($position - $step) + $partOfStep) % $field->getWidth() == 0) {
continue 2;
} else {
$partOfStep--;
}
}
$move = $position - (($field->getWidth() * $step) + $step);
if (!$this->isOccupied($isMe, $field, $move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::NORTHEAST){
if (!((($position % $field->getWidth()) == 0) or ($position - ($field->getWidth() * $step) < 1))) { //диагональ вправо вверх
$partOfStep = $step;
$partOfStep--;
while ($partOfStep > 0) {
if ((($position + $step) - $partOfStep) % $field->getWidth() == 0) {
continue 2;
} else {
$partOfStep--;
}
}
$move = $position - (($field->getWidth() * $step) - $step);
if (!$this->isOccupied($isMe, $field, $move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::SOUTHWEST){
if (!(((($position - 1) % $field->getWidth()) == 0) or ($position + $field->getWidth() * $step > $field->getSize()))) { //диаг влево вниз
$partOfStep = $step - 2;
while ($partOfStep >= 0) {
if ((($position - $step) + $partOfStep) % $field->getWidth() == 0) {
continue 2;
} else {
$partOfStep--;
}
}
$move = $position + (($field->getWidth() * $step) - $step);
if (!$this->isOccupied($isMe, $field,$move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::SOUTHEAST){
if (!((($position % $field->getWidth()) == 0) or ($position + $field->getWidth() * $step > $field->getSize()))) { //диаг вправо вниз
$partOfStep = $step;
$partOfStep--;
while ($partOfStep > 0) {
if ((($position + $step) - $partOfStep) % $field->getWidth() == 0) {
continue 2;
} else {
$partOfStep--;
}
}
$move = $position + (($field->getWidth() * $step) + $step);
if (!$this->isOccupied($isMe, $field, $move,$step, $way)) {
$moves[] = $move;
}
}
}
if ($way == self::STAND) {
$moves[] = $position;
}
}
}
if (count($moves) == 0) { return $moves;
} else {
return $moves;
}
}
/**
* Метод для оценки ходов
* @param array $moves - массив ходов полученный функцией generateMoves()
* @return int ход с наибольшим весом
*/
abstract protected function rateMoves
(Field
$field, array $moves):int
;
/**
* Сделать ход
* Тут происходит вызов generateMoves() и rateMoves()
* @return int возвращаем 0 если ход обычный либо позицию съеденного животного
*/
abstract public function go(Field $field):int;
}
class Cat extends Animal
{
/** @var int кол-во ходов без отдыха */
private $movesWithoutRest;
/** @var int съеденные мышки */
private $eatenMice;
protected function rateMoves
(Field
$field, array $moves):int
{ $movesWithRate = [];
foreach ($moves as $move) {
$rate = 0;
$neighboringMice = 0;
$aroundArea = $this->generateMoves(function($f,$m){return false;}, $field, $move, 1, self::AROUND); //берем клетки вокруг мыша
foreach ($aroundArea as $cell) {
$neighboringMice++;
}
}
if ($neighboringMice >= 2) {//если рядом с мышкой еще две мышки 0 баллов
$movesWithRate[] = [$rate,$move];
} else {
$rate = 12; //иначе высокий балл
$movesWithRate[] = [$rate,$move];
}
} elseif($field->read($move) == PLACEHOLDER) { //пустая клетка
$rate++; //просто пустая клеточка 1 балл
$mice = 0;
$aroundArea = $this->generateMoves(function($f,$m){return false;}, $field, $move, 1, self::AROUND);//берем клетки вокруг клетки
foreach ($aroundArea as $cell) {
$mice++; // по +1 за мышку рядом с пустой клеточкой
}
}
$rate += $mice;
$movesWithRate[] = [$rate, $move];
} else { //своя позиция
if (empty($movesWithRate)) { //других ходов нет return $move;
} else {
$sumRate = 0;
foreach ($movesWithRate as $moveWithRate) {//посчитаем сумму предыдущих ходов
$sumRate += $moveWithRate[0];
}
if ($sumRate == 0) {
return $move;
} else { //если сумма больше 1 стоять не нужно
$movesWithRate[] = [$rate, $move];
}
}
}
}
$rates = array_column($movesWithRate, 0);
$moves = array_column($movesWithRate, 1);
//Если есть несколько одинаковых крупнейших оценок выберем рандомно одну из них
if ($largestRateKeys > 1) {
$key = $largestRateKeys[rand(0, count($largestRateKeys) - 1)]; return $moves[$key];
} else {//Иначе самую крупную
return $moves[0];
}
}
public function go(Field $field):int {
if ($this->eatenMice > 0) {
$this->eatenMice = 0;
$this->movesWithoutRest = 0;
$field->write($this->position, " @");
return 0;
}
if ($this->movesWithoutRest == 8) {
$this->movesWithoutRest = 0;
$field->write($this->position, " @");
return 0;
}
$isMe = function(Field $field, int $move){
if ($field->read($move) == $this->symbol or $field->read($move) == " @") {
return true;
} else {
return false;
}
};
$moves = $this->generateMoves($isMe, $field, $this->position, 1, self::ALL);
$move = $this->rateMoves($field, $moves);
$this->eatenMice++;
}
$field->write($this->position, PLACEHOLDER);
$this->position = $move;
$field->write($move, $this->symbol);
$this->movesWithoutRest++;
if ($this->eatenMice > 0) {
return $move;
} else {
return 0;
}
}
}
class Mouse extends Animal
{
protected function rateMoves
(Field
$field, array $moves):int
{ $cellsWithCats = [];
$emptyCellsWithCats = [];
$emptyCells = [];
$myCell = [];
foreach($moves as $move){
$rate = 0;
if (!is_numeric(trim($field->read($move))) and
$field->read($move) != PLACEHOLDER
) { // на клеточке враг 0 баллов $cellsWithCats[] = [$rate, $move];
} elseif ($field->read($move) == PLACEHOLDER) { // клеточка пуста
/* Посчитаем есть ли рядом с клеткой враги */
if ($move == current($this->generateMoves(function($f,$m){return false;}, $field, $this->position, 1, self::UP))) { $neighboringCells = $this->generateMoves(function($f,$m){return false;}, $field, $move, 1, self::LEFT, self::RIGHT, self::SOUTHEAST, self::SOUTHWEST);
} elseif($move == current($this->generateMoves(function($f,$m){return false;}, $field, $this->position, 1, self::DOWN))) { $neighboringCells = $this->generateMoves(function($f,$m){return false;}, $field, $move, 1, self::LEFT, self::RIGHT, self::NORTHEAST, self::NORTHWEST);
} elseif ($move == current($this->generateMoves(function($f,$m){return false;}, $field, $this->position, 1, self::LEFT))) { $neighboringCells = $this->generateMoves(function($f,$m){return false;}, $field, $move, 1, self::UP, self::DOWN, self::NORTHEAST, self::SOUTHEAST);
} else {
$neighboringCells = $this->generateMoves(function($f,$m){return false;}, $field, $move, 1, self::UP, self::DOWN, self::NORTHWEST, self::SOUTHWEST);
}
$cats = 0;
foreach ($neighboringCells as $cell) {
if (!is_numeric(trim($field->read($cell))) and
$field->read($cell) != PLACEHOLDER
) { $cats++;
}
}
if ($cats > 0) { // враги рядом с клеткочкой есть - 1 балл
$rate++;
$emptyCellsWithCats[] = [$rate, $move];
} else {
$rate += 10; // путь свободен - 10 баллов
$emptyCells[] = [$rate, $move];
}
} else {
if (empty($emptyCells)) { // ходов на 10 баллов нет if (array_sum(array_column
(array_merge($cellsWithCats, $emptyCellsWithCats), 0)) == 0) { // все пути закрыты, стоим return $move;
} else { // иначе выбор между ходом в 1 балл и стоять
$friends = 0;
$aroundArea = $this->generateMoves(function($f,$m){return false;}, $field, $move, 1, self::AROUND);
foreach ($aroundArea as $cell) {
$friends++;
}
}
if ($friends >= 2) { // если рядом друзья - стоим
return $move;
}
$myCell[] = [$rate, $move]; // иначе стоять оценим в 0 баллов
}
} else {
$myCell[] = [$rate, $move];
}
}
}
$movesWithRate = array_merge($cellsWithCats, $emptyCellsWithCats, $emptyCells, $myCell); $rates = array_column($movesWithRate, 0);
$moves = array_column($movesWithRate, 1);
//Если есть несколько одинаковых крупнейших оценок выберем рандомно одну из них
if ($largestRateKeys > 1) {
$key = $largestRateKeys[rand(0, count($largestRateKeys) - 1)]; return $moves[$key];
} else {//Иначе самую крупную
return $moves[0];
}
}
public function go(Field $field):int {
$isMe = function(Field $field, int $move){
return true;
} else {
return false;
}
};
$moves = $this->generateMoves($isMe, $field, $this->position, 1, self::UP, self::DOWN, self::LEFT, self::RIGHT, self::STAND);
$move = $this->rateMoves($field, $moves);
$field->write($this->position, PLACEHOLDER);
$this->position = $move;
$field->write($move, $this->symbol);
return 0;
}
}
$field = new Field(10, 10, PLACEHOLDER);
$m = new Mouse(" 1", $field);
$m2 = new Mouse(" 2", $field);
$m3 = new Mouse(" 3", $field);
$c = new Cat(CAT, $field);
$c2 = new Cat(CAT, $field);
$c3 = new Cat(CAT, $field);
$game = new Game($field, 20, $m, $m2, $m3, $c, $c2, $c3);
$game->play();