[PHP]ライフゲームを作る

lifegame

ライフゲームとは数学的なシミュレーションで、次のルールに従ってセルの状態が変化します。

セル(一つのマス)が生きていて周囲に生きているセルが 1 以下(過疎)のときと 4 以上の時(過密)は消滅し、2 か 3 の時は生存します。

また、周囲に生きているセルが 3 つある時、そこに生きているセルが誕生します。

<?php
session_start();

function check_around($cell, $cell_x, $cell_y){
	$count = 0;
	for($y=-1;$y<=1;$y++){
		for($x=-1;$x<=1;$x++){
			if($x == 0 && $y == 0) continue;
			if(
				isset($cell[$cell_x + $x][$cell_y + $y]) &&
				$cell[$cell_x + $x][$cell_y + $y] == 1
			){
				$count++;
			}
		}
	}
	
	$result = 0;
	
	$current = $cell[$cell_x][$cell_y];
	
	if($current == 1 && $count <= 1) $result = 0;
	if($current == 1 && $count >= 4) $result = 0;
	if($current == 1 && ($count == 2 || $count == 3)) $result = 1;
	if($current == 0 && $count == 3) $result = 1;

	return $result;
}

$cell = array();

$size_x = 20;
$size_y = 20;

$reset = (!isset($_SESSION['cell'])) ? true : false;

if($reset === true || empty($_SESSION['cell'])){
	$_SESSION['cell'] = array();
	for($y=0;$y<$size_y;$y++){
		for($x=0;$x<$size_x;$x++){
			$cell[$x][$y] = mt_rand(0,1);
		}
	}
	
	
} else {
	$cell = $_SESSION['cell'];
}

if($reset !== true){
	$next_cell = array();
	for($y=0;$y<$size_y;$y++){
		for($x=0;$x<$size_x;$x++){
			$next_cell[$x][$y] = check_around($cell, $x, $y);
		}
	}
	$cell = $next_cell;
}

$_SESSION['cell'] = $cell;

header("Content-type:text/html;charset=utf-8");

for($y=0;$y<$size_y;$y++){
	for($x=0;$x<$size_x;$x++){
		if($cell[$x][$y] == 0){
			echo "□";
		} else {
			echo "■";
		}
	}
	echo "<br />\n";
}

あまり凝った組み方ではないですが、基本的なルールに従って進行します。
セッションを使っているのでブラウザを閉じるとリセットされ、ページを更新するごとに世代が変わります。
実験はローカルサーバーで行なって下さい。共用サーバーで更新ボタンを連打しないようにお願いします。

本来こういったものはタイマーで画面が書き換わるタイプの言語で組むのが一般的ですが、PHP で組むと、訪問者があるごとに世代を進行させたり、アクセス毎に餌のようなものを加えてみる等の処理ができて面白いのかもしれません。

Javascript と Canvas の組み合わせでリアルタイムで動作するタイプはこちらの記事で解説しています。

密集の度合いに応じて色を付けてみるのもなかなか綺麗です。

life

[PHP]生年月日(誕生日)から星座を調べる

誕生日を入力して占いなどで使われる12星座を割り出すプログラムです。
自動的に変換されるので生年月日(YYYY-MM-DD)で入力しても構いません。
厳密に日付の書式を定めるなら new DateTime() の代わりに DateTime::createFromFormat() を利用して下さい。

<?php
//誕生日
$birthday = "8/20";
$datetime = new DateTime($birthday);
echo getZodiacFromDateTime($datetime);

function getZodiacFromDateTime($datetime){
	$m = (int)$datetime->format("n");
	$d = (int)$datetime->format("j");
	 
	$zodiacs = array(
	// '名前', 月, 日 ~ 月, 日
		array('牡羊座',  3, 21,  4, 19),
		array('牡牛座',  4, 20,  5, 20),
		array('双子座',  5, 21,  6, 21),
		array('かに座',  6, 22,  7, 22),
		array('獅子座',  7, 23,  8, 22),
		array('乙女座',  8, 23,  9, 22),
		array('天秤座',  9, 23, 10, 23),
		array('蠍座',   10, 24, 11, 22),
		array('射手座', 11, 23, 12, 21),
		array('山羊座', 12, 22,  1, 19),
		array('水瓶座',  1, 20,  2, 18),
		array('魚座',    2, 19,  3, 20)
	);
	 
	foreach($zodiacs as $zodiac){
		list($name, $start_m, $start_d, $end_m, $end_d) = $zodiac;
		if(
			($m === $start_m && $d >= $start_d) ||
			($m === $end_m && $d <= $end_d)
		){
			return $name;
		}
	}
	return false;
}

