TypeScript & React & Firebase で何かつくってみる11 Firestore 3

だんだん 話が入り組んできた. 丁寧に書くと長くなるし, 端折ると訳分かんなくなるし. 文章上手にかける人は大したもんだ.

カードを裏返せるようにする

データに状態を持たせて表か裏か判別するのは簡単だ.

気にしたのは将来的に 「裏返っているカードの表の情報を取得できない」ようにしたくなった場合だ.  ・・・とはいえ,想像力が追いつかないので進むしか無い.

Card 型 を CardSurface Card に分けて管理する.

type CardSurface = {
 name: string
 color: string
 isFace: boolean
}
type Card = {
 face: number
 back: number
}
type Deck = {
 cards: Card[]
 allowedDest: string[]
}

const surfaces: CardSurface[] = [ /* 表52 + 裏1 = 53 の面情報の配列 */ ];

そして, あらかじめ 使用するすべてのカード面デザインを表裏にかかわらず  surfaces に配置する.
Card には この配列のインデックス番号を格納する.

isFace Card CardSurface のどちらに持たせるか迷ったが, 将来性を考えた結果こちらの方法を採用した.  一見 Card に持たせた方がわかりやすいが,「両面が表」という表現ができない. そういうゲームもあるかもしれないと考えた. それだけが理由ではないが.

このカードを「裏返す」場合は face back を入れ替えれば良い.
現在の仕様では カード操作はすべて actions 経由で Cloud Functions 上で操作する, と決めたので 裏返し処理を Cloud Function で実装する.

type Action = {
 type: string
}
type CardAction = Action & {
 target: string
 targetIndex: number
}
const turnCardAction = (action: CardAction) => {
 const deckRef = fireStore.collection('fields').doc(action.target);
 return fireStore.runTransaction(transaction => {
   return transaction.get(deckRef).then(snapshot => {
     const deck = snapshot.data() as Deck;
     const index = resolveIndex(action.targetIndex, deck.cards.length);
     if (isCard(deck.cards[index])) {
       // カッコいい swap
       [deck.cards[index].face, deck.cards[index].back] = [deck.cards[index].back, deck.cards[index].face];
     }
     transaction.update(deckRef, deck);
   });
 });
}

 face と back の入れ替えには ごく最近覚えたカッコいい swap を使う.
上のコードだけではいろいろ説明不足だが まぁ感じ取ってほしい.

以前は 一部にしかトランザクションを使っていなかったが, 「現在の値を見てから更新する」という処理はすべてトランザクションでなければならないと思えたのでそうしている.
「今表だから裏にしよう」と言ってる間に別のだれかが裏にしているかもしれない.

山の作成・削除

公式を拾い読みしていて, コレクション自体を監視する仕組みを見つけた.

今まで deck, alice, bob をそれぞれ listen していたが, これで 1箇所にまとめられる. さらに 新しい  の生成・削除にも気づくことができる.

 const [decks, setDecks] = useState<Map<string,Deck>>(new Map());
 useEffect(() => {
   const fieldsRef = firestore.collection('fields');

   fieldsRef.onSnapshot(snapshot => {
     const newDecks = new Map();
     snapshot.forEach(doc => {
       const deck = doc.data() as Deck | undefined;
       if (deck) {
         newDecks.set(doc.id, deck);
       }
     });
     setDecks(newDecks);
   });

山の追加・削除の実装は, 単なる「コレクションへドキュメントの追加・削除 」なので省略する.

ただ, カードが入っている山を消すと そこにあるカードが一緒に消滅してしまうため, 空っぽの山だけ削除できるようにした.

firestore.rules

match /fields/{deckId} {
  allow read, create, update: if request.time < timestamp.date(2020, 5, 23);
  // cards が空のときだけ 削除 を許可する.
  allow delete: if resource.data.cards.size() == 0;
}

配列長の取得は size() メソッドを使う.
これは JavaScript の Array ではないので length などは使えない.

rules で使う型は以下に一覧がある.

このチェックと更新はアトミックと考えていいんだろうか.
そうでないと困るし, だからこそ そうに決まっているとは思うが.

山に名前をつける

今まで 山の ID に deck, alice, bob と付けていたが, 名前を ID にしてしまうと後で変えられない. 

自由に増減できるようになると 名前の衝突も考えねばならず面倒なので ID と名前は分けて管理すべきだ.    ・・・と作ってみて気がついた.

名前をフィールドで管理し, 変更可能にする.
ちょうど最近, いろいろ参考になりそうな記事があったので その一部を参考にした.

画像1

青い ■ は 裏面 のつもりだ. 上の絵ではやってないが 裏返すこともできる. ただし場所を指定するUIが無いので固定位置のみだ.


進んではいる.
が, 難しいところを全部後回しにしているだけのような気もする.

この記事が気に入ったらサポートをしてみませんか?