ホロライブタイカプにおける弾ける水しぶき 白上フブキ

9月の話ですがWGP2022名古屋でホロライブタイトルカップを優勝することができました。

そこでデッキの中心にした 弾ける水しぶき 白上フブキ について書いていきます。

(すごい今更ですが大晦日に思いつきでスマホから書いてます。)

デッキが出来るまでやったこと

・前期のデッキ確認

・カードリストの確認

Twitterでのデッキリスト収集

・ホロライブ関係の記事を読む

 

その上で、弾ける水しぶき!白上フブキを使用したかったので、そこからデッキ構築をスタートしました。

 

弾ける水しぶき 白上フブキについて

このカードの特性を軽く説明します。

まず書いてあることは

①行き高パワー

CXコンボが宝

2枚回収により手札の質を上げやすい

  →1枚でのCXコンボでも手札が2枚強くなる

④緑

 

これをホロライブでのプールならではの強み弱みに置き換えます。

 

強み

CXを用いたスピードゲームの再現性が高い

,②の特性によりCXコンボを発動しやすく、さらに③で後続を確保できるため、一貫したゲームプランを取りやすいです。

これはホロライブのプールでは大きな強みです。

ホロライブのプールは、オカケンがないことなどにより1レベル帯でのCXコンボをゲームプランの軸に置くと安定性が下がってしまいます。

 

CXシナジーの手札の質向上能力が最も高い

1枚で2枚取れるのはフブキだけ

 

・ストック消費が少ない

これは単純な宝のメリットですが、ホロライブのプールは意外とストックが溜まりづらいです。

 

弱み

・緑である

あやめが使えない。

かなたやそらなどの多面早出しに対応しずらくなります。

 

構築

実際に使用したリストはこちら

https://decklog.bushiroad.com/view/4KZHE

上の強みを活かせるように、CXを用いたスピードゲーム→マリンをコンセプトに組みました。

 

さいごに

今回は弾ける水しぶき 白上フブキについてのみ簡単に書きました。

(デッキの詳細書こうと思ったら時間が😉)

デッキについてやその他諸々の質問はTwitter(@minato_lotus)などでいただければ回答します。

 

プレメモ公式対戦交流会 後だし環境雑感+簡易レポ

先日(1/15)に開催されたプレメモ公式対戦交流会の環境事前予想と実際どうだったかについて書いていきます。

翌週に大体書いたのに公開忘れててめちゃくちゃ今更になりました😉

事前予想

いそうなデッキ

去年の全国からいたあやせとガイル、追加のおちフルとけいおんが有力なデッキかと思っていました。

以下デッキの簡単な紹介

けいおん

f:id:minato_lotus:20220116223956j:plainf:id:minato_lotus:20220116223953j:plainf:id:minato_lotus:20220116224345j:plainf:id:minato_lotus:20220116224341j:plain

スリーブで超絶強化されたデッキ(前が五年以上前だから当然)

前弾からあるアドバンテージの取れるイベントでデッキを回し、
アンタッチャブルの梓をアンブロにして3点+αの打点を入れてくるデッキです。

おちフル

f:id:minato_lotus:20220116224938j:plainf:id:minato_lotus:20220116224940j:plainf:id:minato_lotus:20220116224943j:plainf:id:minato_lotus:20220116224945j:plainf:id:minato_lotus:20220116224947j:plain

去年の全国後に発売され台頭してきたデッキ。

各キャラなんらかの点を通す手段(突破・天然など)を持ち、
EXから取れる0コス連パンと合わせて相手を倒すデッキです。

あやせ

f:id:minato_lotus:20220116225831j:plainf:id:minato_lotus:20220116225828j:plainf:id:minato_lotus:20220116225825j:plain

去年の全国優勝デッキ。

全サーチによる安定性と、除去とハンデスを合わせた圧倒的なコントロール力を持つデッキです。

ガイル

f:id:minato_lotus:20220116230348j:plainf:id:minato_lotus:20220116230351j:plainf:id:minato_lotus:20220116230343j:plainf:id:minato_lotus:20220116230346j:plain

二種のアド取りイベントやドロー・サーチを駆使して、
出禁・イベ禁・テキスト封印で相手に何もさせないようにしショットするデッキです。

相性

基本的に先攻が有利です。

後攻で勝てるデッキはけいおんとガイルだけかなと思っていました。

その他

f:id:minato_lotus:20220116232813j:plainf:id:minato_lotus:20220116232810j:plain

けいおんの調整中に浮かんできたダークホースが物語です。

6コス梓はそのアンタッチャブル性能から1枚しか採用されていないことが多かったため、EXから手札に移した段階で貝木でデッキに戻すことで除外してしまい勝ち手段を奪うことができました。

