JavaScriptでシェフィを実装する(イベントカード実装編(天変地異系))


2014年 08月 27日

前回までのあらすじ

シェフィを実装しようと思い立った俺達はとうとう個々のカードの実装に取りかかったところ。しかし実装すべきカードはまだまだ沢山残っている状態。果たして無事にゲームを実装し終えることができるのか。

今回のあらすじ

前回はひつじを増やす系統のカードを一通り実装しました。
カードの実装方法についてはこれで大体の感触は掴めましたから、
今回はひつじを減らす系統のカード(全9種類)を実装していきます。
実装方法や要領は前回と同様ですから、サクサク進めていきましょう。

以降、前回提示したカードの実装方法を元に個々のカードの実装例を示していきます。

《落石/Falling Rock》

ひつじカード1枚を手放す。

Relase one Sheep card.

これは楽勝ですね。
これまでに実装した《産めよ/Be Fruitful》や《繁栄/Flourish》と同じパターンです。
違いはひつじを得るのではなく手放すことだけです。

また、ひつじを得る効果は場がひつじで埋まっている場合は何も得られませんので、
それも考慮して実装を行う必要がありました。
しかしひつじを手放す場合はそのようなことは考える必要は無いので、
より実装は簡単になるでしょう。

という訳で定義はこんな感じになります:

cardHandlerTable['Falling Rock'] = function (world, state) {
  return world.field.map(function (c, i) {
    return {
      description: 'Release ' + c.rank + ' Sheep card',
      gameTreePromise: S.delay(function () {
        var wn = S.clone(world);
        S.releaseX(wn, i);
        return S.makeGameTree(wn);
      })
    };
  });
};

《嵐/Storm》

ひつじカード2枚を手放す。

Release two Sheep cards.

《落石/Falling Rock》の上位種ですね。
しかし手放す枚数が増えたことによって考慮すべきケースが1つ増えました。
場にひつじが1枚しかない場合は1枚手放したところで効果は終了するからです。
この点も考慮して選択肢を提示する必要がありますね。
具体的には以下のような感じです:

cardHandlerTable['Storm'] = function (world, state) {
  var n = Math.min(state.rest || 2, world.field.length);
  return world.field.map(function (c, i) {
    return {
      description: 'Release ' + c.rank + ' Sheep card',
      gameTreePromise: S.delay(function () {
        var wn = S.clone(world);
        S.releaseX(wn, i);
        var sn = n == 1 ? undefined : {step: state.step, rest: n - 1};
        return S.makeGameTree(wn, sn);
      })
    };
  });
};

《落雷/Lightning》

最大のひつじカード1枚を手放す。

Release your highest-ranking Sheep card.

手放す対象が半自動で決まることを除けば《落石/Falling Rock》と同じです。

cardHandlerTable['Lightning'] = function (world, state) {
  var highestRank = max(world.field.map(function (c) {return c.rank;}));
  return (
    world.field
    .map(function (c, i) {return [c, i];})
    .filter(function (x) {return x[0].rank == highestRank;})
    .map(function (x) {
      return {
        description: 'Release ' + x[0].rank + ' Sheep card',
        gameTreePromise: S.delay(function () {
          var wn = S.clone(world);
          S.releaseX(wn, x[1]);
          return S.makeGameTree(wn);
        })
      };
    })
  );
};

《狼/Wolves》

最大のひつじカード1枚を選び、1ランクダウンする([1] なら手放す)。

Reduce the rank of your highest-ranking sheep card by one. If your highest ranaking Sheep card is 1, release it.

やや被害がマイルドになった《落雷/Lightning》です。
被害の部分を除けば要領は全く同じですね。

cardHandlerTable['Wolves'] = function (world, state) {
  var highestRank = max(world.field.map(function (c) {return c.rank;}));
  if (highestRank == 1)
    return cardHandlerTable['Lightning'](world, state);
  return (
    world.field
    .map(function (c, i) {return [c, i];})
    .filter(function (x) {return x[0].rank == highestRank;})
    .map(function (x) {
      return {
        description: 'Reduce the rank of ' + x[0].rank + ' Sheep card',
        gameTreePromise: S.delay(function () {
          var wn = S.clone(world);
          S.releaseX(wn, x[1]);
          S.gainX(wn, S.dropRank(highestRank));
          return S.makeGameTree(wn);
        })
      };
    })
  );
};

《疫病/Plague》

ひつじカード1種類すべてを手放す。

Release all Sheep cards of one rank.

これまでとはやや趣が異なります。
カードを選ぶのではなく種類を選ぶというのがポイントです。
とはいえ、mapする対象が場から種類になっただけであり、要領は今までと大して変わりありません。

cardHandlerTable['Plague'] = function (world, state) {  //{{{2
  var ranks = uniq(world.field.map(function (c) {return c.rank;}));
  return (
    ranks
    .map(function (r) {
      return {
        description: 'Release all ' + r + ' Sheep cards',
        gameTreePromise: S.delay(function () {
          var wn = S.clone(world);
          for (var i = wn.field.length - 1; 0 <= i; i--) {
            if (wn.field[i].rank == r)
              S.releaseX(wn, i);
          }
          return S.makeGameTree(wn);
        })
      };
    })
  );
};

