<?php
/**
* GDD 2010 Pacman
*
* @author @koriym
*/
/**
* キャラクターインターフェイス
*
*/
interface Character_Interface{
public function __construct($myChar, $y, $x);
}
/**
* キャラクター
*
*/
abstract class Character implements Character_Interface
{
/**
* キャラ文字
*
* @var string
*/
protected $_char;
/**
* X座標
*
* @var int
*/
protected $_x;
/**
* Y座標
*
* @var int
*/
protected $_y;
/**
* X移動
*
* @var int
*/
protected $_dx = 0;
/**
* Y移動
*
* @var int
*/
protected $_dy = 0;
/**
* 移動可能座標
*
* @var array
*/
protected $_wayToGo = array();
/**
* 移動可能場所数
*
* @var int
*/
protected $_wayToGoCount = 0;
/**
* 時計回り配列
*
* @var array
*/
protected $_clockwiseDirection = array(array(0, 1), array(-1, 0), array(0, -1), array(1, 0));
/**
* 方向名
*
* @var array
*/
protected $_clockwiseNames = array('j', 'h', 'k', 'l');
public function __construct($myChar, $x, $y)
{
$this->_myChar = $myChar;
$this->_x = $x;
$this->_y = $y;
}
public function getPosition(){
return array($this->_x, $this->_y);
}
/**
* データ取得
*
*/
public function get()
{
return array($this->_myChar, $this->_x, $this->_y, $this->_dx, $this->_dy);
}
/**
* キャラクタの移動可能状態をセット
*
* @param array $maze 迷路
* @param array $directionStrategy 移動方向戦略
*/
protected function _setPositionStatus($maze, $directionStrategy)
{
$cnt = 0;
$this->_wayToGo = array();
$wayToGo = array();
foreach ($directionStrategy as $item) {
list($dx, $dy) = $item;
$x = $this->_x + $dx;
$y = $this->_y + $dy;
$isExist = isset($maze[$y][$x]);
if ($isExist && $maze[$y][$x] === '.' || $maze[$y][$x] === ' ') {
$this->_wayToGo[] = array('dy' => $dy, 'dx' => $dx);
$cnt++;
}
$this->_wayToGoCount = $cnt;
}
}
}
/**
* パックマン
*
*/
class Pacman extends Character
{
/**
* 足跡
*
* @var string
*/
private $_joystickHistory = '';
/**
* 移動足跡
*
* @var array
*/
private $_footprintMap = array();
/**
* 移動足跡初期化
*
*/
public function setFootprintMap($width, $hight)
{
for ($i = 0; $i < $hight ; $i++) {
$this->_footprintMap[$i] = array_fill(0, $width, 0);
}
}
/**
* パックマン移動
*
* @param array $maze 迷路
* @param int $time タイム
* @param Pacman_Strategy $strategy 移動戦略
*/
public function move($maze, $time, Pacman_Strategy $strategy)
{
$directionStrategy = $strategy->getDirectionStrategy();
$methodStragey = $strategy->getMethodStrategy();
$this->_wayToGo = array();
$this->_setPositionStatus($maze, $directionStrategy);
switch ($this->_wayToGoCount) {
case 0:
// 動けない
$this->_dx = $this->_dy = 0;
break;
case 1:
// 行き止まりなので唯一いける方向へ
$togo = $this->_wayToGo[0];
$this->_dy = $togo['dy'];
$this->_dx = $togo['dx'];
break;
case 2:
// バックじゃない方
$isReverse = ($this->_dx === ($this->_wayToGo[0]['dx'] * -1) && $this->_dy === ($this->_wayToGo[0]['dy'] * -1));
$i = !$isReverse ? 0 : 1;
$this->_dy = $this->_wayToGo[$i]['dy'];
$this->_dx = $this->_wayToGo[$i]['dx'];
break;
case 3:
case 4:
// 交差点で考える
foreach ($methodStragey as $method) {
foreach ($this->_wayToGo as $toGo) {
$result = $this->$method($maze, $toGo['dx'], $toGo['dy']);
if ($result === true) {
$this->_dy = $toGo['dy'];
$this->_dx = $toGo['dx'];
goto finish;
break;
}
}
}
$this->_dx = $this->_dy = 0;
// ランダム
default:
}
finish:
$this->_x += $this->_dx;
$this->_y += $this->_dy;
$direction = array_search(array($this->_dx, $this->_dy), $this->_clockwiseDirection);
$this->_joystickHistory .= $this->_clockwiseNames[$direction];
$this->_footprintMap[$this->_y][$this->_x]++;
$result = array($this->_x, $this->_y, $this->_dx, $this->_dy, 0);
return $result;
}
/**
* 食べれるなら食べる戦略
*/
private function eat($maze, $dx, $dy)
{
if ($maze[$this->_y + $dy][$this->_x + $dx] === '.') {
return true;
} else {
return false;
}
}
/**
* 好奇心戦略
*/
private function discover($maze, $dx, $dy)
{
$destination = $maze[$this->_y + $dy][$this->_x + $dx];
$isNewPath = ($this->_footprintMap[$this->_y + $dy][$this->_x + $dx] <= 1) && ($destination == ' ');
return $isNewPath;
}
/**
* 発見戦略
*/
private function search($maze, $dx, $dy)
{
while (true) {
if ($maze[$this->_y + $dy][$this->_x + $dx] === '.') {
return true;
} elseif (!isset($maze[$this->_y + $dy][$this->_x + $dx]) || $maze[$this->_y + $dy][$this->_x + $dx] === '#') {
return false;
}
$dx++;
$dy++;
}
}
/**
* 前進戦略
*/
private function forward($maze, $dx, $dy) {
$isReverse = $this->_dx == ($dx * -1) && $this->_dy == ($dy * -1);
$destination = $maze[$this->_y + $dy][$this->_x + $dx];
$enbaleToGo = ($destination === ' ' || $destination === '.');
if (!$isReverse && $enbaleToGo) {
return true;
} else {
return false;
}
}
/**
* 行き止まり反転戦略
*/
private function deadend(&$maze, $dx, $dy) {
$isReverse = $this->_dx == ($dx * -1) && $this->_dy == ($dy * -1);
$destination = $maze[$this->_y + $dy][$this->_x + $dx];
$enbaleToGo = ($destination === ' ' || $destination === '.');
if ($isReverse && $enbaleToGo) {
return true;
} else {
return false;
}
}
/**
* 足跡文字列取得
*
*/
public function getJoyStickHistory()
{
return $this->_joystickHistory;
}
}
/**
* パックマン移動戦略
*
*/
class Pacman_Strategy
{
/**
* 方向パターン戦術
*/
public function getDirectionStrategy()
{
// リスト化して再度同じパターンをださないようにする(未実装)
$directionStrategy = array(array(-1, 0), array(0, -1), array(1, 0), array(0, 1));
shuffle($directionStrategy);
return $directionStrategy;
}
/**
* 移動戦略
*
* 問題やゲームの進行によって戦略を変える(未実装)
*/
public function getMethodStrategy()
{
$methodStragey = array('eat', 'discover', 'forward', 'deadend');
// $methodStragey = array('forward', 'deadend');
return $methodStragey;
}
}
class Pacman_Strategy2 extends Pacman_Strategy
{
/**
* 移動戦略
*/
public function getMethodStrategy()
{
$methodStragey = array('eat', 'search', 'forward', 'deadend');
return $methodStragey;
}
}
/**
* モンスター
*/
class Monster extends Character
{
/**
* 最初?
*
* @var bool
*/
private $_init = true;
/**
* モンスターL
*
* @var string
*/
private $_j = 'L';
/**
* 移動
*
* @param array $maze
* @param int $pacmanX
* @param int $pacmanY
*
*/
public function move($maze, $pacmanX, $pacmanY)
{
$this->_wayToGo = array();
if ($this->_init === true) {
//時刻 t = 0 においては、初期位置の 下、左、上、右 の順で最初に進入可能なマスの方向に移動します。
$this->_init = false;
$this->_setPositionStatus($maze, $this->_clockwiseDirection);
$this->_dy = $this->_wayToGo[0]['dy'];
$this->_dx = $this->_wayToGo[0]['dx'];
} else {
//下、左、上、右 の順
$this->_setPositionStatus($maze, $this->_clockwiseDirection);
switch ($this->_wayToGoCount) {
case 1:
// 行き止まりなので唯一いける方向へ
$togo = $this->_wayToGo[0];
$this->_dy = $togo['dy'];
$this->_dx = $togo['dx'];
case 2:
// バックじゃない方
$isReverse = ($this->_dx === ($this->_wayToGo[0]['dx'] * -1) && $this->_dy === ($this->_wayToGo[0]['dy'] * -1));
if ($isReverse) {
$this->_dy = $this->_wayToGo[1]['dy'];
$this->_dx = $this->_wayToGo[1]['dx'];
} else {
$this->_dy = $this->_wayToGo[0]['dy'];
$this->_dx = $this->_wayToGo[0]['dx'];
}
break;
case 3:
case 4:
// モンスターに応じて
$method = '_move' . $this->_myChar;
list($this->_dx, $this->_dy) = $this->$method($maze, $pacmanX, $pacmanY);
if ($this->_dx == 0 && $this->_dy == 0){
p("error $this->_myChar");exit();
}
break;
default:
}
}
$this->_y += $this->_dy;
$this->_x += $this->_dx;
// もし以前パックマンがいたところに移動したら”王手”。パックマンは前にモンスターがいたところには移動できない。仮に壁にする。
$makeMeWall = ($this->_x === $pacmanX && $this->_y === $pacmanY);
$wall = $makeMeWall ? array('x' => $this->_x - $this->_dx, 'y' => $this->_y - $this->_dy) : false;
$result = array($this->_x, $this->_y, $this->_myChar, $wall);
return $result;
}
/**
* モンスターV
*
* 敵から見た自機の相対位置を (dx, dy) と表すものとします。次のルールを上から順に適用し、最初に選ばれた方向に移動します。
*
* 1. dy ≠ 0 でかつ dy の符号方向にあるマスが進入可能であれば、その方向に移動します。
* 2. dx ≠ 0 でかつ dx の符号方向にあるマスが進入可能であれば、その方向に移動します。
* 3. 現在位置の 下、左、上、右 の順で最初に進入可能なマスの方向に移動する。
*/
private function _moveV($maze, $pacmanX, $pacmanY)
{
$dx = $pacmanX - $this->_x;
$dy = $pacmanY - $this->_y;
// 1
if ($dy !== 0 ) {
$ddy = $dy/abs($dy);
if (isset($maze[$this->_y + $ddy][$this->_x]) && $maze[$this->_y + $ddy][$this->_x] !== '#') {
return array(0, $ddy);
}
}
// 2
if ($dx !== 0 ){
$ddx = $dx/abs($dx);
if (isset($maze[$this->_y][$this->_x + $ddx]) && $maze[$this->_y][$this->_x + $ddx] !== '#') {
return array($ddx, 0);
}
}
// 3
$result = array($this->_wayToGo[0]['dx'], $this->_wayToGo[0]['dy']);
return $result;
}
/**
* モンスターH
*
* 敵 V とほぼ同じです。唯一異なるのは 、進行方向を決めるルールのうち
* 最初の二つのルールの適用順序が入れ替わるところです。
*/
private function _moveH($maze, $pacmanX, $pacmanY)
{
$dx = $pacmanX - $this->_x;
$dy = $pacmanY - $this->_y;
// 2
if ($dx !== 0 ){
$ddx = $dx/abs($dx);
if (isset($maze[$this->_y][$this->_x + $ddx]) && $maze[$this->_y][$this->_x + $ddx] !== '#') {
return array($ddx, 0);
}
}
// 1
if ($dy !== 0 ) {
$ddy = $dy/abs($dy);
if (isset($maze[$this->_y + $ddy][$this->_x]) && $maze[$this->_y + $ddy][$this->_x] !== '#') {
return array(0, $ddy);
}
}
// 3
$result = array($this->_wayToGo[0]['dx'], $this->_wayToGo[0]['dy']);
return $result;
}
/**
* モンスターL
*
* 現在位置への進入方向から見て相対的に 左、前、右 の順
*/
private function _moveL($maze, $pacmanX, $pacmanY)
{
$directionStrategy = $this->_getRelativeDirection(array(-1, 0, 1));
$this->_setPositionStatus($maze, $directionStrategy, true);
$result = array($this->_wayToGo[0]['dx'], $this->_wayToGo[0]['dy']);
return $result;
}
/**
* モンスターR
*
* 現在位置への進入方向から見て相対的に 右、前、左 の順
*/
private function _moveR($maze, $pacmanX, $pacmanY)
{
$directionStrategy = $this->_getRelativeDirection(array(1, 0, -1));
$this->_setPositionStatus($maze, $directionStrategy, true);
$result = array($this->_wayToGo[0]['dx'], $this->_wayToGo[0]['dy']);
return $result;
}
/**
* モンスターJ
*
* 最初は敵Lの行動、次回は敵Rの行動、さらに次回はまた敵Lの行動、と繰り返します。
*/
private function _moveJ($maze, $pacmanX, $pacmanY)
{
$method = "_move{$this->_j}";
$result = $this->$method($maze, $pacmanX, $pacmanY);
$this->_j = ($this->_j === 'L') ? 'R' : 'L';
return $result;
}
/**
* 進行方向に対しての相対方向(左右など)戦略の配列を作成
*
* @param interger $relativeDirection 1=右, -1=左
*/
private function _getRelativeDirection($relativeDirections)
{
$result = array();
$currentDirection = array($this->_dx, $this->_dy);
foreach ($relativeDirections as $relativeDirection) {
$pos = array_search($currentDirection, $this->_clockwiseDirection);
$directionIndex = $pos + $relativeDirection;
if ($directionIndex === -1 ) {
$directionIndex = 3;
}
if ($directionIndex === 4 ) {
$directionIndex = 0;
}
array_push($result, $this->_clockwiseDirection[$directionIndex]);
}
return $result;
}
}
/**
* クイズメイン
*
*/
class Pacman_Quiz
{
private $_score = 0;
private $_highScore = 0;
private $_clearScore = 0;
private $_timeOut = 50;
private $_time = 0;
private $_highTime = 999;
private $_pacman;
private $_monsters = array();
private $_maze = array();
private $_history = array();
private $_debug = false;
private $_debugTime = 0;
private $_debugAnimation = false;
public static $record = array();
public static $gameCount = 1;
/**
* 迷路から必要なオブジェクトやプロパティをセット
*
* Pacmanオブジェクト
* Monsterオブジェクト
* ドットの数
* キャラクターがいない迷路
*/
private function _injectFromMaze($maze)
{
$point = 0;
$this->_pacman = null;
$this->_monsters = array();
for ($y = 0; isset($maze[$y]); $y++) {
for($x = 0 ; $x < count($maze[$y]); $x++) {
$char = $maze[$y][$x];
if ($char === '@') {
$this->_pacman = new Pacman($char, $x, $y);
$this->_pacman->setFootprintMap(count($maze[0]), count($maze));
$maze[$y][$x] = ' ';
} elseif ($char === '.') {
$point++;
} elseif (preg_match('/[A-Z]/', $char, $matches)) {
$this->_monsters[] = new Monster($char, $x, $y);
$maze[$y][$x] = ' ';
}
}
}
$this->_clearScore = $point;
$this->_maze = $maze;
}
/**
* 問題1のインジェクター
*
*/
public function _injectQuestionOne()
{
$maze = array();
$maze[] = $this->_split('###########');
$maze[] = $this->_split('#.V..#..H.#');
$maze[] = $this->_split('#.##...##.#');
$maze[] = $this->_split('#L#..#..R.#');
$maze[] = $this->_split('#.#.###.#.#');
$maze[] = $this->_split('#....@....#');
$maze[] = $this->_split('###########');
$this->_injectFromMaze($maze);
$this->_pacmanStrategy = new Pacman_Strategy();
$this->_timeOut = 50;
}
/**
* 問題2のインジェクター
*
*/
public function _injectQuestionTwo()
{
$maze = array();
$maze[] = $this->_split('####################');
$maze[] = $this->_split('###.....L..........#');
$maze[] = $this->_split('###.##.##.##L##.##.#');
$maze[] = $this->_split('###.##.##.##.##.##.#');
$maze[] = $this->_split('#.L................#');
$maze[] = $this->_split('#.##.##.##.##.##.###');
$maze[] = $this->_split('#.##.##L##.##.##.###');
$maze[] = $this->_split('#.................L#');
$maze[] = $this->_split('#.#.#.#J####J#.#.#.#');
$maze[] = $this->_split('#L.................#');
$maze[] = $this->_split('###.##.##.##.##.##.#');
$maze[] = $this->_split('###.##.##R##.##.##.#');
$maze[] = $this->_split('#................R.#');
$maze[] = $this->_split('#.##.##.##.##R##.###');
$maze[] = $this->_split('#.##.##.##.##.##.###');
$maze[] = $this->_split('#@....R..........###');
$maze[] = $this->_split('####################');
$this->_injectFromMaze($maze);
$this->_pacmanStrategy = new Pacman_Strategy2();
$this->_timeOut = 300;
}
/**
* 問題3のインジェクター
*
*/
public function _injectQuestionThree()
{
$maze = array();
$maze[] = $this->_split('##########################################################');
$maze[] = $this->_split('#........................................................#');
$maze[] = $this->_split('#.###.#########.###############.########.###.#####.#####.#');
$maze[] = $this->_split('#.###.#########.###############.########.###.#####.#####.#');
$maze[] = $this->_split('#.....#########....J.............J.......###.............#');
$maze[] = $this->_split('#####.###.......#######.#######.########.###.#######.#####');
$maze[] = $this->_split('#####.###.#####J#######.#######.########.###.## ##.#####');
$maze[] = $this->_split('#####.###L#####.## ##L## ##.## ##.###.## ##.#####');
$maze[] = $this->_split('#####.###..H###.## ##.## ##.########.###.#######J#####');
$maze[] = $this->_split('#####.#########.## ##L## ##.########.###.###V....#####');
$maze[] = $this->_split('#####.#########.#######.#######..........###.#######.#####');
$maze[] = $this->_split('#####.#########.#######.#######.########.###.#######.#####');
$maze[] = $this->_split('#.....................L.........########..........R......#');
$maze[] = $this->_split('#L####.##########.##.##########....##....#########.#####.#');
$maze[] = $this->_split('#.####.##########.##.##########.##.##.##.#########.#####.#');
$maze[] = $this->_split('#.................##............##..@.##...............R.#');
$maze[] = $this->_split('##########################################################');
$this->_injectFromMaze($maze);
$this->_pacmanStrategy = new Pacman_Strategy();
$this->_timeOut = 700;
}
/**
* debugインジェクター
*
*/
public function _injectDebugHitCheck()
{
$maze = array();
$maze[] = $this->_split('#############');
$maze[] = $this->_split('## ##########');
$maze[] = $this->_split('##....@..R..#');
$maze[] = $this->_split('#############');
$this->_injectFromMaze($maze);
$this->_pacmanStrategy = new Pacman_Strategy();
$this->_timeOut = 300;
}
/**
* debugインジェクター
*
*/
public function _injectDebugMonster()
{
$maze = array();
$maze[] = $this->_split('###########');
$maze[] = $this->_split('# V # H #');
$maze[] = $this->_split('# ## ## #');
$maze[] = $this->_split('#L# # R #');
$maze[] = $this->_split('# # ### # #');
$maze[] = $this->_split('# @ #');
$maze[] = $this->_split('###########');
$maze[] = $this->_split('###########');
$maze[] = $this->_split('#. .....#');
$maze[] = $this->_split('#. .....#');
$maze[] = $this->_split('#. .....#');
$maze[] = $this->_split('#.###..##.#');
$maze[] = $this->_split('#.#.#.###.#');
$maze[] = $this->_split('#....@....#');
$maze[] = $this->_split('###########');
$this->_injectFromMaze($maze);
$this->_pacmanStrategy = new Pacman_Strategy2();
$this->_timeOut = 5000;
}
/**
* メイン
*
* ゲームのインスタンスをオブジェクトにして履歴をとり
* リプレイ時に最初からしないで途中の状態から再開する(未実装)
*
*/
public function run($injector = '_injectQuestionOne')
{
do {
$this->$injector();
$this->_runOneGame();
self::$gameCount++;
} while(true);
}
/**
* 1ゲームプレイ
*/
private function _runOneGame()
{
// init
$stach = array();
$this->_time = 0;
$this->_score = 0;
$isHit = $isTimeOut = $isClear = false;
$mazeWithChar = $mazeWithoutChar = $this->_maze;
$pacmanX = $pacmanY = 0;
// main
while (!$isHit && !$isTimeOut && !$isClear) {
$mazeWithChar = $mazeWithoutChar;
foreach ($this->_monsters as $monster) {
list($monsterX, $monsterY, $myChar, $wall) = $monster->move($mazeWithoutChar, $pacmanX, $pacmanY);
$mazeWithChar[$monsterY][$monsterX] = $myChar;
if (is_array($wall)) {
$mazeWithChar[$wall['y']][$wall['x']] = '#';
}
}
list($pacmanX, $pacmanY, $dx, $dy) = $this->_pacman->move($mazeWithChar, $this->_time, $this->_pacmanStrategy);
if ($mazeWithoutChar[$pacmanY][$pacmanX] === '.') {
$this->_score++;
$mazeWithoutChar[$pacmanY][$pacmanX] = ' ';
}
$mazeWithChar[$pacmanY- $dy][$pacmanX - $dx] = ' ';
$mazeWithChar[$pacmanY][$pacmanX] = '@';
$isClear = ($this->_score == $this->_clearScore);
$isTimeOut = ($this->_time == $this->_timeOut);
$isHit |= $this->_hitCheck($pacmanX, $pacmanY);
$this->_time++;
if ($this->_debug) {
$this->_showCompositScreen($mazeWithoutChar);
usleep($this->_debugTime);
if ($this->_debugAnimation) {
echo "\033[;H\033[2J"; // clear screen
}
}
}
// Game End
if ($isHit) {
$this->_gameOver('Hit', $mazeWithChar);
return;
}
if ($isTimeOut) {
$this->_gameOver('timeOut', $mazeWithChar);
return;
}
if ($isClear) {
if ($this->_time > $this->_highTime) {
return false;
}
$this->_highTime = $this->_time;
echo $this->_showCompositScreen($mazeWithChar);
echo "\nGAME CLEAR\n";
//$this->_gameOver('Game Clear');
echo "\n";
return;
}
return;
}
/**
* Game Over画面出力
*/
public function _gameOver($reason, $mazeWithChar) {
if ($this->_score > $this->_highScore) {
$this->_highScore = $this->_score;
echo $this->_showCompositScreen($mazeWithChar);
echo "High Score: $this->_highScore " . 'total: '. self::$gameCount . "($reason)\n";
}
}
/**
* ヒットチェック
*/
public function _hitCheck($pacmanX, $pacmanY)
{
$isHit = false;
foreach ($this->_monsters as $monster) {
list($monsterX, $monsterY) = $monster->getPosition();
if ($pacmanX === $monsterX && $pacmanY == $monsterY) {
$isHit = true;
}
}
return $isHit;
}
/**
* デバックモード
*
* @param int $time アニメーションタイム
*
*/
public function setDebug($time = 0) {
$this->_debugTime = $time * 1000;
$this->_debugAnimation = ($time) ? true : false;
$this->_debug = true;
}
/**
* 迷路配列作成
*
*/
private function _split($str)
{
$result = array();
for ($i = 0 ; $i < strlen($str); $i++){
$result[] = substr($str, $i, 1);
}
return $result;
}
/**
* ゲーム画面描画
*/
private function _showCompositScreen(array $maze, $debug = false)
{
$characters = $this->_monsters;
array_push($characters, $this->_pacman);
foreach ($characters as $character) {
list($char, $x, $y, $dx, $dy) = $character->get();
$maze[$y][$x] = $char;
}
// echo
foreach ($maze as $y) {
echo implode('', $y) . "\n";
}
echo "$this->_score / $this->_clearScore : $this->_time \n";
echo "High Score: $this->_highScore " . 'total: '. self::$gameCount . "\n";
echo "Play:" . $this->_pacman->getJoystickHistory() . "\n";
}
}
$quiz = new Pacman_Quiz();
$quiz->setDebug(500); // コンソールアニメーション
//$quiz->setDebug(); // 単純描画
// debug
// $quiz->run('_injectDebugHitCheck');
//$quiz->run('_injectDebugMonster');
// 問題
//$quiz->run('_injectQuestionOne');
$quiz->run('_injectQuestionTwo');
//$quiz->run('_injectQuestionThree');