また面白い話をしてあげるはアタッカーがすべて6コスのけいおんに対して実質全体アプ禁のように機能するため、梓がアンブロになったとしても止める手段のあるデッキです。

ただその他のデッキに勝つことが難しいだろうと思われたため、考慮はしませんでした。

f:id:minato_lotus:20220116234439j:plainf:id:minato_lotus:20220116234442j:plain

劣等生も梓を止める手段を持つデッキのため検討に上りましたが、けいおんの回りが早いときやその他のデッキに対して不安があったため除外しました。

デッキ選択

事前予想の相性の欄で書いた通り、後攻で勝ちうるデッキはけいおんとガイルだと考えていたためこの二択から。

新年会というお祭り的なイベントで、出たばっかりのけいおんを使いたかったこともありデッキはけいおんを選択しました。

(お祭りイベントなのでタイトルの好き度合いで選びたかったのですが、けいおんガイル両方好きなため悩みました。強いタイトルが限られがちなこのゲームだと幸せな悩みですね。)

現実

実際の分布は以下の画像のようになりました。

f:id:minato_lotus:20220116233804p:plain

事前予想と違ってあやせがほとんどいませんでした。
やはりけいおんへの勝率が5割ないというのが効いた感じになったのでしょうか。

予想にはなかったあの花も使用数だと3位。
ちょっと経験が少ないのでわからないですが、速度や安定性はあるので先攻ならけいおんもとれる、といった使用理由でしょうか。

簡易レポート

ガイル〇
おちフル〇
おちフル〇
けいおん
おちフル〇
物語〇

結果は6-0することができました。

ほぼすべての試合でデッキがよく回ってくれました。

また、最終戦の物語戦で面白い話を1枚しか引かれなかったなど、とても運がよかったです。その試合は公式動画としても上がっているのでよければそちらもご覧ください(途中途切れてるけど)

【プレメモ公式対戦交流会1月】対戦交流会スイスドロー 6回戦 - YouTube

使用したデッキ

イベカンを採用しているのであれば各タイトルについてどこの何をカウンターするかを決めてないとダメでしたね(反省)。

さいごに~今後の環境雑感~

f:id:minato_lotus:20220202124535j:plainf:id:minato_lotus:20220202124537j:plainf:id:minato_lotus:20220202124539j:plainf:id:minato_lotus:20220202124629j:plainf:id:minato_lotus:20220202124544j:plain

f:id:minato_lotus:20220202124532j:plainf:id:minato_lotus:20220202164347j:plainf:id:minato_lotus:20220202165525j:plainf:id:minato_lotus:20220202133930j:plain

上のツイートは半分冗談ですが、きんモザのスリーブが発売されたのでまた環境が変わります。

かなりいろんな種類のデッキが組めるタイトルなので想定が難しくデッキ紹介ができない。。。🥺

交流会の時の、けいおんが強いだろうが動きはわかっている、という状況とは違い仮想敵が置きづらいです。

2/13の東京での大会はきんモザのいいデッキを持ち込んだ人が優勝しそうだな~と思ってます。

 

 

質問などありましたらコメントか

湊 (@minato_lotus) | Twitter

にどうぞ。

読んでいただきありがとうございます。

デッキ紹介 研修生(NEW GAME!)

どうも。

今回はニューゲームの研修生デッキの紹介をします。

デッキリスト

http://prememo.net/decks/detail/210362

f:id:minato_lotus:20210715213322p:plain

 

どんなデッキか

スリーブで強化された研修生デッキです。

盤面に望月紅葉・鳴海ツバメを6体並べて、全体+60/+60パンプ、突破、アクティブに加えて1点バーンや自由登場でのパンチ数増で速攻をしかける前のめりなデッキです。

f:id:minato_lotus:20210715215947j:plain

 

基本的な動き

先攻1ターン目

目指すべきは、6コスツバメと紅葉を並べて同僚をプレイすることです。あとは並べられるキャラを並べておけばOK

 

後攻1ターン目

相手のタイトルにある寝坊系カードに対して紅葉青葉コンビかねねっちをプレイし、アプローチに行けるのが理想です。

なければ先攻と同じ動きをしましょう(どうせアプローチできない)

寝坊系カードがなければ当然アプローチに行きます。

 

2ターン目以降

相手のアプローチは自由登場でいなしつつ、攻めでも自由登場でパンチ数を伸ばしながら7点入れることを目指します。

 

リストについて

このリストは全国2日目のガンスリンガーで使用していたものです。攻め手をわかりやすくするために自由登場を多めに積んでいます。

 

全国の候補にしていた構築は、自由登場を減らしてテキスト無効ひふみが入っていました。

