[PHP]クラスとオブジェクト指向の練習(タロットカード占い)

クラスオブジェクトには変数と関数を両方を持たせることができ、オブジェクトの役割や性質を表現できます。
今回はタロットカードの一枚一枚をオブジェクトとして扱い、簡単な占いゲームのようなものを作ってみます。

カードには次のようなプロパティ(性質・役割)があります。

・カード名
・正位置での意味
・逆位置での意味
・位置

あまりタロットカードに馴染みのない人もいると思うので簡単に解説すると、カードをシャッフルする際テーブルに散らしてかき混ぜるようにシャッフルするため、カードの向きが上下バラバラに混ざります。正しい向きを「正位置」、逆さまの状態を「逆位置」といい、向きによってカードの意味が変わります。(区別しないこともあります)
ですので一枚のカードにつき上記のプロパティを持たせる必要があります。

サンプルでは山札をシャッフルしてカードを3枚引きます。
それぞれが「過去」「現在」「未来」を象徴するという単純なものです。(Three Card Spreads)

ファイルは3つで、メインとなる「index.php」とカード操作を行う「TarotReading.php」、カードの情報を格納する「TarotCard.php」です。

index.php

<?php
require_once dirname(__FILE__) . "/TarotReading.php";
require_once dirname(__FILE__) . "/TarotCard.php";

$tarot = new TarotReading();

// カードを用意 TarotCard(名前, 正位置での意味, 逆位置での意味)
$deck = array(
	new TarotCard('The Fool',           '自由・無邪気', '軽率・愚行'),
	new TarotCard('The Magician',       '始まり・才能', '無気力・空回り'),
	new TarotCard('The High Priestess', '知性・平常心', 'わがまま・不安定'),
	new TarotCard('The Empress',        '豊穣・繁栄', '挫折・嫉妬'),
	new TarotCard('The Emperor',        '支配・強い責任感', '傲慢・勝手'),
	new TarotCard('The Hierophant',     '慈悲・協調性', '束縛・独りよがり'),
	new TarotCard('The Lovers',         '絆・情熱', '失恋・不道徳'),
	new TarotCard('The Chariot',        '勝利・成功', '暴走・失敗'),
	new TarotCard('Strength',           '意思・勇気', '甘え・優柔不断'),
	new TarotCard('The Hermit',         '深慮・単独行動', '無計画・閉鎖的'),
	new TarotCard('Wheel of Fortune',   'チャンス・幸運', '不運・悪化'),
	new TarotCard('Justice',            '公平・善行', '不正・偏向'),
	new TarotCard('The Hanged Man',     '忍耐・努力', 'やせ我慢・徒労'),
	new TarotCard('Death',              '破滅・死', '再生・再出発'),
	new TarotCard('Temperance',         '調和・自制', '浪費・生活の乱れ'),
	new TarotCard('The Devil',          '欲望・堕落', '意地・悪循環'),
	new TarotCard('The Tower',          '崩壊・悲劇', '緊迫・不慮の災い'),
	new TarotCard('The Star',           '希望・ひらめき', '失望・高望み'),
	new TarotCard('The Moon',           '不安定・潜在的な危険', '徐々に好転・漠然とした希望'),
	new TarotCard('The Sun',            '祝福・誕生', '衰退・落胆'),
	new TarotCard('Judgement',          '復活・発展', '悔恨・行き詰まり'),
	new TarotCard('The World',          '完全・成就', '未完成・臨界点')
);

$tarot->set_deck($deck);

// シャッフル
$tarot->shuffle_deck(true);

// カードを引く
$max = 3;	// 枚数

$result = array();
for($i=0;$i<$max;$i++){
	$card = $tarot->draw_card();	// カードを引く
	
	// 山札が切れた時
	if($card === false){
		$tarot->reset_deck();
		$tarot->shuffle_deck(true);
		$card = $tarot->draw_card();
	}
	
	$result[$i] = $card;
}

$header = array('過去', '現在', '未来');	// 見出し

