クソゲーのキャラクターシート登録所みたいなものを作成する

- [もう誰も横浜の果てで涙という名の同窓会だけ見えない、通称もう見えないRPGを知っていますか？|アルパカのサンドバッグ](https://whyimoeat.blogspot.jp/2017/01/mou-mienai-rpg.html)

キャラクター作成が（手順は楽なんですけど、参照するものが多いのと書き写す文字が長くて）だるいので簡略化したいと思っていたのでやってみます。


# 前準備
## laravelとかhomesteadの知識の獲得
- [【Laravel超入門】開発環境の構築（VirtualBox + Vagrant + Homestead + Composer） - Qiita](https://qiita.com/7968/items/97dd634608f37892b18a#%E3%82%A8%E3%83%A9%E3%83%BC1ip%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%81%BE%E3%81%9F%E3%81%AF-homesteadapp-%E3%81%A7%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%97%E3%81%A6%E3%82%82%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84)

これとかわかりやすいです。
おすすめ。

いまさらですけど環境はlaravel5.5にPHP7でHomesteadです。


## laravelプロジェクト作成
```
$ laravel new nsna
```

never see, never againの略字です。

## いろいろ初期化
### composer
```
$ composer update
```

php artisanとか使えなくてビビりました。

### .env
.env.exampleを修正して適当に作成します。
デフォルトではDBはhomesteadとかになってますが、そのまま使っているプロジェクトが存在する場合、そのままmigrateすると既存のDBが持っていかれます。
（そもそも既存のDB名も変えておくべきなんですかね）

DBはdatabaseだけ適当にnsnaとかに変更しておきます。


## DB作成
`mysql -u homestead`でmysqlに接続し、`create database nsna`とかでDBを作成しました。

なんか設定をする余地はあるらしいですがスルーしました。


## github登録
githubでリポジトリを作って、表示されるurlをremoteに登録してpushします。

```
$ git add .
$ git commit -m "first"
$ git remote add origin https://~~~~
$ git push origin master
```

## ローカルアクセス
Homestead.yamlをいじってアクセスできるようにしておきます。
`sites:`に以下を追加します。

```yaml:Homestead.yaml
    - map: moumienai.test
      to: /home/vagrant/code/nsna/public
```

yamlを更新したので`$ homestead up --provision`で設定を反映させます。
具体的には仮想マシンを再起動させるようです。

- [[Laravel] Homestead環境でHomestead.yamlの設定を修正した時の反映方法](https://agatayoshimi.blogspot.jp/2015/10/laravel-homesteadhomesteadyaml.html)

さらに`$ sudo vi /private/etc/hosts`でhostsを修正します。

```
198.162.10.10 moumienai.test

```

こちらも修正したので反映が必要です。

- [Macでhostsファイルを変更・反映 - Qiita](https://qiita.com/junker919/items/cc15653a681e2dcd79ea)

バージョンごとに方法が違うらしいです。
さて、試しに見てみます。

![スクリーンショット 2018-05-09 12.04.37.jpg](https://qiita-image-store.s3.amazonaws.com/0/214031/10016bd8-c17c-14d7-a679-e20d7897c10e.jpeg)

これは……たぶんうまく行ってませんね……。

メッセージに書いてあるとおり、XMLファイルとして認識されているようです。
しらんがな。

ちょっと原因がわかりませんので、困ったときの`vagrant destroy`。

直りました。
やれやれです。

![スクリーンショット 2018-05-09 20.43.37.jpg](https://qiita-image-store.s3.amazonaws.com/0/214031/a2d0c171-16b8-6ea6-98cd-ab8d2dc69d87.jpeg)

## 前準備は以上。
あ、必要ならルールブックも買っておきましょう。

key:generateとかは別にしてもしなくても。
いや本番では必要ですが。

# 設計
イメージとしてはこんな感じです。

- キャラクターの自動生成ができる。
- 自動生成したキャラクターを保存できる。
- 保存したキャラクターを参照できる。
- キャラクターのデータを修正できる。
- Twitterと紐付けることでログイン的なことができる。

僕のオンセ事情がツイッター＆どどんとふ＆適当なボイスチャット（discord多め）環境なんで、そのあたりだけで完結できればいいかなと。

というわけでテーブル構造はユーザー情報とキャラクター情報、あとキャラ作成のための情報があれば十分そうです。

画面はこんな感じ？

- トップ画面（キャラクター一覧がずらっと）
- キャラクター作成画面（保存もできる）
- ユーザー画面（Twitterログインで表示、保存したキャラが表示される）

キャラクター作成画面はなんか適当な文字列をURLにくっつけてアクセスするような感じですね。
ユーザー画面はTwitterのidをつければアクセスできるんでしょう。（作るのは僕ですけど）
修正できるようにしつつ、作成したキャラクターとTwitterをどう紐付けるかは考えどころですね。
連携すれば修正できる、連携解除は本人しかできない、みたいな感じでしょうか。
いずれにしてもそこは後回しでもいいかも。

あとキャラクターの編集は開きっぱなしにして、セッション終了時に保存するのが普通なのでセッション時間とかそのへんも気をつけられたら気をつけたいところです。
これも後回しになりそう。

## キャラクター作成に関して
ここはちょっと考えておく必要がありそうなのでもう少し掘り下げます。

アクセスするたびにキャラクター作成が自動で走るというのはややクレイジーなので、わけてやる必要がありそうです。
作成ボタンを押すとキャラクターシートのページが表示されるみたいな感じですね。
たぶん、普通にキャラクターシートにアクセスする場合と同じviewになりそうです。
渡すデータも同じですね。
urlは `/characters/new` とか `/characters/{id}` みたいになるのかな……。

能力値（良心、邪心、社会信用度）の計算は、view側というかjavascriptでやることになりそうです。
とすると渡す情報は、ルールで固定で決まる情報と、現在値と、自由記入の情報……って感じになりそうですね。

まあ、作るのぼｋ（ｒｙ

# 製造
## 現状確認
web.phpを見てルーティングを確認します。
トップにアクセスするとwelcomeを返す……としか書かれてません。

**というかviewがwelcomeしか存在しない……**
いままでハウツー記事を参考に作ったことしかなかったので、この状態のプロジェクトをじっくり眺めるのがわりと新鮮です。

migrationにはusersとpasswordsがあるし、controllerもauthとかLoginControllerとかがあります。
が、接続する手段は初期状態には存在しないみたいですね。

ふーん。（薄い感想）

## キャラクター一覧画面
ひとまず重要そうなところから作成していきます。

**たぶん、キャラクターテーブルから最新何件かを取得して一覧で表示したり、新規作成ボタンがあったりする画面です。**

もしかするとトップページかもしれません。
いや、もしかしなくてもトップページですね。

ログインボタンもありそうです。

### DB
```bash
$ php artisan make:migration CreateCharactersTable
```

最初「Characters」って名前で作成しちゃったんですけど、この名前で作成するとクラスの中身がちょっと変わるんですね。
それを下地にしつつ適当に作成します。

```php:create_characters_table.php
    public function up()
    {
        Schema::create('characters', function (Blueprint $table) {
            $table->increments('id');
            $table->string('id_rand');
            $table->integer('uid')->unsigned()->nullable();
            $table->string('player_name')->nullable();
            $table->string('actor_name')->nullable();
            $table->string('organization')->nullable();
            $table->string('age')->nullable();
            $table->string('gender')->nullable();
            $table->string('photo')->nullable();
            $table->string('good')->nullable();
            $table->string('evil')->nullable();
            $table->string('social')->nullable();
            $table->string('most_important')->unsigned()->nullable();
            $table->integer('omote1_id')->unsigned()->nullable();
            $table->string('omote1_free')->nullable();
            $table->integer('omote2_id')->unsigned()->nullable();
            $table->string('omote2_free')->nullable();
            $table->integer('omote3_id')->unsigned()->nullable();
            $table->string('omote3_free')->nullable();
            $table->integer('ura1_id')->unsigned()->nullable();
            $table->string('ura1_free')->nullable();
            $table->integer('ura2_id')->unsigned()->nullable();
            $table->string('ura2_free')->nullable();
            $table->integer('ura3_id')->unsigned()->nullable();
            $table->string('ura3_free')->nullable();
            $table->integer('ura4_id')->unsigned()->nullable();
            $table->string('ura4_free')->nullable();
            $table->integer('ura5_id')->unsigned()->nullable();
            $table->string('ura5_free')->nullable();
            $table->string('kill1')->nullable();
            $table->string('kill2')->nullable();
            $table->string('kill3')->nullable();
            $table->string('kill4')->nullable();
            $table->string('kill5')->nullable();
            $table->text('memo')->nullable();
            $table->timestamps();
        });
    }
```

外部キーは嫌いなのでスルーします。
データ的な部分は別のテーブルから読み出すようにします。
すべてnullableにしているので適当なデータが作れるよやったね。

#### 初期データ
……というわけにもいかないので初期データを作成できるようにしておきます。

- [Laravel5.6: DBとしてMySQLを用意する - Qiita](https://qiita.com/sutara79/items/e19fb386451ab9a4a5fa)

5.6じゃないですけど。

fakerを入れます。

```bash:
$ composer require fzaninotto/faker
$ php artisan make:seeder CharactersTableSeeder
```

Seederを設定します。

```php:CharactersTableSeeder.php
    public function run()
    {
        $faker = Faker\Factory::create('ja_JP');

        for ($i = 0; $i < 20; $i++)
        {
            DB::table('characters')->insert([
                'id_rand' => str_random(20),
                'uid' => $faker->unique()->randomNumber(),
                'player_name' => $faker->unique()->userName(),
                'actor_name' => $faker->unique()->userName(),
                'created_at' => $faker->dateTime(),
                'updated_at' => $faker->dateTime(),
            ]);
        }
    }
```

```php:DatabaseSeeder.php
    public function run()
    {
        $this->call([
            CharactersTableSeeder::class,
        ]);
    }
```

末尾に`--seed`をつけてmigrateすればデータが発生するようです。

### モデル
```bash:
$ php artisan make:model Character
```

```php:Character.php
    protected $guarded = [
        'id', 'id_rand',
    ];
```

fillableとかhiddenとかよく知らなかったんですけど。

- [【Laravel：Eloquentクラス】fillableとguardedの指定はどちらかだけでいい - Qiita](https://qiita.com/kk_take/items/3e0639ed605f74c34619)
- [Eloquent：シリアライズ 5.5 Laravel](https://readouble.com/laravel/5.5/ja/eloquent-serialization.html)

fillableがデータ入れていい項目の指定、guardedが入れさせたくない指定、hiddenが気軽に渡したくないデータの指定って感じ？

もう少し理解が必要そうですけどふんわりとスルーします。
最低限、idあたりは変わっちゃあかんやろってことでガードしておきます。
パスワードとかは入ってないのでhiddenは不要そうです。


### コントローラー
```bash:
$ php artisan make:controller CharacterController
```

```php:CharacterController.php
use App\Character;

// 〜〜〜〜〜

    protected function index()
    {
        $characters = Character::orderBy('id', 'desc')->paginate(10);
        return view('characters', compact('characters'));
    }

```

- [Laravelでページネーションを実装してみる | cly7796.net](http://cly7796.net/wp/php/try-to-implement-pagination-in-laravel/)

これを見つつpaginateとか使ってみます。


### ビュー
`php artisan make:auth`で作成されたビューがとてもbootstrap的なので、それを流用します。

```php:characters.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <h1 class="d-none d-sm-block">もう誰も横浜の果てで涙という名の同窓会だけ見えない　キャラクターシート保管庫</h1>
        <h5 class="d-sm-none">もう誰も横浜の果てで涙という名の同窓会だけ見えない　キャラクターシート保管庫</h5>
    </div>
<div class="container character-list">
    <div class="row justify-content-center">
        <div class="col-xs-2 panel-heading">No</div>
        <div class="col-xs-5 panel-heading">アクター名</div>
        <div class="col-xs-5 panel-heading">プレイヤー名</div>
    </div>
    @foreach ($characters as $c)
    <div class="row justify-content-center panel panel-default">
        <div class="col-xs-2 panel-body">{{ $c->id }}</div>
        <div class="col-xs-5 panel-body"><a href="{{ url('character')."/".$c->id_rand }}">{{ $c->actor_name }}</a></div>
        <div class="col-xs-5 panel-body"><a href="{{ url('user')."/".$c->uid }}">{{ $c->player_name }}</a></div>
    </div>
    @endforeach
    {{ $characters->links() }}
</div>
@endsection
```

- [Bootstrapのグリッドシステムについてまとめてみた - Qiita](https://qiita.com/akatsuki174/items/53b7367b04ed0b066bbf)


```css:nsna.css
.panel-heading{
    background-color: lightgray;
    border: solid 0.1px gray;
    padding: 5px 20px 5px 20px;
}
.panel-body{
    background-color: ghostwhite;
    border: solid 0.1px gray;
    padding: 5px 20px 5px 20px;
}
.character-list{
    min-width: 600px;
    margin-left: 1rem;
    margin-right: 1rem;
}
.character-list > .row > .col-xs-2{
    min-width: 100px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.character-list > .row > .col-xs-5{
    width: 250px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.pagination{
    margin-top: 1rem;
}
```

見よう見まねでcssも追加してレスポンシブっぽくしました。
app.blade.phpで呼び出すようにしています。


### ルーティング
```php:routes/web.php
Route::get('/', 'CharacterController@index');
```

しれっとトップにしておきます。

### こんな感じ。

![スクリーンショット 2018-05-10 2.33.43.jpg](https://qiita-image-store.s3.amazonaws.com/0/214031/b6e41705-c2d4-d314-0424-d424a3a48dbe.jpeg)


## 続く
長くなりすぎました。
続きます。