out 自由登場紅葉3 ツバメ2

in テキスト無効ひふみ4 ひふみ自由登場1

 

あやせが無理すぎるので自由登場を減らしてせめてもの抵抗ができるカードとして採用しています。

(あやせさえいなければ自由登場多めの方が守り攻め双方に優れていて好きです)

 

主要マッチ

対あやせ

絶望的なマッチです。相手の動きが弱ければスピードで勝てますが基本的に負けます。

上のリストではあやせは諦めています。

 

ひふみを増やしているデッキで対戦する際は、ひふみで加奈子を止めて、青葉コンビでウェディングあやせを除外するゲームになります。

もちろん格闘されますが、ハンドを縛りながら格闘を青葉コンビに使わせることで、メインパンチャーに対する除去が間に合わなくなることを狙います。

 

対ガイル

速攻で倒しにいけるのと、防御札の種類が多くショットに必要なものが多いので、ちょっと弱めの回りをしてくれれば勝てます。

止血八幡が強いため、サポートに出された場合はうみこ、メインならひふみで消して対策しましょう。

 

対劣等生(青)

うみこでEX回収の深雪を止めながら速攻を仕掛けます。長引くとテキスト無効を防御で使い易くなり負けます。

万が一長引いてしまった際は数値変更の深雪を青葉コンビで除外することで点を通し易くすることもあるので覚えておきましょう。

 

対劣等生(黄)

対戦経験がなくわかりません😇

これから勉強します。

 

対ミラー

テキスト無効ひふみやたまこ、自由登場によるキャラ圧殺で捌き合いをします。

概ね先行有利で後攻はねねっちから始めて点を先に入れることを狙います。(ただし他キャラを並べつつ返しの防御札を握る必要があり要求値は高いです。)

ひふみを採用していない分今回の構築では不利が付くかもしれません。

 

さいごに

非常にテンポよくアプローチできて楽しいデッキになっています。

僕自身もガンスリでこのデッキを使うことで「プレメモを楽しむ気持ち」を改めて持つことができました(あやせに当たっていない。)

皆さんもぜひ試してみてください。

 

質問などありましたらコメントか

湊 (@minato_lotus) | Twitter

にどうぞ。

読んでいただきありがとうございます。

デッキ紹介 ガイル無限LO

プレシャスメモリーズ2021 1stの全国大会お疲れ様でした。

今回は使用したデッキを紹介します。

レシピ

デッキメーカー:http://prememo.net/decks/detail/210357

f:id:minato_lotus:20210711212846p:plain

やること

2点受けて、相手のデッキを破棄し続けて勝ちます。

主要カード

下記カードを使用して無限ループします。

f:id:minato_lotus:20210711214542j:plain


前提
  • 2点受けて、ポイント置き場のカード2枚が3ソースになるようにします。
  • プロムコインを10個溜めます。
  • 山の中が01-086 小町、01-008 雪乃、01-031 平塚、02-014 雪乃、P-006 雪乃のみになるようにします。
ループ概要

前提を満たした上で下記動きを組み合わせて相手のデッキを破棄し続けます。

  • 01-031 平塚の使用 → リソース+1
  • 01-086 小町でST-011 八幡を出しなおして2ドロー → ±0
  • 01-008 雪乃(ブレスト)でドロー →  ±0
  • 02-014 雪乃でデッキ破棄 → ‐2
  • P-006 雪乃でデッキ修復 → - 1

基本的なカードのプレイ順は

(修復)→小町→ブレスト→小町→ブレスト→小町→LO雪乃 or ブレスト→修復

です。ソースや山が必要なタイミングで平塚を使用します。

細かくなるので詳細は省略しますが、山を一回修復(P-006雪乃)してからもう一度修復するまでにLOをする回としない回があります。

LOする回では01-086 小町×3、01-008 雪乃×2、02-014 雪乃、P-006 雪乃を、

しない回では01-086 小町×3、01-008 雪乃×3、P-006 雪乃を山に戻します。

(原則として捨て札のパーツが7枚ピッタリになるときに修復するようにするとよいです。)

欠陥

無限にLOできるので最強に思えますが、いくつか欠点があります。

2点受けないと何もできない

2点受けないと01-031 平塚が使用できないため、

  • 0,1点から一気に詰めるデッキに勝てない。
  • 一度も殴らずにエンドし続けて、山が減ったところでLOを狙われてしまう。

といった負け筋があります。

多くのシャッフルが必要になる

一人で回す分には30分もあれば相手の山を削りきることができますが、対人戦でシャッフルするタイミングで相手のカットに時間がかかると試合時間が足りなくなってしまいます。(ポイントを受けているために判定負けしてしまいます。)

