JavaScriptでシェフィを実装する(仮UI作成編)


2014年 08月 13日

前回までのあらすじ

シェフィを実装しようと思い立った俺達は基本ルールの実装を何とか終えたばかり。しかしUIが付いてないので肝心のゲームができない状態。果たして無事にゲームを実装し終えることができるのか。

画面の作成

今の段階では実装したゲームがちゃんと動くことさえ確認できれば十分なので、
当初の予定通り、
仮UIはテキストで盤面を表示し、
選択肢はボタンの形で提示することにします。

まずは盤面と選択肢と補助メッセージを表示できるようざっくりとページ index.html を書きましょう:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Shephy JS</title>
    <link href="app.css" rel="stylesheet">
  </head>
  <body>
    <div id="world">
      <div id="sheepStock">
        <div id="sheepStock1">1 (<span class="count"></span>)</div>
        <div id="sheepStock3">3 (<span class="count"></span>)</div>
        <div id="sheepStock10">10 (<span class="count"></span>)</div>
        <div id="sheepStock30">30 (<span class="count"></span>)</div>
        <div id="sheepStock100">100 (<span class="count"></span>)</div>
        <div id="sheepStock300">300 (<span class="count"></span>)</div>
        <div id="sheepStock1000">1000 (<span class="count"></span>)</div>
      </div>
      <div id="enemySheepCount">Enemy: <span class="count"></span></div>
      <div id="field">Field: <span class="cards"></span></div>
      <div id="hand">Hand: <span class="cards"></span></div>
      <div id="deck">Deck: <span class="count"></span></div>
      <div id="discardPile">Discard pile: <span class="cards"></span></div>
      <div id="exile">Exile: <span class="cards"></span></div>
    </div>
    <div id="console">
      <div id="message"></div>
      <div id="moves"></div>
    </div>
    <script src="jquery.js"></script>
    <script src="app.js"></script>
  </body>
</html>

今の段階で外観はどうでも良いのでCSS app.css は空で構いません:

// TODO: 本格的なUIを作る時に考える。

app.js には今まで実装してきたコードが一通り入っているものとします(全体像は省略)。

jquery.js は適当に最新版をコピーしておきましょう(執筆時点だと2.1.1)。

局面の表示

現在の局面(盤面+選択肢)を表示する drawGameTree を定義しましょう。
テキストで表示するだけなので楽勝ですね:

function drawGameTree(gameTree) {
  var w = gameTree.world;
  S.RANKS.forEach(function (rank) {
    $('#sheepStock' + rank + ' > .count').text(w.sheepStock[rank].length);
  });
  $('#enemySheepCount > .count').text(w.enemySheepCount);
  $('#field > .cards').text(
    w.field
    .map(function (c) {return c.rank;})
    .join(', ')
  );
  $('#hand > .cards').text(textizeCards(w.hand));
  $('#deck > .count').text(w.deck.length);
  $('#discardPile > .cards').text(textizeCards(w.discardPile));
  $('#exile > .cards').text(textizeCards(w.exile));

  $('#message').text(
    gameTree.moves.length == 0
    ? S.judgeGame(gameTree.world).description
    : 'Choose a move:'
  );
  $('#moves').empty().append(gameTree.moves.map(nodizeMove));
}

手札や捨て場等、カードの集合を表示する箇所については名前を列挙するだけにします:

function textizeCards(cs) {
  if (cs.length == 0)
    return '-';
  else
    return cs.map(function (c) {return c.name;}).join(', ');
}

勝敗を判定して適切なメッセージを表示する関数も用意しましょう
(よくよく考えるとこれは
listPossibleMovesForBasicRules と若干被ってる部分があるので要改善ですね。
後で考えることにします):

S.judgeGame = function (world) {
  if (world.field.some(function (c) {return c.rank == 1000;})) {
    return {
      result: 'win',
      description: 'You win!'
    };
  }
  if (world.enemySheepCount == 1000) {
    return {
      result: 'lose',
      description: 'Enemies reached 1000 sheep - you lose.'
    };
  }
  if (world.field.length == 0) {
    return {
      result: 'lose',
      description: 'You lost all your sheep - you lose.'
    };
  }

  throw 'Invalid operation';
};

後は選択肢をボタンとして提示する部分を作れば完成です:

function nodizeMove(m) {
  var $m = $('<input>');
  $m.attr({
    type: 'button',
    value: m.description
  });
  $m.click(function () {
    drawGameTree(S.force(m.gameTreePromise));
  });
  return $m;
}

ブートストラップ

これでルールエンジンと仮UIはできたものの、
肝心のゲームを始める部分ができていませんでした。
と言う訳でさくっと作りましょう。
初期盤面からゲーム木を作ってそれを表示するだけなので簡単ですね:

$(function () {
  drawGameTree(S.makeGameTree(S.makeInitalWorld()));
});

実際に遊んでみる

と言う訳で実際に遊んでみましょう!

ゲームの様子

やはりしょぼいですね……とはいえモノは動いているので良しとしましょう。

次回予告

これで一応ゲームは遊べるようになったものの、
個々のカードを全く実装していないので、
これでは何も楽しくありません。
と言う訳で次回はイベントカード実装編です。