Scala、PlayFrameworkでゲームのブキ一覧Webアプリを作る

最近某インクを塗りあうゲームでブキのサブウェポン、スペシャルウェポンを覚えられないので、ブキを覚えることを目的にWebアプリを作ってみました。X帯みんな強い。

![buki-app.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/296b1afb-e957-c25d-3d8c-f7b57d427459.gif)

ソースは[こちら](https://github.com/shin620/buki-app)

ちなみにフロントエンド周りやPlayFrameworkは初心者ですので、ご承知おきください。

## 環境
+ Windows10
+ sbt 1.7.2
+ Oracle Corporation Java 1.8.0_211
+ PlayFramework 2.8.18

## プロジェクト作成
`sbt new`コマンドでHello Worldプロジェクトを作成します。プロジェクト名以外はデフォルトにしました。

```
sbt new playframework/play-scala-seed.g8
...
name [play-scala-seed]: buki-app
organization [com.example]:
play_version [2.8.18]:
scala_version [2.13.10]:
cd buki-app
sbt run
```

すると、以下のように表示されます。

```
[info] loading settings for project root from build.sbt ...
[info]   __              __
[info]   \ \     ____   / /____ _ __  __
[info]    \ \   / __ \ / // __ `// / / /
[info]    / /  / /_/ // // /_/ // /_/ /
[info]   /_/  / .___//_/ \__,_/ \__, /
[info]       /_/               /____/
[info]
[info] Version 2.8.18 running Java 1.8.0_211
[info]
[info] Play is run entirely by the community. Please consider contributing and/or donating:
[info] https://www.playframework.com/sponsors
[info]

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Enter to stop and go back to the console...)
```

ブラウザで
http://localhost:9000/
にアクセスすると、以下のように表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/9261c456-078f-ae13-d9d4-63aab1665f8a.png)

## ブキページ追加

次にブキページを追加してみます。

その前にプロジェクトのフォルダ構成が分かっていないと追加できないので、フォルダ構成から見てみます。以下がプロジェクトフォルダ構成をtreeコマンドで示した結果です。たくさんフォルダがあるので、適宜省略しています。

```
buki-app
├─.g8
│  └─...
├─.metals
├─.vscode
├─app
│  ├─controllers
│  └─views
├─conf
├─logs
├─project
│  ├─project
│  ...
├─public
│  ├─images
│  ├─javascripts
│  └─stylesheets
├─target
│  ├─global-logging
│  ...
└─test
    └─controllers
```

これを見ると、app/controller配下のscalaファイルにリクエストを受け取った後の処理を記載、app/views配下のhtmlファイルにブラウザに表示する画面を記載、conf\routesに各HTTPリクエストを定義するイメージであると予想しました。

そしていろいろといじった結果、以下の手順でページを追加することができました。

1. conf\routesに以下を追記します。
    ```
    GET     /:buki_id                   controllers.BukiController.buki(buki_id)
    ```
    :（コロン）をつけることで、該当パラメータを変数として扱うことができます。今回はブキにIDを振ることにし、そのIDをパスパラメータとして指定することにしました。これにより、例えばhttp://localhost:9000/1 でブキIDが1のページに飛ぶことができます。
1. ブキの情報を返すコントローラーを作成します。既にあるapp\controllers\HomeController.scalaを参考に、以下のように作成してみました。`views.html.buki()`で、app\views\buki.scala.htmlをもとにレンダリングしたHTMLを返しています。
    ```scala:app\controllers\BukiController.scala
    package controllers
    
    import javax.inject._
    import play.api._
    import play.api.mvc._
    
    // This controller returns buki information
    @Singleton
    class BukiController @Inject() (val controllerComponents: ControllerComponents)
        extends BaseController {
    
      def buki(buki_id: String) = Action { implicit request: Request[AnyContent] =>
        Ok(views.html.buki("わかばシューター"))
      }
    }

    ```
1. app\views\buki.scala.htmlを以下のように作成します。一番上の`@(name: String)`が関数`views.html.buki()`の引数に対応しています。名前以外も受け取る必要がありそうですが、とりあえず名前を受け取って表示することにします。
`@()`のかっこ内にはScalaのコードを書くことができます。（詳しくは公式ドキュメントの[テンプレートエンジン](https://www.playframework.com/documentation/ja/2.3.x/ScalaTemplates "テンプレートエンジン")を参照）
    ```html:app\views\buki.scala.html
    @(name: String)
    
    @main("Welcome to Play") {
      <p>@(name)の情報</p>
    }
    ```
1. app\views\index.scala.htmlにaタグを追加し、リンクを追加します。
    ```html:app\views\index.scala.html
    @()
    
    @main("Welcome to Play") {
      <h1>Welcome to Play!</h1>
      <a href="http://localhost:9000/1">わかばシューター</a>
    }
    ```

これでブキページに飛べるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/55bf4c8a-210d-e508-2fde-0b88d8551a88.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/6d826b50-745a-cad0-5c5a-08136111c97d.png)

## ブキの一覧を表示する

ブキの情報の更新などは行わない想定のため、今回はCSVで管理することにしました。以下のようなCSVファイルを用意します。

```:app\controllers\buki.csv
1,わかばシューター,スプラッシュボム,グレートバリア
2,レプリカヒーローシューターレプリカ,キューバンボム,ウルトラショット
3,スプラシューター,キューバンボム,ウルトラショット
(以下省略)
```

CSVにはブキのID、名前、サブウェポン、スペシャルウェポンの順で記載しています。

次に、これを読み込むモジュールを作成します。csvファイルを文字列として読み込み、単純に行ごとにカンマ区切りで分割した配列として扱っています。

```scala:app\controllers\Buki.scala
package controllers