※大会時点でのプレメモのルールだと手順を省略することができないので、削りきるまでプレイする必要があります。

除外に弱い

当然ですが、ループできなくなるので除外系カードに弱いです。

その他採用カードについて

基本的にはデッキを削るカードと防御札とプロムコインを溜めるカードで構成されています。それ以外のカードを説明します。

ポイント交換(01-061 八幡、P-019 由衣)

ポイントに3ソースが2枚ある必要があるため、この2種を用いてポイントを入れ替えます。

八幡で入れ替えるのが簡単ですが、複数枚コンボパーツが落ちてしまったり、イベントが落ちてしまった際は、ブレスト(01-008 雪乃)で山上に3ソースを置いた上で由衣を出して入れ替えします。

バウンス雪乃(01-003 雪乃)

劣等生の0コスキャラのテキスト封印(02-006 司波 達也)対策です。

01-059 由衣

いれていましたが必要ないカードです。

修復がシャッフルな関係上ループが止まる可能性もあるかと思い入れていましたが、ループは止まりません(多分)。

さいごに

無限LOという素敵な響きを持ったデッキですが、欠陥の項で語ったことによって使用はおすすめできません。逆に言うと欠陥が解消できれば使用に足るデッキかと思います。

僕は使用してから欠陥に気づきました精進が足りませんでした次回に活かします。

 

質問などありましたらコメントか

湊 (@minato_lotus) | Twitter

にどうぞ。

読んでいただきありがとうございます。

プレメモ全国環境雑感 2021年

どうも。お久しぶりです。湊です。

 

今回は、自分の思考整理のために2021年のプレメモ全国大会前の雑感を語ります。

流れ

環境デッキにいそうなデッキ

いそうなデッキは下記

・俺妹(あやせ)

・ガイル(プロム)

アララギ

・ニューゲーム(研修生)

・劣等生

それぞれについて軽く説明していきます。

 

俺妹(あやせ)

例: http://www.p-memories.com/node/962400#B1

最強であり環境の基準。

『「あやせデッキ」とは、「ハンデスランデスLO除去バーンコントロールデッキ」のことです。』

と下記記事でも語られるように、相手のリソースを縛り尽くすコントロールデッキです。コントロールだけではなく、6コス桐乃黒猫コンビを絡めたショットも可能で万能なデッキです。

2021札幌地区優勝 あやせ解説|水希|note

他のデッキはあやせをどう突破するかを考えないといけません。

 

ガイル(プロム)

例: http://www.p-memories.com/node/962400#B2

あやせに勝てるデッキその1。

折本かおりは禁止になってしまいましたが、まだまだデッキの全てのカードにアクセスすることは可能です。

出禁・イベ禁・テキスト封印・全タップにパンチ数を合わせて相手を倒します。

 

アララギ

例: http://www.p-memories.com/node/962400#A1

あやせに勝てるデッキその2

黄色ヴァルハラコンビ私の秘密斧乃木余接などを用いた上で、アララギ・赤吸血鬼による圧倒的なパンチ数で相手を破壊します。

全サーチや多様な防御札も持ち合わせているため防御も強いです。

 

ニューゲーム(研修生)

例: なし(地区大会時点で発売していないため)

あやせに勝てる?デッキ

6面+60+60突破アクティブ + 1点バーン + 0コス自由登場と後攻1ターン目で勝てる非常に前のめりなデッキ。

前のめりといっても、ニューゲームの持つ強力な防御札ねねっちや、寝坊・土下座などもあり、アタッカーを軽いコストで出せることから継戦能力も高いデッキです。

 

劣等生

例: http://www.p-memories.com/node/962400#A2

環境のコントロール枠二つ目

強力な自由登場達也に加え、相手ターンにも飛ばせるテキスト無効や回復イベントがあり非常に固いデッキ。

4コスEx達也による除去あり、タイトルや休息を絡めたショットもありと防御以外も侮れないデッキです。

アタッカーを光井ほのかにすることでより攻撃力を高めたデッキも存在します。

 

各デッキのマッチについて

相性表

プレイヤーの腕にもよりますが相性表はこんな感じだと思っています。

 

あやせ

ガイル

アララギ

ニューゲーム

劣等生

あやせ

 

×

ガイル

 

×

アララギ

 

×

ニューゲーム

×

 

×

劣等生

×

×

×

 

 

ミラーも含めて、それぞれのマッチについて簡単に見ていきます。

 

あやせミラー

基本的に先攻が勝ちます。後攻は細い勝ち筋を通す必要があり、相手が完璧なら勝ち目はありません。逆にいうと練度の差で後攻でも捲れる可能性があります。

(再度の引用になりますが、下の記事では後手でも捲れると書かれているため自分の練度の問題もありそうです。。)

