Webアプリ習作#9

ツイート機能の仮実装について

Laravelの枠組みで動かす前にまずは簡易的な仮実装で動作を確認する

abraham/twitteroauthのインストール

TwitterAPIを操作できるライブラリ
プロジェクトディレクトリにて以下を実行

composer require abraham/twitteroauth

終わったらcomposer.jsonを開き、requireセクションにabraham/twitteroauthが追加されていることを確認

アクセストークンをメモ

ツイートを実行するTwitterOAuthにはアクセストークンが必要
本来ならLaravel Socialiteから取得できるユーザー情報を参照するところだが、仮実装なので直打ちする
app\Http\Controllers\TwitterAuthController.phpを修正

    public function callback()
    {
        $providerUser = Socialite::driver('Twitter')->user();
        dd($providerUser); // これを一時的に追加.(後で削除)

http://localhost:8000/auth/twitterにアクセスしてログイン情報を確認してtokenとtokenSecretの値をメモしておく
終わったらTwitterAuthController.phpの修正を戻す

ツイートする仮画面を作成

resources/views/test.blade.phpを作成
TwitterOAuthのコンストラクタの引数には先ほどメモしたtoken,tokenSecretの文字列を記載する
※token,tokenSecretの文字列は取り扱いに注意(公開厳禁)

<?php
use Abraham\TwitterOAuth\TwitterOAuth;
function testTweet(){
    $twitter = new TwitterOAuth( env('TWITTER_CLIENT_ID'),
        env('TWITTER_CLIENT_SECRET'),
        "メモしたtokenの文字列",
        "メモしたtokenSecretの文字列" );

    $twitter->post("statuses/update", [
       "status" =>
            'テストツイートです'
        ]);
}

if (isset($_GET['testtweet'])) {
    testTweet();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>
    test
  </title>
</head>
<body>
<a href='index.php?testtweet=true'>テストツイートをする</a>
</body>
</html>
ルーティングの編集

routes/web.phpを以下のように修正

Route::get('/', function () {
    //return view('welcome'); // これを削除.
    return view('test'); // これを追加.
});
確認

http://localhost:8000 にアクセスして"テストツイートをする"というリンクをクリック
ログインしたTwitterアカウントを確認してテストツイートが追加されていることを確認

オンラインサロンに参加してみた

知見を広めるためにはもっと人とつながる機会を増やさないとと思っていましたが、新型コロナが一向に収束しない状況で勉強会などに参加することに二の足を踏んでいた中でオンラインサロンに思い当たり、試してみることにしました。

みんサロプログラマ・エンジニア向けのサロンを探してみたところ、堤修一さんという方が主催するオンラインサロンを発見。

エンジニアと人生コミュニティの口コミと評判|みんなのオンラインサロン

31歳でプログラマになり株式会社カヤック社員を経てサンフランシスコの企業で働いていたという経歴の持ち主で、Youtubeには下記の動画を始めとして、エンジニアが発信することの大事さを説く動画をいくつかアップされています。

【エンジニアのための発信講座】#1 発信をはじめよう - YouTube

主催するサロンには現在200人以上が参加しているとのこと。
このコミュニティから良い刺激を受けられそうだと感じたので入会を申し込むことに。
プランは月額1000円と1500円があったが、違いがよくわからなかったのでとりあえず1000円のプランを選択。
みんサロのページから[このサロンに参加する]を選択すると、CAMPFIRE community内のページ

エンジニアと人生コミュニティ CAMPFIREコミュニティ

にリダイレクトされたのでそこでアカウントを作成して申し込み。
すぐにSlackの招待メールが届きました。
Slackは今まで仕事でしか使ってこなかったので、プライベート用のアカウントを新規作成した上で参加。
まずは自己紹介をとのことなので、自己紹介チャンネルに短い文章とこのブログのURLを添えて投稿してみることに。

キャラクターの命令実行処理について(2)

複数のステート間の移行

前記事ではユニットに与えられた命令を処理するために、複数のステートを適宜切り替える必要があることを説明しました。
この一連のステート間の移行をどう管理すべきでしょうか?

キュー(FIFO)で管理する

命令を入力したときに必要なステートをキューに格納することで管理する方法です。
例:攻撃コマンドを実行した場合、移動ステート・攻撃ステートの順にキューに格納します。
最初に格納されたものから処理されるので移動ステート・攻撃ステートの順に処理されます。
また、キューが空になったら待機ステートが設定されるようにしておきます。

スタック(LIFO)で管理する

命令を入力したときに必要なステートをスタックに格納することで管理する方法です。
例:攻撃コマンドを実行した場合、攻撃ステート、移動ステートの順にスタックに格納します。
最後に格納されたものから処理されるので移動ステート・攻撃ステートの順に処理されます。
また、スタックの底には待機ステートが常に格納されるようにしておきます。

何が違うのか

両者にはそれぞれ異なるメリットがあります。
キュー管理型のメリットは複数命令の順次実行に対応できる点です。
作業ユニットに複数の作業を順に実行させたい場合(PCゲームであればCtrl+クリックなどで入力させる)、キューに移動ステート、作業ステート、移動ステート、作業ステート・・・の順に格納することで実現できます。
運営や管理に主眼を置いたストラテジーゲーム向けの仕様だと思います。
一方スタック管理型のメリットは割り込み命令に対応できる点です。
戦闘ユニットが目標に向かう途中で敵の迎撃を受けた場合、スタックに新たに攻撃ステートを積むことで敵ユニットへの攻撃を開始、敵ユニットを壊滅させたらそのまま元の目標への移動を再開、という流れをプレイヤーの追加入力なしに実現できます。
こちらは戦闘や戦略に主眼を置いたストラテジーゲーム向けの仕様になります。

複合型

ユニットデータにキューとスタックの両方を持たせることも可能です。
命令が複数入力された場合最初の命令だけスタックに、以降の命令はキューに格納しておき、スタックの命令が終了したら(=待機ステートになったら)キューから次の命令を取り出してスタックに積むという形になります。
このようにすれば両者の利点が利用できますが、実際のところここまで実装しているゲームはあまり見かけません。
複雑なルールにするとプレイヤーにも理解しづらく混乱を招くのかもしれません。
ゲームの仕様に応じてキュー管理型かスタック管理型かを選ぶのがよいと思います。

キャラクターの命令実行処理について(1)

キャラクターに与えられた命令を実行させるには

ゲーム内のキャラクターに何らかの行動をさせるために必要な処理はゲームジャンルによって大きく異なります。
アクションゲームであればプレイヤーの入力に応じた動作を即時反映させるだけなのでシンプルですが、そのようなゲームばかりではありません。
この記事ではストラテジーゲームなどに多く見られるような、 「プレイヤーがキャラクターに命令を入力し、キャラクターはその命令に応じた行動を自動で実行する」という一連の処理を実装する上での基本的な考え方を説明します。

用語について

この記事では各キャラクターのことをユニットと呼びます。
プレイヤーキャラ(PC)も非プレイヤーキャラ(NPC)も同じユニットとして扱うことで処理を共通化します。
また、ユニットが何の命令を実行中かを示す状態をステートと呼びます。
各ユニットがそれぞれステートを持ち、毎フレームの処理によってユニットがそのステートに応じた行動を実行します。
PCはプレイヤーによって、 NPCはゲームのアルゴリズムによって命令を与えられることになりますが、 NPCの命令アルゴリズムについてはゲームの仕様に大きく左右されるので記事としてはフォローしません。

待機ステートについて

待機ステートは最も基本的ステートです。
何も命令が与えられていないユニットはこのステートになります。
また、何らかの命令が与えられたユニットも命令に応じた行動が終了したらこのステートに戻ります。
毎フレームの処理ではユニットは何もせずに終了します。

移動ステートについて

ユニットに移動命令が与えられると移動ステートに移行します。
移動ステートは目標を持っており、命令として与えられた目標が格納されます。特定の地点や建造物や他のユニットが目標になり得ます。
毎フレームの処理ではまずユニットが目標に到着しているかどうかを判定します。
到着していなければユニットの移動力に応じて座標移動をさせ、 到着していれば移動ステートを終了して待機ステートに移行します。

移動ステートの経路探索について

ユニットが存在するのが何も障害物のない均一な世界であれば、目標に向けてただ直線移動させればいいのですが、そのようなゲームを作る機会は多くないでしょう。
障害のあるワールド上でユニットを移動させるためには経路探索処理を実装しなければなりません。
経路探索について詳しく知りたければ、A*(エースター)アルゴリズムについて調べるといいと思います。
ユニットの目標が動くオブジェクト(他のユニットなど)の場合、経路探索は適宜更新をしなければなりませんが、 毎フレーム探索すると処理不可が大きいので数フレーム置きに間引いた方がよいと思います。
目標が動かない場合(地点や建造物など)は経路探索は命令入力直後の一回だけで済みます。
いずれの場合も探索した経路は移動ステートから参照できる形で保持させます。

その他の行動ステート

ここからはゲームの仕様次第なのですが、 例えば軍事ユニットであれば敵ユニットに攻撃する、敵の建造物を破壊するなどの命令が、 作業ユニットであれば採掘地点で資源を得る、建造物を建てる等の命令が必要になるはずです。
それぞれの命令に対応した行動ステートを実装します。
攻撃命令であれば攻撃ステートを実装し、このステートには目標として敵ユニットを持たせるようにします。
破壊命令であれば破壊ステートを実装し、このステートには目標をとして敵建造物を持たせるようにします。
毎フレーム処理ではまず各行動が終わったかどうかを判定します。
攻撃ステートの場合目標ユニットが壊滅したかどうか、破壊ステートで場合目標ユニットが破壊されたかどうかです。
終わっていればそのステートを終了して待機ステートに移行します。
終わっていなければ各ステートに応じた行動を実行させます。

ステートの移行について

これらの命令を入力した際にユニットが既に各目標に隣接していればよいですが、 そうでなければユニットを目標の場所に移動させてから各行動を実行させます。
命令を与えられたユニットはまず移動ステートが設定されることで目標への移動を開始し、目標に到達したら対応した行動ステートに移行します。
こうすることでプレイヤーが移動→各行動という命令を適宜入力することなく、一度の命令入力でユニットに一連の行動をさせることができます。

次の記事ではこの流れを実装するための考え方を説明します。

Webアプリ習作#8

前記事に引き続き主にこちらを参考にしました。
Laravel Socialiteを使ってTwitterアカウントでログイン機能 - Crieit

モデルのディレクトリ構成の変更

Laravelのプロジェクト作成時にUserモデルのファイル(User.php)がapp直下に自動生成されるが、app/Modelsディレクトリを作成しその下に移動させる
それに伴いUser.phpの中身も変更

namespace App;
↓
namespace App\Models;

また、プロジェクトディレクトリ以下を検索して

App\User

と書かれている箇所を

App\Models\User

に置換する
具体的には以下の三か所
app\Http\Controllers\Auth\RegisterController.php

use App\User;
↓
use App\Models\User;

config\auth.php

   'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
↓
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

database\factories\UserFactory.php

use App\User;
↓
use App\Models\User;
social_usersに対応するモデル作成

プロジェクトディレクトリで以下コマンドを実行

php artisan make:model Models/SocialUser

app\ModelsにSocialUser.phpが生成されたことを確認
app\Models\User.phpに以下のメソッドを追加してSocialUserモデルと連携

    public function socialUsers()
    {
        return $this->hasMany(SocialUser::class);
    }

app\Models\SocialUser.phpの方にも以下を追加

class SocialUser extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

これでusersとsocial_usersが連携される

callbackメソッドの実装

app\Http\Controllers\TwitterAuthController.phpに以下のuseキーワードの記述を追加

// ここからを追加.
use App\Models\User;
use App\Models\SocialUser;
use Auth;
use DB;
// ここまでを追加.
use Illuminate\Http\Request;
use Socialite;

同じくapp\Http\Controllers\TwitterAuthController.phpのcallbackメソッドを修正

    public function callback()
    {
        $providerUser = Socialite::driver('Twitter')->user();

        // 既存ユーザーか.
        $socialUser = SocialUser::where('provider_user_id', $providerUser->id)->first();

        if ($socialUser) {
            // 既存ユーザーならログインしてトップページへ.
            Auth::login($socialUser->user, true);
            return redirect('/');
        }

        // 新しいユーザーを作成.
        $user = new User();
        $user->unique_id = $providerUser->nickname;
        $user->name = $providerUser->name;
        $user->avatar = $providerUser->user['profile_image_url_https'];
        $user->bio = $providerUser->user['description'];

        $socialUser = new SocialUser();
        $socialUser->provider_user_id = $providerUser->id;

        DB::transaction(function () use ($user, $socialUser) {
            $user->save();
            $user->socialUsers()->save($socialUser);
        });

        // 作成したユーザーでログイン.
        Auth::login($user, true);
        return redirect('/');
    }
アクセス確認

ローカルサーバを起動してログインURLにアクセス
http://localhost:8000/auth/twitter
Twitterのアプリケーション認証画面を介してLaravelのホーム画面へ移行することを確認

DBを確認

psqlを起動してDBに接続

\c twiapp_db

テーブル一覧を表示してユーザー情報が一人分生成されていることを確認

select * from users;
select * from social_users;
再度ログイン確認

もう一度ログインURLにアクセスして正常にログインできることを確認
その後DBにアクセスしてデータが複数生成されていないことも確認

Webアプリ習作#7

前々記事に引き続き主にこちらを参考にしました。
Laravel Socialiteを使ってTwitterアカウントでログイン機能 - Crieit

DBの方針

ユーザー情報はLaravelにデフォルトで用意されているusersテーブル(マイグレーション済み)の他にsocial_usersテーブルを新規に作成し、この2つで管理する
social_usersには連携するSNS(今回のケースではTwitter)固有の情報が格納されusersデータ一つにつき複数個(連携するSNSの数ぶん)紐づけられる
つまりusersとsocial_usersは一対多の関係にある(ただし、今のところTwiiterとしか連携しないので実質一対一の関係)

Doctrine DBALのインストール

DBの既存のカラムを修正するchangeメソッドの実行に必要なライブラリ
プロジェクトディレクトリ上で下記を実行

composer require doctrine/dbal
social_usersテーブルの作成

プロジェクトディレクトリ上で以下を実行

php artisan make:migration create_social_users --create social_users

database/migrations下に[日付]_create_social_users.phpというファイルが生成されるので下記のように修正

   public function up()
    {
        Schema::create('social_users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('user_id')->unsigned()->index();// この行を追加.
            $table->string('provider_user_id')->index();// この行を追加.
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');// この行を追加.
        });
    }

上記のprovider_user_idが連携SNS(Twitter)のユニークIDに相当する

usersテーブルの拡張

usersテーブルを生成するマイグレーションファイルは'2014_10_12_000000_create_users_table.php'(Laravelプロジェクト作成時に自動生成済み)だが、これとは別にusersテーブルを修正するマイグレーションファイルを作成する
--createオプションの代わりに--tableオプションを指定する

php artisan make:migration add_social_columns_to_users --table users

[日付]_add_social_columns_to_users.phpというファイルが生成されるので編集

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            // ここからを追加.
            $table->string('unique_id')->after('id');
            $table->string('avatar')->after('password');
            $table->text('bio')->after('avatar');
            $table->string('email')->nullable()->change();
            $table->string('password')->nullable()->change();
            // ここまでを追加.
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            // ここからを追加.
            $table->string('password')->change();
            $table->string('email')->change();
            $table->dropColumn('bio');
            $table->dropColumn('avatar');
            $table->dropColumn('unique_id');
            // ここまでを追加.
        });
    }
