スライドパズル(JSF)

JavaServer? Facesで4×4のスライドパズルを作成する。
ピースをクリックすると、ピースの隣が空きマスの場合にピースが空きマスに移動する。

まず、ManagedBean?を作成する。
パズルの途中の状態をサーバー側に保持しておくために、
クラスに@SessionScoped?(セッションが有効な間使用可能)アノテーションを付加しておく。

ピースの番号を左上から横方向に順に0,1,2,・・・,15とする。
ただし、パズルを解いている間はピース0のみ空きマスとする。
パズル上の現在のピースの番号を格納する4×4の2次元配列blocksとそのgetterを作成する。
また、パズルが解けたかどうかを表す変数finishedとそのgetterを作成する。

ピースをシャッフルするshuffle()メソッドとピースを動かすmove()メソッドを作成する。

shuffle()メソッドはピースの初期化後に空きマスに上下左右のピースをランダム移動して行き、finishedをfalseにする。
@PostConstruct?アノテーションを付加し最初に表示されるときにピースがシャッフルされているようにする。
シャッフルの方法によっては永遠に解けないパズルが出来上がるのでアルゴリズムに注意する。

move()メソッドはピースの上下左右に空きマスがあるか調べ、空きマスがあればその方向にピースを動し、
パズルが解けていればfinishedをtrueにする。

これらのメソッドは、指定位置のピースの番号を取得(配列の範囲外で取得できない場合は-1)するgetメソッド、
指定位置のピースの番号を設定するsetメソッド、ピースを入れ替えるswapメソッド、
パズルが解けているかチェックするcheckFinishedメソッドを使用している。

import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import java.io.Serializable;
import java.util.Random;
import javax.annotation.PostConstruct;
@Named(value = "puzzle")
@SessionScoped
public class Puzzle implements Serializable {
   private static final int SIZE = 4;
   private final int[][] blocks = new int[SIZE][SIZE];
   private boolean finished;
   public Puzzle() {
   }
   // getter省略
   private int get(int x, int y) {
       if (x < 0 || x >= SIZE || y < 0 || y >= SIZE) {
           return -1;
       }
       return blocks[x][y];
   }
   private void set(int x, int y, int value) {
       blocks[x][y] = value;
   }
   @PostConstruct
   public void shuffle() {
       for (int i = 0; i < SIZE; i++) {
           for (int j = 0; j < SIZE; j++) {
               set(j, i, i * SIZE + j);
           }
       }
       Random r = new Random();
       int x = 0;
       int y = 0;
       for (int i = 0; i < 1000; i++) {
           switch (r.nextInt(4)) {
               case 0:
                   if (get(x - 1, y) > 0) {
                       swap(x, y, x - 1, y);
                       x = x - 1;
                   }
                   break;
               case 1:
                   if (get(x + 1, y) > 0) {
                       swap(x, y, x + 1, y);
                       x = x + 1;
                   }
                   break;
               case 2:
                   if (get(x, y - 1) > 0) {
                       swap(x, y, x, y - 1);
                       y = y - 1;
                   }
                   break;
               case 3:
                   if (get(x, y + 1) > 0) {
                       swap(x, y, x, y + 1);
                       y = y + 1;
                   }
                   break;
           }
       }
       finished = false;
   }
   public void move(int x, int y) {
       if (!finished) {
           if (get(x - 1, y) == 0) {
               swap(x, y, x - 1, y);
           } else if (get(x + 1, y) == 0) {
               swap(x, y, x + 1, y);
           } else if (get(x, y - 1) == 0) {
               swap(x, y, x, y - 1);
           } else if (get(x, y + 1) == 0) {
               swap(x, y, x, y + 1);
           }
           if (checkFinished()) {
               finished = true;
           }
       }
   }
   private boolean checkFinished() {
       for (int i = 0; i < SIZE; i++) {
           for (int j = 0; j < SIZE; j++) {
               if (get(j, i) != i * SIZE + j) {
                   return false;
               }
           }
       }
       return true;
   }
   private void swap(int x1, int y1, int x2, int y2) {
       int v1 = get(x1, y1);
       int v2 = get(x2, y2);
       set(x1, y1, v2);
       set(x2, y2, v1);
   }
}

続いて、パズルの画面をFaceletsで作成する。
事前に、4×4に分割した16枚の画像と空きマス用の画像をすべて同一サイズで作成し、
プロジェクト名/プロジェクト名-war/webにフォルダ(ここではimage)を作成して格納しておく。

#ref(): File not found: "sp1.jpg" at page "スライドパズル(JSF)"

各ピースを表に配置する。ピースとピースの間に隙間を作らないために、tableタグにcellpadding="0" cellspacing="0"を指定している。

c:forEachタグとtrタグ(表の縦区切り)、tdタグ(表の横区切り)を使用して16個のピースをテーブルに配置する。
c:forEach var="i" begin="0" end="3"はfor (int i = 0; i <= 3; i++)の意味である。

各ピースはボタンに画像を貼り付けて作成する。
貼り付ける画像は、blocksに格納されているピースの番号に対応する画像を使用する。
ただし、パズルを解いている途中でピースの番号が0の場合は空きマス用の画像を使用するようにする。
なお、style="vertical-align:bottom"は表に隙間を作らないために指定している。

各ボタン(ピース)が押されたときに、move()メソッドを呼び出しピースを空きマスに移動するようにする。
ここではf:ajaxタグを使用してページ全体を再読みこみせずに、画面を書き換えるようにする。

また、ボタンが押されたときにshuffle()メソッドを呼び出してピースをシャッフルするシャッフルボタンを作成する。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:h="http://xmlns.jcp.org/jsf/html"
     xmlns:f="http://xmlns.jcp.org/jsf/core"
     xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
   <h:head>
       <title>スライドパズル</title>
   </h:head>
   <h:body>
       <h:form>
           <table cellpadding="0" cellspacing="0">
               <c:forEach var="i" begin="0" end="3">
                   <tr>
                       <c:forEach var="j" begin="0" end="3">
                           <td>
                               <h:commandButton action="#{puzzle.move(j, i)}"
                                                image="image/pz#{(not puzzle.finished) and (puzzle.blocks[j][i] eq 0) ? 'bg' : puzzle.blocks[j][i]}.jpg"
                                                style="vertical-align:bottom">
                                   <f:ajax render="@form"/>
                               </h:commandButton>
                           </td>
                       </c:forEach>
                   </tr>
               </c:forEach>
           </table>
           <h:commandButton value="シャッフル" action="#{puzzle.shuffle()}">
               <f:ajax render="@form"/>
           </h:commandButton>
       </h:form>
   </h:body>
</html>

アルゴリズムの改良は大歓迎である。

コメント



トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS