シェフィを実装しようと思い立った俺達はとうとう全カードの実装。具体的にはこんな感じ:
これでは作った当人も遊ぶ気が起きない。もっとまっとうなUIにしなければ……
シェフィはカードゲームです。
カードゲームなのにカードをカードとして表示していない今の実装はカードゲームとは言えません。
という訳でカードを良い感じにレンダリングして盤面をカードゲームっぽくしましょう。
とはいえ、いきなり完璧なものを作るのは難しいので、
簡単なものから始めて徐々にカードゲーム感を醸し出していくことにしましょう:
トランプですら創意工夫が凝らされている今の時代、
イラストが付いてないカードゲームなんて誰も見向きもしません。
なので本当はイラストを付けたいところですが、
実物のカードをスキャンして使う訳にもいきませんので、
何となくそれっぽい雰囲気の表示にするだけで抑えておくことにします。
まずは盤面(正確には局面)の表示を行う関数 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(', ');
}
テキストで表示してるのですから簡単ですね。
この中で textizeCards
を利用している部分を以下の形に変更しましょう:
$(‘#hand > .cards’).html(visualizeCards(w.hand));
それと、フィールドの表示ですが、何でこんな小難しいこと書いてたんでしょうね……
個々のカードを表示すれば良いのですから、これも visualizeCards
を使った形に変更しましょう。
結果として 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').html(visualizeCards(w.field));
$('#hand > .cards').html(visualizeCards(w.hand));
$('#deck > .count').text(w.deck.length);
$('#discardPile > .cards').html(visualizeCards(w.discardPile));
$('#exile > .cards').html(visualizeCards(w.exile));
$('#message').text(
gameTree.moves.length == 0
? S.judgeGame(gameTree.world).description
: 'Choose a move:'
);
$('#moves').empty().append(gameTree.moves.map(nodizeMove));
}
visualizeCards
ではカードを表すDOMツリーを返すことにしましょう:
function visualizeCards(cards) {
return cards.map(visualizeCard);
}
function visualizeCard(card) {
var $card = $('<span>');
$card.addClass('card');
$card.text(card.name);
return $card;
}
これだけだとテキスト時代の表示と何ら変わらないので、
ここらでようやくCSSを書くことにしましょう:
.card {
display: inline-block;
width: 5.6ex;
height: 7.8ex;
border: thin solid #000;
background: #eee;
overflow: hidden;
white-space: nowrap;
margin: 0.5ex;
padding: 0.5ex;
}
これでゲームの局面を表示させると以下のようになります:
おお……まだまだ素朴な外観とはいえ、多少はやる気が湧きそうな形になってきましたね。
実物のカードだとひつじカードとイベントカードで枠や背景が異なります。
流石に完全に同じものを再現するのは困難ですし権利的にアウトな感じなので、
何となくそれっぽい雰囲気にはなるよう表示を変えましょう。
まず、カードのタイプを判定する関数がないのでそれを定義しましょう:
function cardType(card) {
return card.rank === undefined ? 'event' : 'sheep';
}
次に visualizeCard
の結果にカードのタイプを仕込みます。
また、さらなる装飾用に追加のレイヤーも仕込んでおきましょう:
function visualizeCard(card) {
var $body = $('<span>');
$body.addClass('body');
$body.text(card.name);
var $card = $('<span>');
$card.addClass('card');
$card.addClass(cardType(card));
$card.append($body);
return $card;
}
最後にCSSを調整すれば完成です:
.card {
display: inline-block;
border: thin solid #000;
overflow: hidden;
white-space: nowrap;
margin: 0.5ex;
}
.card.sheep > .body {
display: inline-block;
width: 5.6ex;
height: 3.8ex;
padding: 0.5ex;
border-top: 2ex solid #cff;
border-bottom: 2ex solid #3c3;
background: #fff;
text-align: center;
}
.card.event > .body {
display: inline-block;
width: 5.1ex;
padding: 0.5ex 0.5ex 0.5ex 0;
background: #ec8;
text-align: center;
line-height: 7.8ex;
border-left: 0.5ex solid #db7;
}
うーん。これだとひつじカードが何だかウズベキスタンやシエラレオネの国旗のようですね……とはいえ実物のカードにおける雲感や草原感を再現するのはちょっと面倒なのでこれで良しとしましょう。最初のグレーの枠だけに比べればこれでも十分です。
実物のカードをよく見てみると、
実はひつじカードのランクがすぐに分かるよう、ボーダーの色が微妙に違っています。
せっかくなのでこれも再現しましょう。
まずは visualizeCard
でひつじカードのランクも仕込みましょう:
function visualizeCard(card) {
var $body = $('<span>');
$body.addClass('body');
$body.text(card.name);
var $card = $('<span>');
$card.addClass('card');
$card.addClass(cardType(card));
$card.addClass('rank' + card.rank);
$card.append($body);
return $card;
}
後はひつじカード用のCSSをちょろっと修正するだけです:
.card.sheep.rank1 > .body {border-bottom-color: #ce0;}
.card.sheep.rank3 > .body {border-bottom-color: #9e0;}
.card.sheep.rank10 > .body {border-bottom-color: #6e0;}
.card.sheep.rank30 > .body {border-bottom-color: #0d0;}
.card.sheep.rank100 > .body {border-bottom-color: #0a0;}
.card.sheep.rank300 > .body {border-bottom-color: #080;}
.card.sheep.rank1000 > .body {border-bottom-color: #060;}
なかなか良い感じになってきたのではないでしょうか。
実物準拠にするならひつじのランクは緑のボーダーの部分に表記すべきですが、
これをやるならカード中央部分にイラストが無いと映えませんし、
そこまでやり始めるとキリがないのでここらで切り上げておくことにしましょう。
やはり外観は重要です。
これで結構なカードゲーム感が出てきました。
しかし山札やひつじ山の表示は無機質な数字のままです。
ひつじを増やすほんわかしたゲームなのに、こんな冷たい表示のままでは心が荒んでしまいます。
という訳で次回はUI本格化編/カードスタック可視化の巻です。