マイグレーションを実行
php artisan migrate

※エラー発生※

LogicException  : Laravel v6 is only compatible with doctrine/dbal 2, in order to use this feature you must require the package "doctrine/dbal:^2.6".

Doctrine DBALがLarvel 6に対して新し過ぎるので、プロジェクトディレクトリのcomposer.jsonを開いてdoctrine/dbalの項目を以下のように修正

"doctrine/dbal": "^2.6",

更新して再度マイグレーション

composer update
php artisan migrate
DBの確認

psqlを起動して追加されたことを確認する
DBに接続

\c twiapp_db

テーブル一覧を確認

\dt

social_usersが追加されていることを確認
テーブルの中身を確認

\d social_users
\d users

Webアプリ習作#6

アプリ機能の大枠を決定

Twitter APIのElevated Access申請にあたってアプリの機能を簡単に説明する必要がある
習作目的のシンプルなものという方針から以下のように決定

  • 自分のTwitterアカウントにログインして固定のメッセージをツイートする
  • アプリによって生成されたツイートは一覧で表示される
  • Twitterのデータ分析は特にしない
  • 使用するTwitterの機能はツイートのみ、リツイート・いいね・フォロー・DM機能は使用しない
言語の壁

各項目は英語で入力する必要がある
DeepL翻訳:世界一高精度な翻訳ツール

