3
3

More than 5 years have passed since last update.

「第12回 オフラインリアルタイムどう書く」の課題をPHPで書いてみた

Last updated at Posted at 2014-01-13

0.きっかけ

qiitaを見ていてたまたま見かけて興味を持ったので。
なんで第12回かというと、特に意味はありません。たまたまです。
第12回 オフラインリアルタイムどう書く

実行環境にFuelPHPを使用していますが、
アルゴリズムの部分はFuelPHPと切り離して書いたので、namespaceの一文削れば他でも使えるはずです。
FuelPHPはあくまで実行環境として利用した程度です。
まあついで程度で。

1. サイコロを表すDiceクラスの実装

課題の部分です。

自分の場合、下記のように書きました。
まだ回答例等見ていないのでさぶいコードでしたらすみません・・・

工夫した点としては、デザインパターンのStateパターンぽい感じに、
「現在の状態」から「次の状態」に遷移させることでサイコロの状態を表現するようにした点です。

苦労した点は、その「状態」をどういった形で表現すべきか?という点です。
で、やってみたのは、サイコロの初期状態に合わせて、「1は上」「2は北」「3は西」というルールを定義する、
という表現方法です。

もっといいやり方もあるかもしれませんが、気づいたら手が止まらなくなっていたのでこんなもんでw

dice.php
<?php

namespace Fuel\App\Object;

/**
 * サイコロを表すクラス
 */
class Dice{

    /** サイコロの方向 x 数値のマッピング */
    const TOP    = 1;
    const NORTH  = 2;
    const WEST   = 3;
    const EAST   = 4;
    const SOUTH  = 5;
    const BOTTOM = 6;

    /** 略称とのマッピング */
    private $DIRECTIONS = array(
        'N' => self::NORTH,
        'W' => self::WEST,
        'E' => self::EAST,
        'S' => self::SOUTH,
    );

    /** サイコロの状態を管理する配列 */
    private $_state = array();

    /**
     * コンストラクタ
     */
    public function __construct($top, $north, $west){

        // サイコロの初期状態を設定する
        $this->setState($top, $north, $west);
    }

    /**
     * サイコロの状態を設定する
     */
    public function setState($top, $north, $west){

        $this->_state = array(
            self::TOP    => $top,
            self::NORTH  => $north,
            self::WEST   => $west,
            self::EAST   => 7 - intval($west),
            self::SOUTH  => 7 - intval($north),
            self::BOTTOM => 7 - intval($top),
        );
    }

    /**
     * サイコロを転がす
     */
    public function shake($directions){

        // 初期配置時の上面を取得
        $result = array($this->getTop());

        for($i = 0, $len = mb_strlen($directions); $i < $len; $i++){

            // 方向を1つ取る
            $direction = mb_substr($directions, $i, 1);

            // サイコロを1マス転がす
            $next = $this->getNext($direction);

            // サイコロの状態を更新
            $this->setState(
                $next['top'],
                $next['north'],
                $next['west']
            );

            // 現在の上面を取得
            $result[] = $this->getTop();
        }

        $result = implode('', $result);

        return $result;
    }

    /**
     * サイコロが転がったときの状態を取得する
     *
     * TODO:縦と横方向に分解すればもっとリファクタできるかも
     */
    public function getNext($direction){

        $dirNum = $this->DIRECTIONS[$direction];

        // 上面を取得(上面にくるのは、転がった方向の裏側の数値)
        $top = $this->_state[7 - $dirNum];

        switch($direction){

            // 東, 西の場合、北面は変わらない
            case 'E':
                $north = $this->_state[self::NORTH];
                $west  = $this->_state[self::BOTTOM];
                break;

            case 'W':
                $north = $this->_state[self::NORTH];
                $west  = $this->_state[self::TOP];
                break;

            // 北, 南面の場合、西面は変わらない
            case 'S':
                $north = $this->_state[self::BOTTOM];
                $west  = $this->_state[self::WEST];
                break;

            case 'N':
                $north = $this->_state[self::TOP];
                $west  = $this->_state[self::WEST];
                break;
        }

        return array(
            'top'   => $top,
            'north' => $north,
            'west'  => $west,
        );
    }

    /**
     * 上面の数値を取得する
     */
    public function getTop(){

        return $this->_state[self::TOP];
    }

}