function h($str){
	return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

header("Content-type: text/html;");
?>
<!DOCTYPE html>
<head>
<title>TarotReading</title>
<meta charset="utf-8">
</head>
<body>
<table border="1">
<?php
for($i=0;$i<$max;$i++){
    $position = ($result[$i]->position == 'reversed') ? '逆位置' : '正位置';
	$format = <<<EOD
	<tr>
	  <th>%s</th>
	  <td>%s</td>
	  <td>%s</td>
	  <td>%s</td>
	</tr>
	
EOD;
	printf($format, h($header[$i]), h($result[$i]->name), h($position),
		h($result[$i]->get_meaning()));
}
?>
</table>
</body>
</html>

TarotReading.php

<?php
class TarotReading
{
	private $deck;			// 山札
	private $originalDeck;	// 初期化用の山札
	
	// 山札を用意
	function set_deck( $cards ){
		$this->deck = $cards;
		$this->originalDeck = $cards;
	}
	
	// 山札をリセット
	function reset_deck(){
		$this->deck = $this->originalDeck;
	}
	
	// カードをシャッフル
	function shuffle_deck($shufflePosition=false){
		shuffle($this->deck);
		
		// 位置をシャッフル
		if($shufflePosition){
			foreach($this->deck as &$card){
				$card->position = (mt_rand(0, 1)) ? 'upright' : 'reversed';
			}
		}
	}
	
	// カードを引く
	function draw_card(){
		if( count($this->deck) == 0 ) return false;
		return array_pop($this->deck);
	}
}

TarotCard.php

<?php
class TarotCard
{
	public $name;					// カード名
	public $upright;				// 正位置での意味
	public $reversed;				// 逆位置での意味
	public $position = 'upright';	// 位置(正位置・逆位置)

	function __construct($name, $upright, $reversed=""){
		$this->name     = $name;
		$this->upright  = $upright;
		$this->reversed = $reversed;
	}
	
	// 位置に応じた意味を取得
	function get_meaning($ignorePosition=false){
		if($ignorePosition){
			return $this->upright;
		} else {
			return ($this->position == 'upright') ? $this->upright : $this->reversed;
		}
	}
}

出力例:

過去 「The Magician(正位置)」 始まり・才能
現在 「The Empress(逆位置)」 挫折・嫉妬
未来 「The High Priestess(逆位置)」 わがまま・不安定

あまり明るい未来じゃなさそうですね。

TarotCard クラスに画像ファイルに関するプロパティを持たせて結果にカード画像を表示するとよりそれらしくなると思います。今回は Three Card Spreads という単純なスプレッド(カード配置・占い方)を採用しましたが、カードを引く枚数を変えれば他のスプレッド用にも改造できます。
タロットの結果はあくまで断片的なキーワードであり、それをどう読み解くかは人間の仕事ですが、雰囲気としては楽しめると思います。

参考: Wikipedia 「大アルカナ」

[PHP]ディレクトリー(フォルダ)の階層構造を維持したまま圧縮する

ZipArchive クラスを使った単純なファイルの圧縮については前回の記事に書きましたが、ディレクトリーを対象に、そのサブディレクトリも含めて圧縮するには再帰的に処理する工夫が必要です。

<?php
// 圧縮するディレクトリー
$dir = dirname(__FILE__) . '/base/';

// Zipファイルの保存先
$file = './test.zip';

zipDirectory($dir, $file);


// ディレクトリを圧縮する
function zipDirectory($dir, $file, $root=""){
	$zip = new ZipArchive();
	$res = $zip->open($file, ZipArchive::CREATE);

	if($res){
		// $rootが指定されていればその名前のフォルダにファイルをまとめる
		if($root != "") {
			$zip->addEmptyDir($root);
			$root .= DIRECTORY_SEPARATOR;
		}

		$baseLen = mb_strlen($dir);
		
		$iterator = new RecursiveIteratorIterator(
			new RecursiveDirectoryIterator(
				$dir,
				FilesystemIterator::SKIP_DOTS
				|FilesystemIterator::KEY_AS_PATHNAME
				|FilesystemIterator::CURRENT_AS_FILEINFO
			), RecursiveIteratorIterator::SELF_FIRST
		);

		$list = array();
		foreach($iterator as $pathname => $info){
			$localpath = $root . mb_substr($pathname, $baseLen);
		
			if( $info->isFile() ){
				$zip->addFile($pathname, $localpath);
			} else {
				$res = $zip->addEmptyDir($localpath);
			}
		}

		$zip->close();
	} else {
		return false;
	}
}

ディレクトリ内のファイル一覧の作り方に関しては過去の記事のものをもとに少し変更してあります。
関数を使う際は引数として (ディレクトリパス, Zipファイル保存先, 親ディレクトリの名前) を指定します。親ディレクトリの名前は省略可で、指定するとその名前のフォルダがZipファイル内に作られ、ファイルは親フォルダ以下にまとめられます。

zip_root

[PHP]ZipArchiveクラスを使ったファイルのZip圧縮と展開(解凍)

PHP5.2 以上で、zip Extension が利用可能な環境であればZIPファイル圧縮や展開は次の方法で簡単にできます。

圧縮

<?php
// 圧縮するファイルの配列
$files = array('file1.txt', 'file2.txt');

$zip = new ZipArchive();
$res = $zip->open('./test2.zip', ZipArchive::CREATE);

if($res === true){
	foreach($files as $file){
		$zip->addFile($file);
	}
	$zip->close();
} else {
	echo 'Error Code: ' . $res;
}

展開

<?php
// 圧縮ファイルのパス
$file = './test.zip';

// 展開先ディレクトリ
$to = './test/';

$zip = new ZipArchive();
$res = $zip->open($file);

if($res === true){
	$zip->extractTo($to);
	$zip->close();
} else {
	echo 'Error Code: ' . $res;
}

ZipArchive オブジェクトにファイルを追加するには addFile() を使います。
ファイルではなく文字列からファイルを作成して追加する場合は addFromString(ファイル名, 内容) で行います。

$zip->addFromString("sample.txt", "Hello, World!");

作成した ZIP ファイルをダウンロードさせる場合は次のようにします。

<?php
$file = './test.zip';
header('Content-Type: application/octet-stream'); 
header(sprintf('Content-Disposition: attachment; filename="%s"', basename($file)) ); 
header(sprintf('Content-Length: %d', filesize($file)) );
readfile($file);

今回はファイルのみを圧縮しましたが、フォルダーをディレクトリ構造を維持したまま圧縮する方法についてはこちらの記事にまとめてあります。