2021札幌地区優勝 あやせ解説|水希|note

 

あやせ-ガイル

基本的にガイルが勝ちます。ソース雪乃からの出禁に俺妹側はなすすべがありません。

あやせ側の少ない勝ち筋は、相手にリソースを与えずに先に倒すくらいでしょうか。

 

あやせ‐アララギ

構築によって勝率が変わるマッチです。

アララギ側は斧乃木余接により相手の場の防御札を止めて勝利します。(黄色ヴァルハラは京介されるのでおおむね使えません)

基本的に自由登場は出せるので、01-009桐乃を採用していれば妨害回数が飛躍的に増えあやせ側が有利になるでしょう。

 

あやせ‐ニューゲーム

あやせ側の展開次第です。ニューゲームが速攻で倒すか、あやせが少し点を受けた返しに格闘あやせを連打して面とハンドを破壊することで勝利が決まります。

あやせは安定したデッキなので、あやせが有利かと思います。

 

あやせ‐劣等生

あやせ側の除去した際のアドバンテージの取り方が異常なので、基本的にあやせ優位です。

劣等生側の除去で3コスExあやせが復帰できない状態に追い込まれるとあやせ側が敗北します。

 

ガイルミラー

お互い点を入れあわず一気に決める勝負になるため、倒すためのリソースを早く集めた方が勝ちます。ということは後攻有利?

 

ガイル-アララギ

ガイル側は封じなければならない防御札の種類が多いのに対し、アララギ側は秘密のみで詰められるため、基本的にアララギが有利だと思っています。

 

ガイル-ニューゲーム

ニューゲームが打点を入れた返しに、ガイル側が決めきれるかという勝負になります。

EX回収由衣のテキストが封じるられるため、決めきれないことも十分にあります。

ニューゲーム側ができることはお祈りです。

 

ガイル-劣等生

劣等生側の防御札をかいくぐるための出禁+イベ禁とパンチ数を用意できるかの勝負です。

劣等生側が一気に決めることは難しいため、基本的にガイル有利です。

 

アララギミラー

正直アララギは浅いのでわかりません…

不用意に3コスアララギを墓地に落とすと、扇で除外される可能性があるので注意といったところでしょうか。

 

アララギ-ニューゲーム

アララギがねねっち1枚を貫通できる札を用意する感じの勝負になりそうです。

浅くて申し訳ない…

 

アララギ-劣等生

黄色ヴァルハラが殴る前に劣等生側がいかに早く勝てるかという勝負になりそうです。

ガイル-劣等生と同様、劣等生側の攻めが弱いため、アララギ有利かと思っています。

 

ニューゲームミラー

ブレイク次第で守られるため、常にブレイクを意識する必要があります。

ねねっちがあるため先攻が基本的に有利。

(後攻0封じから6面並べられれば後攻が有利になります)

 

ニューゲームー劣等生

テキスト無効が強すぎるため基本的に劣等生が有利です。

ニューゲーム側は劣等生にリソースを与えないようにしつつ2, 3ターンで決めきる必要があります。

 

劣等生ミラー

わかりません!!(投)
お互いアプローチ中にできることが多いので練習を多くし時間切れにならないようにしましょう。

 

全国では勝てないと思っているデッキ

 ダージリン

あやせに勝てない上にアララギには撫子で止められ、ガイルには連パンをため込む前にショットされるという事情で、外しました。

(劣等生も同じ状態ですが、まだ構築を煮詰める余地はあると思うため残しています)

マクロコスモスを使ったニューゲーム

アララギを殺すことができます。

あやせが無理です。

ガイルは、マクロを置くことで展開が遅くなってしまうようだと、向こうの準備ができてしまい負けます。

研修生をぶん回しつつマクロコスモスを添えるというのが一番現実的なラインでしょうか。

いい感じで混ぜられればありだと思います。

 

まとめ

各デッキの相性の項の表のとおり、あやせ、ガイル、アララギが割と優位にいるが圧倒的ではない、といった印象です。

自分の使用するデッキが上記デッキにどうやって勝つか、綿密に計画し練習できている人が勝てるのかなと思っています。

 

僕自身は練習する時間がねぇ!!と開き直っておもちゃを使うか、上記3デッキのうち一つを残り時間目いっぱい練習して使うと思います😇

 

皆さんは全国の環境がどうなると考えていますか?

こんな環境になりそう、このデッキはどうなの?などありましたらコメントか

湊 (@minato_lotus) | Twitter

にお願いします。

※申し訳ないですが返信速度には期待しないでください。。。

 

ここまで読んでいただきありがとうございました。

スマホアプリで一人回しがしたい(ついでにSwiftUIお勉強)その2