2.bootstrapでDiceクラスを定義

上記で作成したDiceクラスをFuelPHP内で実行できるように、bootstrap.phpに登録します。
下記の文を追記しました。

Autoloader::add_namespace('Fuel\\App', APPPATH.'classes/');

Autoloader::add_classes(array(
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    'Fuel\\App\\Object\\Dice' => APPPATH.'classes/object/dice.php',
));

こんな感じになりました。

bootstrap.php
<?php

// Load in the Autoloader
require COREPATH.'classes'.DIRECTORY_SEPARATOR.'autoloader.php';
class_alias('Fuel\\Core\\Autoloader', 'Autoloader');

// Bootstrap the framework DO NOT edit this
require COREPATH.'bootstrap.php';

Autoloader::add_namespace('Fuel\\App', APPPATH.'classes/');

Autoloader::add_classes(array(
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    'Fuel\\App\\Object\\Dice' => APPPATH.'classes/object/dice.php',
));

// Register the autoloader
Autoloader::register();

/**
 * Your environment.  Can be set to any of the following:
 *
 * Fuel::DEVELOPMENT
 * Fuel::TEST
 * Fuel::STAGING
 * Fuel::PRODUCTION
 */
Fuel::$env = (isset($_SERVER['FUEL_ENV']) ? $_SERVER['FUEL_ENV'] : Fuel::DEVELOPMENT);

// Initialize the framework with the config file.
Fuel::init('config.php');

3.Taskクラスの作成

TaskクラスはFuelPHPのコマンドライン用のクラスです。(バッチとかで使うやつ)
デフォルトは「run」に書かれた処理が実行されるので、runからDiceクラスのメソッドを実行するだけです。

あとは問題の下部に記載されていたテストケースを肉付け。
(本当はテストクラスを別に作成すべきですがここでは省略)

shakedice.php
<?php

namespace Fuel\Tasks;

use Fuel\App\Object\Dice;;

/**
 * サイコロを転がす実行クラス
 */
class ShakeDice {

    /** テストケースの番号 */
    private static $_caseNum = 0;

    /**
     * サイコロを振る
     */
    public static function run($directions){

        // サイコロの初期状態を設定し、インスタンスを生成
        $dice = new Dice('1', '2', '3');

        return $dice->shake($directions);
    }

    /**
     * テストケースを実行
     */
    public static function doTest(){

        /*0*/ static::test( "NNESWWS", "15635624" );
        /*1*/ static::test( "EEEE", "13641" );
        /*2*/ static::test( "WWWW", "14631" );
        /*3*/ static::test( "SSSS", "12651" );
        /*4*/ static::test( "NNNN", "15621" );
        /*5*/ static::test( "EENN", "13651" );
        /*6*/ static::test( "WWNN", "14651" );
        /*7*/ static::test( "SSNN", "12621" );
        /*8*/ static::test( "NENNN", "153641" );
        /*9*/ static::test( "NWNNN", "154631" );
        /*10*/ static::test( "SWWWSNEEEN", "12453635421" );
        /*11*/ static::test( "SENWSWSNSWE", "123123656545" );
        /*12*/ static::test( "SSSWNNNE", "126546315" );
        /*13*/ static::test( "SWNWSSSWWE", "12415423646" );
        /*14*/ static::test( "ENNWWS", "1354135" );
        /*15*/ static::test( "ESWNNW", "1321365" );
        /*16*/ static::test( "NWSSE", "154135" );
        /*17*/ static::test( "SWNWEWSEEN", "12415154135" );
        /*18*/ static::test( "EWNWEEEEWN", "13154532426" );
        /*19*/ static::test( "WNEWEWWWSNW", "145151562421" );
        /*20*/ static::test( "NNEE", "15631" );
        /*21*/ static::test( "EEEEWNWSW", "1364145642" );
        /*22*/ static::test( "SENNWWES", "123142321" );
        /*23*/ static::test( "SWWWSNSNESWW", "1245363635631" );
        /*24*/ static::test( "WESSENSE", "141263231" );
        /*25*/ static::test( "SWNSSESESSS", "124146231562" );
        /*26*/ static::test( "ENS", "1353" );
        /*27*/ static::test( "WNN", "1453" );
        /*28*/ static::test( "SSEENEEEN", "1263124536" );
        /*29*/ static::test( "NWSNNNW", "15414632" );
        /*30*/ static::test( "ESSSSSWW", "132453215" );
        /*31*/ static::test( "ESE", "1326" );
        /*32*/ static::test( "SNWNWWNSSSS", "121456232453" );
        /*33*/ static::test( "SWEESEN", "12423653" );
        /*34*/ static::test( "NEEWNSSWWW", "15323631562" );
        /*35*/ static::test( "WSEW", "14212" );
        /*36*/ static::test( "SWSNNNSNWE", "12464131353" );
        /*37*/ static::test( "ENWEWSEEW", "1351513545" );
        /*38*/ static::test( "WSEWN", "142124" );
        /*39*/ static::test( "EWNEESEWE", "1315321414" );
        /*40*/ static::test( "NESEEN", "1531263" );
        /*41*/ static::test( "WSW", "1426" );
        /*42*/ static::test( "ENEWE", "135656" );
    }

