Marqueeタグに詳しくないと攻略できないブロック崩しゲーム

はじめまして, 都内でCTO(Chotto Technology Ojisan)やCFO(Chotto Frontend Ojisan)をしている @sadnessOjisan です. この投稿は[クソアプリカレンダー2](https://qiita.com/advent-calendar/2018/kuso-app2)の13日目の投稿です. 

先人たちはすごいクソの山 [^kuso] を作っていたので, ぼくも負けじとクソを作ろうと思い, 気合を入れました. そこで玉もバーも`marquee`タグで実装されたブロック崩し [Marquee Breakout](https://inspiring-galileo-db9414.netlify.com/) を開発しました. （残念ながらPCのみでの対応です. 小さい画面だとプレイできないようになっています. ）
ソースコードは記事の末尾にあります. 初めての投稿なのですが, よろしくお願いいたします. 

[^kuso]: [「通知止まらんwww」を体験できるアプリを作った](https://qiita.com/okumura_daiki/items/ed6398c06c1b40d10c6c) / [社会に一石を投じるクソアプリ開発](https://qiita.com/de_teiu_tkg/items/e82870eca1f265807dbe)

ゲームはこちら: https://inspiring-galileo-db9414.netlify.com/

## はじめに

みなさん, `marquee`タグってご存知でしょうか?？こんなやつです. 

![sample.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/323ae349-336c-7de7-800a-dc94e0345177.gif)


`marquee`タグって昔のHPにあった, 要素をスライドさせられるタグのことです. 

```{html}
<marquee>あなたは100人目のお客様です</marquee>
```

実はこの`marquee`タグは, 入れ子にすることができます. 

```{html}
<marquee direction='up' height='400px'>
  <marquee>入れ子だよ〜〜〜〜</marquee>
</marquee>
```

入れ子にすると何が起きるでしょうか. なんと, 片方の`marquee`の移動方向を上向きにしていると, 斜めに動くようになります　. 

![ireko.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/ff3c4a12-2c0f-a109-ac2d-4dac66a3616c.gif)


そして, 入れ子にするとなんとブロック崩しを作れます（困惑）. 
バーは普通のmarqueeで作れます. 

```{html}
<marquee behavior='alternate'>ーーーーーーーーーーーーーーー</marquee>
```

`behavior='alternate'`は跳ね返りを指定するためのプロパティです. 

玉は斜めに動かす + 跳ね返り指定で作れます. (入れ子 + `alternate`指定)

```{html}
<marquee direction='up' height='400px' behavior='alternate'>
  <marquee behavior='alternate'>●</marquee>
</marquee>
```



今日は`marquee`タグ実装したブロック崩しゲームをクソアプリとして持ってきました！それが, `marquee`タグの挙動に詳しくないとなかなか高得点を取れないゲーム, [Marquee Breakout](https://inspiring-galileo-db9414.netlify.com/)です. 

![gamesample.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/58c787e9-f9c0-fc19-ea4a-8df07fa30ad1.gif)

## この記事で学べること
* [Marquee Breakout](https://inspiring-galileo-db9414.netlify.com/)の遊び方
* `marquee`タグが魅せる未来
* `marquee`タグでブロック崩しを作る方法
* ログインとスコア保存機能をもったサービスを, 無料で開発・ホスティングできる方法

## Marquee Breakout
### 遊び方
1. 全国ランキングに挑戦したい人はサインアップする.
2. マーキーのパラメータを入力してスタートする.
3. 球が下に落ちないようにパラメータを調節する.

ぜひとも, `marquee`に変わって台頭してきた今時の子たちに球をぶつけてください. [^browselify]
<img width="396" alt="スクリーンショット 2018-12-13 1.23.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/89256/bbf3a734-ee2b-b038-63b4-36534deec52d.png">

 [^browselify]: なにぃ！？「そこは`browselify`じゃなくて`Webpack`だろ」だと！? 画像を差し替えるPRをください(涙)

どれを壊しても1点です.

## `marquee`タグが魅せる未来

### Marqueeタグは今のHTML5の仕様では廃止されている
[マーキー要素](https://developer.mozilla.org/ja/docs/Web/HTML/Element/marquee)は様々な理由からHTML5から廃止されたタグです. 明確な理由は私が探す限りでは見つけられなかったのですが, アクセシビリティが悪い, セマンティックなタグではないといった批判がされていました. [^html4]
[^html4]: 他にも, HTML5では`center`タグや`blink`タグといったものも削除されています. 

### Marqueeタグの使い道
そんなmarqueeタグですがこの子は多種多様なことができます. 
その面白い挙動は [marqueeのいろいろな移動](http://sound.jp/ptf/html/marquee.html)にまとめられていたので, 再現してみました. 

#### 文字を流す
これは先ほどのものです. 
![sample.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/2b34a6ed-bb1a-3189-566c-b443a67ac027.gif)

```{html}
<marquee>あなたは100人目のお客様です</marquee>
```

#### 折り返す

![alt.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/a183996c-30c9-fc19-d4e3-6d81fd3408cd.gif)

```{html}
<marquee behavior='alternate' scrollAmount='50'>折り返すよー</marquee>
```

#### 上にも流せる

![ue.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/f7e4bc77-2978-e01b-1622-c60c3fe85445.gif)

```{html}
<marquee direction='up' scrollAmount='50' height='500px'>上上上上上</marquee>
```

#### 波を作る

[marqueeのいろいろな移動](http://sound.jp/ptf/html/marquee.html) さんから拝借しています. 

![nami.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/34f9a56a-58dd-bfa7-9d30-6cf8453d0a72.gif)

```{html}
<marquee bgcolor="#f0f0ff" width="500">
  <marquee bgcolor="#f0f0ff" height="50" width="40" direction="up" behavior="alternate">
    波～
  </marquee>
</marquee>
```

#### 斜めに流す
これも先ほどのものです. 

![ireko.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/c71b6a4d-7c0e-5eda-5609-e4f5a211a654.gif)

### マーキーの入れ子でブロック崩しを作れる

[marqueeのいろいろな移動](http://sound.jp/ptf/html/marquee.html) さんから拝借しています. 

![break.gif](https://qiita-image-store.s3.amazonaws.com/0/89256/a2a4aa85-703b-b403-4d6e-58de3ee94c8e.gif)

```{html}
<marquee bgcolor="#f0f0ff" width="300" behavior="alternate" scrollamount="11">
  <marquee bgcolor="#f0f0ff" width="16" height="270" direction="up" behavior="alternate" scrollamount="4">●</marquee>
</marquee>
<BR>
<marquee bgcolor="#f0f0ff" width="300" behavior="alternate" scrollamount="12">
</marquee>
```

### ブロック崩しを作るにあたっての課題
このように入れ子にして多様な動きを出せました. しかしブロック崩しの動きであって, ブロック崩しではありません. ブロック崩しをするためにはブロックとの衝突判定, バーとの衝突判定や跳ね返りが必要です. またゲームのスコアやゲームオーバーしているかの状態の管理も必要になってきます. それらはどのようにして実装すればよいのでしょうか. それらを次の章にまとめてみました. 

## 技術的なおはなし
### フロントエンド側
衝突判定を行うために`JavaScript`を利用して`marquee`タグの要素を監視・制御しました. 

#### Marqueeの挙動
プロパティを設定することで動き方を制御できます. 方向は `direction`, 跳ね返りの挙動は `behavior`, スピードは`scrollamount`で指定できます. どのようなプロパティを利用できるかは[<marquee>: マーキー要素 (廃止)](https://developer.mozilla.org/ja/docs/Web/HTML/Element/marquee)を参照すると良いでしょう. しかしアニメーション中にプロパティを書き換えても, アニメーション終了後にしか反映されないため注意が必要です. 

ブロックやバーにぶつかると跳ね返るというのは, `behavior='alternate'`とすれば実現できます. 

|  プロパティ名  |  制御できる箇所  |
| ---- | ---- |
|  behavior  |  スクロール方法  |
|  bgcolor  |  背景色  |
|  direction  |  スクロール方向  |
|  height  |  スクロールの高さ  |
|  hspace  |  高さのpadding  |
|  loop  |  繰り返し回数  |
|  scrollamount  |  1インターバルにおける要素の移動ピクセル数  |
|  scrolldelay  |  １インターバルとして扱う時間  |
|  truespeed  |  scrolldelayに足切りを設けるかのフラグ  |
|  vspace  |  垂直方向のpadding  |
|  width  |  スクロールの幅  |

#### marqueeのプロパティの数値調整
背に腹は代えられません. `React` を使いました. 反則かもしれませんが, 便利なものはどんどん使って行きましょう. `marquee`は`JSX`の中でも動きます. `marquee`のプロパティに`props`を渡してプログラマブルな`marquee`タグを作ることができます. [^sc]

[^sc]: （注）`styled-components`からは`marquee`タグをスタイリングできないため,スタイルは直接設定しましょう. 

```{tsx}
<GameCanvas>
  <Score>your score is {score}. </Score>
  <Blocks />
  <marquee
    behavior={ballYBehavior}
    scrollamount={ballYSpeed}
    height={GAME_SCREEN_HEIGHT}
    style={{ position: "absolute", top: `${bounceBorder}px` }}
    direction={vBallDirection}
  >
    <marquee
      behavior={ballXBehavior}
      scrollamount={ballXSpeed}
      direction={hBallDirection}
      width={`${width}%`}
    >
      <Ball ref={this.ball}>●</Ball>
    </marquee>
  </marquee>
  <marquee
    behavior={barBehavior}
    scrollamount={barSpeed}
    style={{ position: "absolute", top: GAME_SCREEN_HEIGHT }}
  >
    <span ref={this.text}>--------------------</span>
  </marquee>
</GameCanvas>
```

ちなみに`React`で`Marquee`タグ ([ReMarquee](https://github.com/sadnessOjisan/remarquee)) を作ったこともあります. 
FYI. [Marqueeタグでブロック崩しを作りたい](https://speakerdeck.com/sadnessojisan/remarquee-number-megurocss)

#### marquee内の要素の位置を取得する
`ref`を取ってDOMの位置を取得します. `marquee`移動中の位置は`setTimeout`で毎sec取得し, `state`を更新していきましょう. その`state`は球, バー, ブロックにpropsとして渡して生きましょう. 「なんかやばいことしていない？」だって？クソアプリなので実装はクソで良いんですよ.  [^tekitou]

[^tekitou]: 職場で私は非常に真面目です. 今日だけです. 

```{tsx}
private text = React.createRef<HTMLParagraphElement>();
private ball = React.createRef<HTMLParagraphElement>();
```

```{tsx}
setInterval(() => {
  if (this.text.current && this.ball.current) {
    const barPosition = this.text.current.getBoundingClientRect();
    const barLeftPosition = barPosition.left;
    const barTopPosition = barPosition.top;
    const barBottomPosition = barPosition.bottom;
    const ballPosition = this.ball.current.getBoundingClientRect();
    const ballLeftPosition = ballPosition.left;
    const ballWidth = ballPosition.width;
    const ballRightPosition = ballLeftPosition + ballWidth;
    const ballTopPosition = ballPosition.top;
    const ballBottomPosition = ballPosition.bottom;
    const barWidth = this.text.current.getBoundingClientRect().width;
    const barRightPosition = barLeftPosition + barWidth;
    this.setState({
      barPositon: {
        left: barLeftPosition,
        right: barRightPosition,
        top: barTopPosition,
        bottom: barBottomPosition
      },
      ballPosition: {
        top: ballTopPosition,
        left: ballLeftPosition,
        right: ballRightPosition,
        bottom: ballBottomPosition
      }
    });
  }
}, 1);
```

#### marqueeタグ要素とブロックの衝突判定を行う
ここが僕の一番の工夫ポイントです. 実は`block`一つ一つをコンポーネントとして作り, その中で, `block`の位置の`state`と`props`経由で知られせるバーと球の一から, ブロックにぶつかったkどうかの衝突判定を行なっていました. 

玉の位置はms間隔で`props`から流し込まれます. あとは`props`が変更されるたびに `static getDerivedStateFromProps`でstateを衝突判定と`state`の更新を行うと良いです. [^getDerivedStateFromProps]

[^getDerivedStateFromProps]: `static getDerivedStateFromProps`は`React` v16.3で導入されたAPIです. `React`v17から, `props`が更新されたときに発火される組み込み関数の `componentWillReceiveProps` や `componentWillUpdate`の利用は非推奨になります. 

```{tsx}
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
    const blockState = prevState;
    const { onCollide } = nextProps;
    const { left, right, top, bottom } = nextProps.ballPosition;
    if (left && right && top && !blockState.isCollapsed) {
      if (
        top <= blockState.bottom &&
        right >= blockState.left &&
        left <= blockState.right &&
        bottom >= blockState.top
      ) {
        onCollide(bottom);
        return { isCollapsed: true };
      }
    }
  }
```

#### 最後にお型付け
とりあえず`any`って書いておきましょう. クソアプリカレンダーなので大丈夫です. ここには上司もレビュアーもいません. [^type]

![スクリーンショット 2018-12-09 23.38.59.png](https://qiita-image-store.s3.amazonaws.com/0/89256/5ea2b7e4-e347-8e0a-7933-c8e8d5c2cbf0.png)

ちなみに`styled-components`で使うpropsの型付けの方法が分からなくてて苦労しました. 

[^type]: 普段のお仕事ではきちんと型を書いています. こんなことをしているのは今日だけですよ. 今日だけ. 


### サーバー＆インフラ側
@kt3kさんの[Netlify+Node.js+MongoDBでJAMstackな草を生やすサービスをOSSで公開しました!](https://qiita.com/kt3k/items/10df41c74a752a4c6c9c)という記事を参考にしました. この記事では, スケーラブルで高機能なWebアプリケーションを0円で開発・公開するための方法を紹介しており, 当記事でもその構成を参考にしました. また記事内には, 

> Web 2.0 になってフロントエンド JavaScript が複雑化する中で、これが正解と言える Web プログラミングのスタイルがなかなか分かりづらい時期が続いていたように思います。また、上から下までの動的な Web サイトを作るのに必要な知識が相当に増えてしまったように感じました。そのせいか、(統計がある訳ではありませんが)自分の観測範囲では、誰かが趣味で作った変な Web サービスを見かける機会が減ってしまったように感じました。
...
この記事を読んで、変な思いつきの/独創的な Web サービスを作る人が出てくれたら良いなと思います。

とあり, 変な思いつきなWebサービス作りのために参考にさせていただきました. ソースコードも公開されているため, 興味がある方は一読されることをオススメします. 

#### NodeJS
[NodeJS](https://nodejs.org/en/)は, サーバーサイドで動くJSです. `express`というライブラリを利用することで, ルーティングの設定やリクエストの`parse`を簡単に行え, 手間がかかる設定も少なくWebアプリを動かせます. 

#### Auth0
[Auth0](https://auth0.com/)は, 認証機能を提供するサービス(IDaaS)です. このサービスを使うことで, ログインやユーザーを管理する機能を自前で作ることなく, それらの機能を利用できます. 実装もとても簡単で, フロントエンド側のJavaScriptには`auth-0.js`というライブラリを入れてそのメソッドを呼ぶだけでよく, サーバー側もSDKとを入れて, 認証したいルーティングに少し設定を書くだけで認証ができます. 

これにより, ログインユーザーでしかAPIにアクセスできなくしたり, ログインしたユーザーの特定などができます. Marquee Breakoutではスコアを登録するときの機能をログインユーザーしか行えないようにしています. 

#### mongodb + mongoose + mlab
[mongodb](https://www.mongodb.com/) は, 「`JSON` をそのまま保存できて, そのまま取り出せるかのように扱うことができてしまうDB」です. スキーマ的なものは, アプリケーションのコードにそのまま書けて, どんどん破壊的な変更を加えても動きはするので, スピード重視開発だとすごい楽です. 操作やデータの構造も直感的なのでそれもスピーディな開発を行う上で非常に助かりました. 

そして, [mlab](https://mlab.com/)はそのDBをクラウド上にホスティングしてくれるサービスで500MBまでならタダで使えます. 同様のサービスに`mongodb`が提供している[MongoDB Atlas](https://www.mongodb.com/cloud/atlas)というものがあります. 本当はこちらを採用したかったのですが, プランの設定, `NodeJS`のバージョン, `mongodb`のバージョン, [mongoose](https://mongoosejs.com/) (`mongodb`のODM)のバージョンが合わないと使えなさそうだったので, 少し格闘した末に諦めて`mlab`を採用しました. 

#### Now
これらの 機能を呼び出せるサーバーは Now というサービスにデプロイしています. Nowも無料で利用できます. `package.json`や`Dockerfile`を書いて 

```{zsh}
$ now 
```
ってすれば, もうそれだけでホスティングできます. 無料プランでは発行されるURLはランダムなのですが, 固定化できたり, デプロイ前に発行されるURLも知れるので, クライアント側にあらかじめ向き先を登録することができます. 

#### Netlify
クライアントサイドのコードは [Netlify](https://www.netlify.com/)にデプロイしています. 僕は[Zeit](https://zeit.co/home)教なのでこちらも`Now`にあげたかったのですが, クソアプリカレンダーは失敗しても良さそうな雰囲気を感じ, 新しいことに挑戦してもバチが当たらないと思い, `Netlify`を使ってみました. これはGithubと連携し, あるブランチにコードがpushされたらそのコードを対応する向き先にデプロイしてくれるサービスです. CDっぽいことをしてくれるだけでなく, デプロイ先も提供してくれるのに無料で利用できます. 学習コストがかからなかったのでとても助かりました. 

## 終わりに
後半は真面目な話になってしまいましたが, この[クソアプリ](https://inspiring-galileo-db9414.netlify.com/)はいかがでしたでしょうか. 是非, 全国TOP目指して頑張ってください!!!

ブロック崩しコード: https://github.com/sadnessOjisan/MarqueeBreakout

（P.S.） Qiitaの投稿楽しいですね!!いつも見てばっかりだったのですが, これからもがんばって投稿していきたいです!