導入 スマホで一人回しがしたい

どうも、湊です。

私はTCGでよく一人回しというのをします。*1

ただこの一人回し、自宅などカードを広げることが許されるスペースでしかできません(当然)。

どこでもやりたくない...?
スマホでやりたくない...?

と思ったのでアプリを作ることにしました。
趣味のツール作成と仕事の勉強(SwiftUI)ができて一石二鳥です。

前回まで

前回↓はキャラクターを配置するViewをSwiftUIで作りました。
スマホアプリで一人回しがしたい(ついでにSwiftUIお勉強)① - minato_blog

今回は盤面全体を作っていくことにします。

画面イメージ(ざっくり)

スマホの画面に収める必要があるため、実際の盤面からは少々レイアウトを変える必要がありそうです。
こんな感じです。

f:id:minato_lotus:20201114153142p:plain
画面イメージ

ピンク色のマスがAFで青色はDFです。
山札や置き場は、ぱっと見でわかればいいのが枚数だけなのでまずはそれだけ表示することにします。
(山札などの中身が見れる画面は別で作ります)

AF, DF

前回作ったフィールド一つあたりのViewを6つ並べるだけでよさそうです。

これはVStackとHStackを用いることで簡単に実現できます。(Viewの表示用データを格納するstructも一緒に記載しておきます)

struct Fields {
    var leftAF = Field()
    var centerAF = Field()
    var rightAF = Field()
    var leftDF = Field()
    var centerDF = Field()
    var rightDF = Field()
}

struct FieldsView: View {
    var fields: Fields
    
    var body: some View {
        GeometryReader { geometry in
            let fieldWidth = min(geometry.size.width, geometry.size.height) / 3.2
            
            VStack {
                HStack {
                    FieldView(field: fields.leftAF)
                        .frame(width: fieldWidth, height: fieldWidth)
                    FieldView(field: fields.centerAF)
                        .frame(width: fieldWidth, height: fieldWidth)
                    FieldView(field: fields.rightAF)
                        .frame(width: fieldWidth, height: fieldWidth)
                }
                HStack {
                    FieldView(field: fields.leftDF)
                        .frame(width: fieldWidth, height: fieldWidth)
                    FieldView(field: fields.centerDF)
                        .frame(width: fieldWidth, height: fieldWidth)
                    FieldView(field: fields.rightDF)
                        .frame(width: fieldWidth, height: fieldWidth)
                }
            }
            .frame(width: geometry.size.width)
        }
    }
}

これをPreviewで見てみるとこんな感じになります。

f:id:minato_lotus:20201114171325p:plain
Preview

山札・ゴミ箱・その他置き場の情報

とりあえずその領域の名前とカード枚数だけわかればよさそうなので、Textで配置しました。

コード。ここを押して展開。

struct DomainInfoView: View {
    var onePlayerDomain: PlayerDomain
    var otherPlayerDomain: PlayerDomain
    
    init(one oneDomain: PlayerDomain, other otherDomain: PlayerDomain) {
        self.onePlayerDomain = oneDomain
        self.otherPlayerDomain = otherDomain
    }
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                HStack {
                    Spacer()
                    HStack {
                        VStack {
                            HStack {
                                VStack {
                                    Text("山札")
                                    Text("\(otherPlayerDomain.deck.cards.count)枚")
                                }
                                .padding(.leading, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
                                VStack {
                                    Text("ゴミ箱")
                                    Text("\(otherPlayerDomain.trashBox.cards.count)枚")
                                }
                                Spacer()
                            }
                            .font(.system(size: 12))
                            HStack {
                                ForEach(otherPlayerDomain.somePlaces, id: \.name) { place in
                                    VStack {
                                        Text(place.name)
                                        Text("\(place.cards.count)枚")
                                    }
                                }
                            }
                            .font(.system(size: 10))
                            .padding(.bottom, 8)
                        }
                    }
                    .frame(width: geometry.size.width * 0.45)
                    .overlay(
                        Rectangle()
                            .frame(width: geometry.size.width * 0.45, height: 1),
                        alignment: .bottom
                    )
                    .overlay(
                        Rectangle()
                            .frame(width: 1),
                        alignment: .leading
                    )
                    .overlay(
                        Rectangle()
                            .frame(width: 1),
                        alignment: .trailing
                    )
                    Spacer()
                    HStack {
                        VStack {
                            HStack {
                                Spacer()
                                VStack {
                                    Text("山札")
                                    Text("\(onePlayerDomain.deck.cards.count)枚")
                                }
                                VStack {
                                    Text("ゴミ箱")
                                    Text("\(onePlayerDomain.trashBox.cards.count)枚")
                                }
                                .padding(.trailing, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
                            }
                            .font(.system(size: 12))
                            .padding(.top, 8)
                            HStack {
                                ForEach(onePlayerDomain.somePlaces, id: \.name) { place in
                                    VStack {
                                        Text(place.name)
                                        Text("\(place.cards.count)枚")
                                    }
                                }
                            }
                            .font(.system(size: 10))
                        }
                    }
                    .frame(width: geometry.size.width * 0.45)
                    .overlay(
                        Rectangle()
                            .frame(width: geometry.size.width * 0.45, height: 1),
                        alignment: .top
                    )
                    .overlay(
                        Rectangle()
                            .frame(width: 1),
                        alignment: .leading
                    )
                    .overlay(
                        Rectangle()
                            .frame(width: 1),
                        alignment: .trailing
                    )
                    Spacer()
                }
                .frame(width: geometry.size.width, height: geometry.size.height)
            }
        }
    }
}