function uniq(xs) {
  var us = [];
  var found = {};
  for (var i = 0; i < xs.length; i++) {
    var x = xs[i];
    if (!found[x]) {
      us.push(x);
      found[x] = true;
    }
  }
  return us;
}

《過密/Crowding》

2枚以下になるまで、ひつじカードを手放す。

Release all but two Sheep cards.

ややややこしくなってきました。
これは《地に満ちよ/Fill the Earth》の時のように選択肢をループさせる形にしましょう。
つまり、

  • ひつじが場に3枚以上→どれか1枚を捨てる選択肢を提示
  • ひつじが場に2枚以下→処理を終了

ということにします。
また、ひつじが場に2枚以下という状況でこのカードをプレイした場合は何も起きません。
なのでそれっぽいメッセージが出るように調整しておきましょう。

cardHandlerTable['Crowding'] = function (world, state) {
  if (world.field.length <= 2) {
    return [{
      description: 'Too few sheep - nothing happened',
      gameTreePromise: S.delay(function () {
        return S.makeGameTree(world);
      })
    }];
  } else {
    return world.field.map(function (c, i) {
      return {
        description: 'Release ' + c.rank + ' Sheep card',
        gameTreePromise: S.delay(function () {
          var wn = S.clone(world);
          S.releaseX(wn, i);
          var sn = wn.field.length <= 2 ? undefined : state;
          return S.makeGameTree(wn, sn);
        })
      };
    });
  }
};

《暴落/Slump》

枚数が半分以下になるまで、ひつじカードを手放す。

Relase half of your Sheep cards (Round down.)

《過密/Crowding》は場を見るだけでまだひつじを手放すべきかどうか判断できましたが、
この《暴落/Slump》の場合はプレイした時点でのひつじの枚数を基準にして判断する必要があります。
これは state = {step: 'Slump', initialCount: 5} のように保持することにしましょう。

なお、手放す枚数は端数切り捨てです。
場に5枚のひつじカードがある場合は2枚だけ手放せばよく、
場に1枚しかひつじカードが無ければ何も手放す必要はありません。

cardHandlerTable['Slump'] = function (world, state) {
  if (world.field.length == 1) {
    return [{
      description: 'No sheep to release - nothing happened',
      gameTreePromise: S.delay(function () {
        return S.makeGameTree(world);
      })
    }];
  } else {
    var n = state.initialCount || world.field.length;
    var countToKeep = Math.ceil(n / 2);
    return world.field.map(function (c, i) {
      return {
        description: 'Release ' + c.rank + ' Sheep card',
        gameTreePromise: S.delay(function () {
          var wn = S.clone(world);
          S.releaseX(wn, i);
          var sn = wn.field.length == countToKeep
            ? undefined
            : {step: state.step, initialCount: n};
          return S.makeGameTree(wn, sn);
        })
      };
    });
  }
};

《メテオ/Meteor》

このカードを追放する。ひつじカード3枚を手放す。

Release three Sheep cards, and then remove this card from the game.

基本的には手放す枚数が増えた《嵐/Storm》なので同じ要領で進められますが、
「このカードを追放する」というのが特色です。
カードをプレイした時点でそのカードは捨て場に置かれているので、
それを追放する必要があります。
問題のカードは捨て場からカード名を基準にして調べてもよいのですが、
プレイしたばかりのカードは捨て場の一番上に置かれているので、
それを追放することにすれば簡単です(←伏線)。

cardHandlerTable['Meteor'] = function (world, state) {  //{{{2
  var n = Math.min(state.rest || 3, world.field.length);
  return world.field.map(function (c, i) {
    return {
      description: 'Release ' + c.rank + ' Sheep card',
      gameTreePromise: S.delay(function () {
        var wn = S.clone(world);
        if (state.rest === undefined)
          S.exileX(wn, wn.discardPile, wn.discardPile.length - 1);
        S.releaseX(wn, i);
        var sn = n == 1 ? undefined : {step: state.step, rest: n - 1};
        return S.makeGameTree(wn, sn);
      })
    };
  });
};

《シェフィオン/Shephion》

ひつじカード7枚を手放す。

Release seven Sheep cards.

さらに手放す枚数の増えた《嵐/Storm》……と言いますか、
場にはひつじカードを7枚までしか配置できないので、
このカードは「場にあるひつじカードを全て手放す」と同義です。
これに関しては手放すひつじの選択肢を提示しても不毛ですから、
「全てのひつじを手放す」という選択肢を1つだけ提示することにしましょう。

cardHandlerTable['Shephion'] = function (world, state) {
  return [{
    description: 'Release all Sheep cards',
    gameTreePromise: S.delay(function () {
      var wn = S.clone(world);
      while (1 <= wn.field.length)
        S.releaseX(wn, 0);
      return S.makeGameTree(wn);
    })
  }];
};

次回予告

前回のひつじブースト系と今回の天変地異系を合わせて15種類のイベントカードが実装できました。
しかしまだ未実装のカードが4種類も残っています。
という訳で次回はイベントカード実装編(ユーティリティ系)です。