結果:

獅子座

関数に DateTime オブジェクトを渡すと星座の名前を返します。
プログラム自体はシンプルに黄道十二星座の期間を格納した配列によって条件分岐させています。

[PHP]よくあるエラーメッセージの原因と対処法 2

前回の続きです。

PHP でよくあるエラーメッセージの意味と解決方法を具体例とともに紹介します。


Notice: Undefined offset: (キー)

<?php
$arr = array("test", "sample");
echo $arr[2];

配列変数で、定義されていないキーを指定した時に起こるエラーです。
例ではキー 0 と 1 は値が定義されていますが、2 は未定義です。


Warning: Invalid argument supplied for foreach()

<?php
$arr = "";

foreach($arr as $value){
	echo $value;
}

foreach が扱えるのは配列変数等のオブジェクトに限られます。(厳密にはイテレータを持つオブジェクト)
文字列や null、 false ではエラーが発生します。
例のようにわかりやすければいいのですが、たいていのプログラムは次のようになっていることが多いかと思います。

<?php
$result = get_results();

foreach($result as $value){
  echo $value;
}

仮に何らかの結果を配列で返すユーザー定義関数 get_results があったとして、
結果がない場合に空の配列を返すのであれば問題ないのですが、
もし一つも結果が得られなかったときに false や null を返す仕様になっていた場合、
このエラーが起きる可能性があります。


Warning: Cannot modify header information – headers already sent

<?php
echo "sample";
header("Location: index.php");
exit;

指定したページに移動させるとき、header() で Location を指定するのですが、
ヘッダーを出力するよりも前に何らかの文字等が出力されているとエラーが起こります。
header() より前に echo をしていたり、<?php の前に無駄な文字や改行などが無いか確認して下さい。
setcookie() を使う場合も、使用している行より前に他のヘッダーや文字の出力を行わないように気をつける必要があります。
文字コード UTF-8 の BOM(Byte Order Mark) が原因で起こる可能性もあります。
その場合は BOM なしで保存できるテキストエディタを利用して UTF-8N で保存して下さい。

<?php include "test.php";?>
<?php
header("Location: index.php");
exit;
?>

上の例では一見無駄な文字は無いように見えますが、1行目の末尾で改行を出力しているとみなされ、エラーになる場合があります。


Notice: A session had already been started

<?php
session_start();
session_start();

例はやや極端ですが、既にセッションが開始されている時、session_start() することはできません。
サーバー側の php.ini 等で session.auto_start が有効になっていると、session_start() していなくても
自動的にセッションがスタートしていることがあります。また、include しているファイルの中でも session_start() が使われていないか確認して下さい。

<?php
if( !isset($_SESSION) ) {
  session_start();
}

セッションが開始されていれば重ねてスタートしないようにすれば回避出来ます。


Fatal error: Cannot access private property

<?php
class Test {
	private $sample;
}

$test = new Test();
$test->sample = 10;

private で保護されたプロパティ(クラス内の変数)には直接アクセスできません。
public で宣言すれば可能ですが、クラス内に操作するためののメソッド(セッターメソッド又はゲッターメソッド)がないか確認して下さい。


Fatal error: Maximum execution time of 30 seconds exceeded

具体例は省略しますが、処理に 30 秒以上(環境によります)かかってしまうような重い処理や、
無限ループができてしまっている場合にこのようになる場合があります。
php.ini の max_execution_time を必要な秒数に変更するか、phpファイルに

ini_set("max_execution_time", 120);

あるいは

set_time_limit(120);

のように書いてタイムアウト時間を変更します

共用サーバーなどでタイムアウト時間の変更に制限がある場合があるので気をつけて下さい。
また、処理に長い時間のかかるスクリプトの実行はサーバーに高い負荷をかけるため、
可能な限りスクリプト側の実行時間を短く出来るように改良するのが理想です。
あくまで大型プログラムのインストール時など、一時的に時間が掛かるケースでのみ変更して下さい。


Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32 bytes)

サイズの大きなファイルを開こうとした場合や画像のリソースなどを開放し忘れている場合など、サーバーが確保した容量が不足している場合に表示されるメッセージです。数値はサーバーの設定により異なります。
画像ファイルのリソースを開放する必要がある場合は imagedestroy() で開放します。
サーバー側の上限を変更するにはサーバーの php.ini にある memory_limit を修正するか、ソースコードに ini_set() を記述して上限を設定します。(例は 512MB の場合)

ini_set('memory_limit', '512M');

あまり大きな数値は設定できない可能性があります。また、極端にメモリを消費する場合はプログラムの書き方を見なおしたほうがいいかもしれません。