やってることは単純なのですが、枠をつけるためのコードで行数が嵩んでしまいました。。
表示はこんな感じになります。

f:id:minato_lotus:20201114203659p:plain
山・ゴミ箱・その他置き場

手札

カード情報から画像を取得して、HStackとForEachで並べます。

struct Hand {
    var cards: [Card]()
}

struct HandView: View {
    var hand: Hand
    
    var body: some View {
        HStack {
            ForEach(hand.cards.indices) { index in
                Image(hand.cards[index].imageName)
                    .resizable()
                    .scaledToFit()
            }
        }
    }
}

盤面全体

それではここまでで作ってきたViewで画面を組み立てましょう

f:id:minato_lotus:20201114205037p:plain
盤面構成図

コードは上の図の通り並べるだけです。

struct BoardView: View {
    var board: Board
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                HandView(hand: board.otherDomain.hand)
                    .frame(width: geometry.size.width, height: geometry.size.width / 8)
                FieldsView(fields: board.otherDomain.fields)
                    .rotationEffect(Angle(degrees: 180))
                DomainInfoView(one: board.oneDomain, other: board.otherDomain)
                    .frame(width: geometry.size.width, height: geometry.size.width / 6, alignment: .center)
                FieldsView(fields: board.oneDomain.fields)
                HandView(hand: board.oneDomain.hand)
                    .frame(width: geometry.size.width, height: geometry.size.width / 8)
            }
        }
    }
}

すごい簡単ですね。ここまででこんな感じの表示になります。

f:id:minato_lotus:20201115014556p:plain
Preview

まだちょっと味気ないですが、必要なものは表示できたのではないでしょうか。

その2はここまで。
その3の記事では実際に表示しているこれらのカードを操作する部分をつくっていきます。


各種カード画像はLycee公式サイトから拝借しました。

LYCEE OVERTURE TRADING CARD GAME || リセ オーバーチュア トレーディングカードゲーム

*1:一人回し:一人で2つのデッキを使って練習すること

スマホアプリで一人回しがしたい(ついでにSwiftUIお勉強)その1

スマホで一人回しがしたい

どうも、湊です。
私はTCGでよく一人回しというのをします。*1

ただこの一人回し、自宅などカードを広げることが許されるスペースでしかできません(当然)。

どこでもやりたくない...?
スマホでやりたくない...?

と思ったのでアプリを作ることにしました。
趣味のツール作成と仕事の勉強(SwiftUI)ができて一石二鳥です。

題材

今一番練習したいのはLyceeなのでLyceeで作ってみることにします。
(盤面として表現する必要のあるものが少ないので作るのが楽そうという事情もあります。)

要件(ざっくり)

  • 盤面の情報表示
    • AF, DFに配置しているカード情報
    • 山札、ゴミ箱、○○置き場などの情報
    • 手札
  • 盤面の操作
    • ドロー, シャッフル, デッキを見る, デッキからカードを抜き出し手札に加える etc...
    • カードを盤面に配置する, 盤面のカードを移動する, キャラクターをタップする(攻撃など) etc...

まずは情報表示の部分をやっていこうと思います。

表示内容洗い出し

プログラムを作るにはゲームの要素をコードで表現する必要があります。
対象を洗い出していきましょう。

カード

f:id:minato_lotus:20201113231232p:plain
カード参考(かわいい)

リセのカードはこんな感じ。
今回の要件ですと、とりあえず盤面にカードを表示できればよさそうなので、一旦テキストは無視して画像だけ呼び出せるようにしてみます。
オブジェクトとしてはIDを持っていてそれに対応した画像を呼び出せれば十分そうなので以下のようにしました。

struct Card {
    let id: CardID
    private let imageName: String
    
    init(id: CardID) {
        self.id = id
        self.imageName = id.prefix + String(id.number) + (id.pallarelSuffix ?? "")
    }
}