import scala.io.Source

class Csv(filePath: String, coding: String = "utf-8") {
  def readAll: Array[Array[String]] = {
    val source = Source.fromFile(filePath, coding)
    val lines = source.getLines
    lines.toArray.map(_.split(","))
  }
}

class BukiFormatCsv(filePath: String = "app/controllers/buki.csv")
    extends Csv(filePath, "utf-8") {
  def getRow(id: String): Array[String] = readAll.filter(_(0) == id)(0)
  def names: Array[String] = readAll.map(_(1))
}

class Buki(id: String) {
  private val bukiCsv = new BukiFormatCsv()
  private val bukiData = bukiCsv.getRow(id)

  def name: String = bukiData(1)
  def subWeapon: String = bukiData(2)
  def specialWeapon: String = bukiData(3)
}
```

次に、コントローラーを書き換えます。書き換えた部分のみ示します。

```scala:app\controllers\HomeController.scala
  def index() = Action { implicit request: Request[AnyContent] =>
    val data = new BukiFormatCsv()
    Ok(views.html.index(data.readAll))
  }
```

```scala:app\controllers\BukiController.scala
  def buki(buki_id: String) = Action { implicit request: Request[AnyContent] =>
    val bukiData = new Buki(buki_id)
    Ok(views.html.buki(bukiData))
  }
```

次に、HTMLを書き換えます。index.scala.htmlでは、ブキを一覧表示するためにscalaのfor式を使っています。

```html:app\views\index.scala.html
@(bukis: Array[Array[String]])

@main("ブキ一覧") {
  <h1>ブキ一覧</h1>
  <ul>
  @for(buki <- bukis){
    <li>
      <a href="http://localhost:9000/@buki(0)">@buki(1)</a>
    </li>
  }
  </ul>
}
```

```html:app\views\buki.scala.html
@(buki: controllers.Buki)

@main("ブキ情報") {
  <p>@(buki.name)の情報</p>
  <p>サブ: @(buki.subWeapon)</p>
  <p>スペシャル: @(buki.specialWeapon)</p>
}
```

ここまでのモジュール構成は以下の図の通りです。

![buki-app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/edb43c4e-462d-0a62-ee43-2866b3971ad7.png)


以上の変更を行うと、Web画面は以下のような表示になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/8d138e6b-a197-c928-cb88-10a93f2c253c.png)

## CSS

app\views\main.scala.htmlでpublic\stylesheets配下のスタイルシートを読み込んでいるようなので、そちらを編集しました。詳細は省略しますが、play特有の何かがあるというわけではありませんでした。なので、htmlやcssをこれまで使ってきた方からすれば、特につまずくことは少ないのではないかと思います。

スタイルを当て、以下の画像のようにしました。ブキの画像を用意するのは手間だったのでダミー画像を使用しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/5aca6656-8064-47ca-0afb-aa0981be6219.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/3444c46f-953b-50e1-6718-aebedbcd1bb7.png)

## まとめ
scalaのplay frameworkを用いてブキ一覧を見れるWebアプリをつくりました。現状でも単語帳みたいに使えると思います。
本当にブキのサブスぺを覚えたいならクイズ機能とかを入れた方がいいような気もしますが、それは気が向いたらにします。
これまで、PlayFrameworkは簡単なHelloWorldのAPIを作ったことしかありませんでした。今回初めてPlayFrameworkでフロントエンド周りの実装をしたのですが、思ったよりも使いやすかったです。

ゲームで勝てるように頑張ってブキを覚えたいと思います。