Elevated Access申請

https://developer.twitter.com/en/portal/dashboardにアクセス
ProjectsのProject 1(ESSENTIAL)を選択
Apply for Elevatedを選択
f:id:jinbewe:20220319165620p:plain

(1)Basic infoを入力

f:id:jinbewe:20220319165734p:plain

What's your name?
What country are you based in?

には入力済み

What's your current coding skill level?

にSome Experienceを入力

Want updates? (optional)

チェックしない
Nextを選択

(2)Intended Useを入力

f:id:jinbewe:20220319165754p:plain

In English, please describe how you plan to use Twitter data and/or APIs. The more detailed the response, the easier it is to review and approve.
[訳]Twitterのデータおよび/またはAPIをどのように利用する予定か、英語で記入してください。より詳細な回答があればあるほど、審査や承認が容易になります。

上記の方針に基づいて入力

Are you planning to analyze Twitter data?
[訳]Twitterのデータを分析する予定はありますか?

Noを選択

Will your App use Tweet, Retweet, Like, Follow, or Direct Message functionality?
[訳]ツイート、リツイート、いいね、フォロー、ダイレクトメッセージの機能を使いますか?

Yesを選択

Please describe your planned use of these features.
[訳]これらの機能の予定使用について教えてください。

上記の方針に基づいて入力

Do you plan to display Tweets or aggregate data about Twitter content outside Twitter?
[訳]Twitterのコンテンツに関するツイートや集計データをTwitterの外部で表示する予定はありますか?

Yesを選択

Please describe how and where Tweets and/or data about Twitter content will be displayed outside of Twitter.
[訳]Twitterのコンテンツに関するツイートやデータが、Twitter以外でどのように、どこに表示されるかを説明してください。

上記の方針に基づいて入力

Will your product, service, or analysis make Twitter content or derived information available to a government entity?
[訳]あなたの製品、サービス、分析によって、Twitterのコンテンツや派生する情報を政府機関が利用できるようになりますか?

Noを選択

(3)Review画面で入力項目を確認

f:id:jinbewe:20220319165822p:plain 確認したらNext

(4)Termsを確認

確認した旨をチェックしてSubmit
※申請待ち期間が発生することなく登録後に即座に申請が終了した(通知メールなども特になし)

動作確認

申請終了したらローカルサーバを起動し
http://localhost:8000/auth/twitter にアクセス
Twitterのアプリケーション認証画面にリダイレクト→アプリに戻りログインしたユーザー情報が表示されることを確認
f:id:jinbewe:20220319165853p:plain