struct CardID {
    let prefix = "LO-"
    let number: Int
    let pallarelSuffix: String?
    
    init?(number: Int, pallarel: String? = nil) {
        guard number > 0 else { return nil }
        self.number = number
        if let pallarel = pallarel {
            self.pallarelSuffix = "-\(pallarel)"
        } else {
            self.pallarelSuffix = nil
        }
    }
    
    var string: String {
        return prefix + String(format: "%04d", number) + (pallarelSuffix ?? "")
    }
}

盤面

Lyceeの盤面はこんな感じ

f:id:minato_lotus:20201113162627p:plain
盤面
キャラクターなどのカードを置く枠が6つ、デッキやゴミ箱を置く枠があります。
この画像には現れていませんが、その他「横」と呼ばれる領域や「〇〇置き場」という領域が利用できます。
(実際の対戦では左の部分に置いている方が多いと思います)

キャラクターなどのカードを配置する枠 (6枠)

紹介していませんが、リセのカード種類には4種類(キャラクター、イベント、アイテム、エリア)があります。
そしてこの6枠にはキャラクター、アイテム、エリアがそれぞれ一枚ずつしか配置できません。*2

というわけでこのように表現してみました。

struct Field {
    var character: Card?
    var characterState: CharacterState = .stand
    var item: Card?
    var area: Card?
    
    enum CharacterState {
        case stand, rest
    }
}

紹介していませんでしたが、キャラクターは縦向き、横向きの状態で配置されることがあり、CharacterStateはそれを表現しています。

盤面全体

上記キャラクターなどを配置する6枠に加え、デッキ、ゴミ箱、「横」、「〇〇置き場」などをプレイヤーの管理領域として表現するならこのような形でしょうか

struct PlayerDomain {
    var fields = Fields()
    var deck = Deck()
    var trashBox = TrashBox()
    var somePlaces = [Place]()
    var hand = Hand()
}

struct Fields {
    var leftAF = Field()
    var centerAF = Field()
    var rightAF = Field()
    var leftDF = Field()
    var centerDF = Field()
    var rightDF = Field()
}

struct Deck {
    var cards = [Card]()
}

struct TrashBox {
    var cards = [Card]()
}

struct Hand {
    var cards = [Card]()
}

struct Place {
    let name: String
    var cards = [Card]()
}

粗はありそうですが、これでカードや盤面の情報を表現できました。
これをSwiftUIを用いて画面に表示していきます。

表示の実装

キャラクターを配置する枠

まずはキャラクターを枠に配置できるようにしましょう。
また、1枠にはキャラクターやアイテム、エリアが配置されうるため、複数のものを配置している場合はすべてが視認できるようにずらして配置してあげるのが良さそうですね。
こんな感じにしてみました。

struct FieldView: View {
    var field: Field
    
    var body: some View {
        GeometryReader { geometry in
            let fieldWidth = min(geometry.size.width, geometry.size.height)
            let areaOffset = CGSize(width: fieldWidth / 16, height: fieldWidth / 10)
            let itemOffset = CGSize(width: fieldWidth / 8, height: fieldWidth / 6)
            
            ZStack {
                if let area = field.area {
                    Image(area.imageName)
                        .resizable()
                        .scaledToFit()
                        .offset(field.character != nil ? areaOffset : .zero)
                }
                if let character = field.character {
                    Image(character.imageName)
                        .resizable()
                        .rotationEffect(.init(degrees: field.characterState == .rest ? 270 : 0))
                        .scaledToFit()
                    if let item = field.item {
                        Image(item.imageName)
                            .resizable()
                            .offset(itemOffset)
                            .scaledToFit()
                    }
                }
            }
            .frame(width: fieldWidth, height: fieldWidth)
        }
    }
}

プレビューするとこんな感じです。

f:id:minato_lotus:20201114004508p:plain
Preview

あとはこれを複数個並べたらキャラクターなどを配置する枠は出来上がりそうです。

次回

長くなってきたので今回はここまでにします。
②以降で他のViewの構築や操作周りの実装をしていきます。
SwiftUIは不慣れでまだ探り探りですので、ご指摘・ご質問等歓迎です。


各種画像はLycee公式サイトから拝借しました。

LYCEE OVERTURE TRADING CARD GAME || リセ オーバーチュア トレーディングカードゲーム

*1:一人回し:一人で2つのデッキを使って練習すること

*2:正確にはアイテムはキャラクターに装備させるものなので、キャラクターがいる状態でしか配置できません。なのでプログラムとしてもキャラクターがアイテムを持つことを表現できるとよさそうです。ただ実現したいことに対して費用対効果が高いと判断し今回はかんたんにしています。