0.きっかけ
qiitaを見ていてたまたま見かけて興味を持ったので。
なんで第12回かというと、特に意味はありません。たまたまです。
第12回 オフラインリアルタイムどう書く
実行環境にFuelPHPを使用していますが、
アルゴリズムの部分はFuelPHPと切り離して書いたので、namespaceの一文削れば他でも使えるはずです。
FuelPHPはあくまで実行環境として利用した程度です。
まあついで程度で。
1. サイコロを表すDiceクラスの実装
課題の部分です。
自分の場合、下記のように書きました。
まだ回答例等見ていないのでさぶいコードでしたらすみません・・・
工夫した点としては、デザインパターンのStateパターンぽい感じに、
「現在の状態」から「次の状態」に遷移させることでサイコロの状態を表現するようにした点です。
苦労した点は、その「状態」をどういった形で表現すべきか?という点です。
で、やってみたのは、サイコロの初期状態に合わせて、「1は上」「2は北」「3は西」というルールを定義する、
という表現方法です。
もっといいやり方もあるかもしれませんが、気づいたら手が止まらなくなっていたのでこんなもんでw
<?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',
));
こんな感じになりました。
<?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クラスのメソッドを実行するだけです。
あとは問題の下部に記載されていたテストケースを肉付け。
(本当はテストクラスを別に作成すべきですがここでは省略)
<?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くらいかかったんじゃないかな?
もっと速く書きたい。
課題自体は「手入力した文字列をサイコロの目に変換する」というシンプルな出題ですが、
実際こういうのってゲーム作ったりとかに活かせそうだな〜とか考えながらやってました。
別の機会に、他の課題にも挑戦してみたいです。
ではまた。