    /**
     * テストを実行し、結果を出力する
     */
    public static function test($case, $answer){

        $result = static::run($case);

        if($result !== $answer){

            print_r('[' . static::$_caseNum . '][NG] result:' . $result . "\n");
            exit();
        }

        print_r('[' . static::$_caseNum . '][OK] result:' . $result . "\n");

        static::$_caseNum++;
    }

}

4.実行

taskクラスは「oil」を使って実行します。
なので、oilがあるディレクトリまで移動。

s ls -l
合計 20
drwxrwxr-x 6 mtaji mtaji 4096  3月 24 12:39 2013 fuel
drwxr-xr-x 4 root  root  4096  3月 24 10:20 2013 myproject
-rw-rw-r-- 1 mtaji mtaji 1451  3月 24 12:37 2013 oil
-rw-r--r-- 1 root  root    24  3月 24 07:37 2013 phpinfo.php
lrwxrwxrwx 1 root  root    20  3月 24 06:50 2013 phpmyadmin -> /var/lib/phpmyadmin/
drwxrwxr-x 3 mtaji mtaji 4096  3月 24 12:37 2013 public

まずはrunクラスから1つ実行。
下記のように実行します。

s sudo php oil refine shakedice NNESWWS
15635624

大丈夫そう。

続いて、全てのテストケースを実行。
こちらはdoTestメソッドを実行します。

s sudo php sudo php oil refine shakedice:doTest
[0][OK] result:15635624
[1][OK] result:13641
[2][OK] result:14631
[3][OK] result:12651
[4][OK] result:15621
[5][OK] result:13651
[6][OK] result:14651
[7][OK] result:12621
[8][OK] result:153641
[9][OK] result:154631
[10][OK] result:12453635421
[11][OK] result:123123656545
[12][OK] result:126546315
[13][OK] result:12415423646
[14][OK] result:1354135
[15][OK] result:1321365
[16][OK] result:154135
[17][OK] result:12415154135
[18][OK] result:13154532426
[19][OK] result:145151562421
[20][OK] result:15631
[21][OK] result:1364145642
[22][OK] result:123142321
[23][OK] result:1245363635631
[24][OK] result:141263231
[25][OK] result:124146231562
[26][OK] result:1353
[27][OK] result:1453
[28][OK] result:1263124536
[29][OK] result:15414632
[30][OK] result:132453215
[31][OK] result:1326
[32][OK] result:121456232453
[33][OK] result:12423653
[34][OK] result:15323631562
[35][OK] result:14212
[36][OK] result:12464131353
[37][OK] result:1351513545
[38][OK] result:142124
[39][OK] result:1315321414
[40][OK] result:1531263
[41][OK] result:1426
[42][OK] result:135656

全てOK
大丈夫ですかね??

今回実装したソースコードはgithubに上げました。
走り書きなのでなんか気づいたら変えるかも。
https://github.com/otajisan/realtime

ご参考まで。

5.やってみた感想

予想よりだいぶ時間かかりました。
1hじゃ終わらなかったですwww
リファクタとか合わせると2hくらいかかったんじゃないかな?
もっと速く書きたい。

課題自体は「手入力した文字列をサイコロの目に変換する」というシンプルな出題ですが、
実際こういうのってゲーム作ったりとかに活かせそうだな〜とか考えながらやってました。

別の機会に、他の課題にも挑戦してみたいです。

ではまた。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3