第9回 jQuery UI Sortableで1ラインオセロゲーム – jQuery入門

2009 年 5 月 14 日 投稿者: naga3

jQuery入門の第9回目は、jQuery UIを使ってみます。jQuery UIは、jQuery要素に対してドラッグ&ドロップ・リサイズ・選択などの基本機能、さらにダイアログ・タブ・スライダーと言った多様なUIを追加してくれるプラグインです。その中で今回は、要素をドラッグして並び替えを可能にする「Sortable」を使ってみましょう。作るものはオセロ・・・はソースが長くなるので1行だけのオセロゲームです。

1ラインオセロゲーム

コマをドラッグして移動すると、普通のオセロのようにコマが裏返ります。決められた手数以内で全てのコマを黒くするとクリアです。全5面でだんだん難しくなります。

1ラインオセロのソース

ファイルが必要ならば右クリックで保存してください。
HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery", "1.3.2");</script>
<script type="text/javascript">google.load("jqueryui", "1.7.1");</script>
<script type="text/javascript">
$(function() {
    var round = 0;
    var remain;
    var field = [
        [1, "●○●○○"],
        [1, "●○○○●○●"],
        [2, "●○●○●○●○●"],
        [2, "●○●●○●●●○"],
        [2, "●●○●●○○●●●○●○●●"]
    ];

    // 面の開始
    function start() {
        remain = field[round][0];
        $('#field')
        .empty()
        .html($.map(field[round][1].split(''), function(i) {
            return '<div>' + i + '</div>';
        }).join(''));

        $('#round').text(round + 1);
        $('#remain').text(remain);
    }
    start();

    $('#field').sortable({
        axis: 'x',
        update: function(e, u) {

            // 裏返す
            var color = u.item.text();
            $.each(['prev', 'next'], function(i, dir) {
                var rev = false;
                (function (item) {
                    if (item.length < 1) return;
                    if (item.text() == color) rev = true;
                    else arguments.callee(item[dir]());
                    if (rev) item.text(color);
                })(u.item[dir]());
            });

            // クリアチェック
            $('#remain').text(--remain);
            if (!$('#field div').is(':contains("○")')) {
                if (++round >= field.length) {
                    alert("オールクリア!");
                } else {
                    alert("クリア!");
                    start();
                }
            }

            // ゲームオーバーチェック
            if (remain <= 0) {
                $('#field').sortable('destroy');
                alert("ゲームオーバー");
            }
        }
    });
});
</script>
<style type="text/css">
div {
    float: left;
    cursor: pointer;
}
</style>

</head>
<body>
<p>全て●にせよ! 第<span id="round"></span>面 残り<span id="remain"></span>手</p>
<div id="field" />
</body>
</html>

プログラムの解説

<script type="text/javascript">google.load("jqueryui", "1.7.1");</script>
GoogleでホスティングされているjQuery UIのバージョン1.7.1を読み込んでいます。

変数定義

var round = 0;
現在の面数です(実際の面数は+1される)。
var remain;
残り手数です。これが0になるとゲームオーバー。
var field = [
    [1, "●○●○○"],
    [1, "●○○○●○●"],
    [2, "●○●○●○●○●"],
    [2, "●○●●○●●●○"],
    [2, "●●○●●○○●●●○●○●●"]
];
各面の配置データ配列です。上から順に1~5面のデータで、それぞれの要素がさらに配列になっており、最初の要素が手数、次の要素が実際の配置データです。

各面のHTML書き出し

$('#field')
.empty()
.html($.map(field[round][1].split(''), function(i) {
    return '<div>' + i + '</div>';
}).join(''));
配列fieldから実際のHTMLを書き出している部分です。まず前の面のデータが残っているのでemptyで空にし、その後各文字(●か○)を1文字ずつdivで囲っています。$.mapはオブジェクトや配列の要素それぞれについて指定した関数を実行するjQueryのユーティリティ関数です。

並び替え関数

$('#field').sortable({・・・});
この部分で要素の並び替えを設定しています。並び替えの対象になる要素は#field以下の要素全て、つまり先ほどdivで囲った○,●文字全てです。引数のハッシュで並び替え動作の設定を行ないます。
axis: 'x',
ドラッグを横方向のみに制限しています。
update: function(e, u) {・・・}
ドラッグが完了して、実際に要素が並び替えられたときに呼ばれる関数を設定します。要素が並び替えられないときは関数が呼ばれないので注意してください。引数のeはイベントオブジェクト、uは各種オプションですが、今回使っているのはドラッグされた要素を表すu.itemだけです。

コマを裏返す

並び替えが完了したときに、コマを裏返す処理が実行されるわけですが、結構面倒な処理なので、今回は再帰を使ってなるべくシンプルに実装してみました。
var color = u.item.text();
まず最初に裏返すときのチェック用に、ドラッグされた要素のテキストを保存します。
$.each(['prev', 'next'], function(i, dir) {・・・}
$.eachもjQueryのユーティリティ関数で、第1引数で指定したオブジェクトや配列のそれぞれの要素に対して関数を実行します。引数iには配列のインデックス、dirには値が入ります。
(function (item) {・・・})(u.item[dir]());
JavaScriptの独特な書き方です。「function(i) {・・・}(x)」という書き方で、匿名関数を定義して、さらに実引数xで実行までを行なうのです。ここでは実引数「u.item[dir]()」が「item」に代入されて実行されます。
「u.item[dir]()」の部分ですが、dirには先ほどの$.eachによって「prev」もしくは「next」の文字が入るので、「u.item['prev']()」「u.item['next']()」となるわけですが、JavaScriptのオブジェクトの各メンバは、ハッシュとしてアクセスすることができます。つまり「u.item.prev()」「u.item.next()」となるわけです。prev()は要素のひとつ前の兄弟要素、nextは要素のひとつ後の兄弟要素を辿るjQueryのメソッドです。
if (item.length < 1) return;
兄弟要素が存在するときは、要素の長さlengthは「●」か「○」で必ず1になるハズなので、ここの条件が満たされるのは先頭か最後にたどり着いたときです。
if (item.text() == color) rev = true;
先頭か最後にたどり着く前に、置いたコマと同じ色のコマが現れたときは、挟める状態となるのでフラグを立てています。
else arguments.callee(item[dir]());
置いたコマと違う色のコマが現れたときは、裏返される可能性のあるコマですが、とりあえず置いておき次の兄弟要素のコマを再帰によって辿ります。
if (rev) item.text(color);
フラグが立っていたならば、さきほどとりあえず置いておいたコマを裏返します。

クリア・ゲームオーバー

if (!$('#field div').is(':contains("○")')) {・・・}
isは、jQueryの要素に対して、引数で指定したセレクタに合致する要素がひとつでもある場合にtrueになるメソッドです。:containsは、要素のテキストに指定した文字列が含まれているものを選択するセレクタです。ここでは、白丸がひとつも含まれていない場合、ifの条件が満たされ、クリアとなります。
$('#field').sortable('destroy');
ゲームオーバーのとき、並び替えを無効にしています。

この記事へのトラックバックURL

コメントをどうぞ