めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

JSF2.0でMineSweeper

定期的にMineSweeperを実装したくなる病で・・・、JSF2.0(with JBoss)でやりました。

マス目のゲーム盤の表示でちょっと手間取ったので、自分用にメモ書きを残しておきます。

メインのFaceletsはこれ。
index.xhtml

<!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://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:ui="http://java.sun.com/jsf/facelets">

<ui:composition template="templates/masterLayout.xhtml">

  <ui:define name="windowTitle">#{msgs.windowTitle}</ui:define>

  <ui:define name="head">
    <h:outputStylesheet library="css" name="styles.css" />
  </ui:define>

  <ui:define name="content">
    <h:form id="form1">
      <h:panelGroup id="field">
        <div id="clickModeIndicator">
          <h:panelGroup id="clickMode">
            <f:ajax event="click" listener="#{mineField.clickModeChange}"
              render="clickMode">
              <h:outputLabel value="Click to [OPEN]  MARK "
                rendered="#{mineField.clickMode == 'OPEN'}">
              </h:outputLabel>
              <h:outputLabel value="Click to  OPEN  [MARK]"
                rendered="#{mineField.clickMode == 'MARK'}">
              </h:outputLabel>
            </f:ajax>
          </h:panelGroup>
        </div>
        <div>
          <table border="0" cellspacing="0" cellpadding="0">
            <ui:repeat value="#{mineField.rows}" var="row">
              <tr>
                <ui:repeat value="#{row}" var="item">
                  <td><h:outputLabel value="#{item.toString()}">
                      <f:attribute name="location" value="#{item.locationCode}" />
                      <f:ajax event="click" listener="#{mineField.clickPanel}"
                        execute="@form" render=":form1:field" />
                    </h:outputLabel></td>
                </ui:repeat>
              </tr>
            </ui:repeat>
          </table>
        </div>

        <div id="messages">
          <h:outputText value="Watch out!"
            rendered="#{mineField.status == 'PLAY'}" />
          <h:outputText value="Congraturations!"
            rendered="#{mineField.status == 'FINISHED'}" />
          <h:outputText value="Game Over"
            rendered="#{mineField.status == 'FAILED'}" />
        </div>
      </h:panelGroup>
      <br/>
      <div>
        <h:commandButton action="#{mineField.replay}" value="Replay" />
        <h:commandButton action="setup" value="Settings..." />
      </div>
    </h:form>

  </ui:define>

</ui:composition>
</html>

の2重ループでゲーム盤のテーブルを作っています。

          <table border="0" cellspacing="0" cellpadding="0">
            <ui:repeat value="#{mineField.rows}" var="row">
              <tr>
                <ui:repeat value="#{row}" var="item">
                  <td><h:outputLabel value="#{item.toString()}">
                      <f:attribute name="location" value="#{item.locationCode}" />
                      <f:ajax event="click" listener="#{mineField.clickPanel}"
                        execute="@form" render=":form1:field" />
                    </h:outputLabel></td>
                </ui:repeat>
              </tr>
            </ui:repeat>
          </table>

外側のループで#{mineField.rows}を返しているBacking Beanのgetterがこれ。
MineField.java

    public List<List<PanelWrapper>> getRows() {
        List<List<PanelWrapper>> rows = new ArrayList<List<PanelWrapper>>();
        for (int y = 0; y < getSizeVertical(); y++) {
            List<PanelWrapper> row = new ArrayList<PanelWrapper>();
            for (int x = 0; x < getSizeHorizontal(); x++) {
                PanelWrapper panelWrapper = new PanelWrapper();
                panelWrapper.setPanel(getPanel(x, y));
                panelWrapper.setLocationCode(x + "," + y);
                row.add(panelWrapper);
            }
            rows.add(row);
        }
        return rows;
    }

PanelWrapperは、1枚のパネルについて、パネルの絵柄(Panel)と座標情報(LocationCode)をまとめたオブジェクト。ゲーム盤全体のパネルに対応するPanelWrapperのリストのリストを作って返しています。

の中でを使う際は、謎のお約束があるようです。次のように、「execute="@form"」が必要で、renderターゲットはrootからID指定が必要(「redner=":form1:field"」)。

                  <td><h:outputLabel value="#{item.toString()}">
                      <f:attribute name="location" value="#{item.locationCode}" />
                      <f:ajax event="click" listener="#{mineField.clickPanel}"
                        execute="@form" render=":form1:field" />
                    </h:outputLabel></td>

を使って、各パネルの座標情報を埋め込んであります。パネルをクリックすると、このAjaxのlistenerである#{mineField.clickPanel}が処理を行います。次のように、eventオブジェクトからattributeを取り出しています。

    public void clickPanel(AjaxBehaviorEvent event) throws AbortProcessingException {
        UIComponent component = event.getComponent();
        String location = (String) component.getAttributes().get("location");
        Pattern p = Pattern.compile("[0-9]+,[0-9]+");
        if (!p.matcher(location).matches()) {
            clickModeChange(event);
            return;
        }
        String[] coordinate = location.split(",", 0);
        int locationHorizontal = Integer.valueOf(coordinate[0]);
        int locationVertical = Integer.valueOf(coordinate[1]);
(以下略)