コンピューターが決定を下すか、一緒にプレイするためのAIを作成する

ゲーム内で意思決定を行う独自の人工知能の作成がどれほど簡単かを考えたことはありますか? しかし、それは本当に簡単です。 彼に最初からランダムな決定をさせますが、後で彼を教育し、状況を分析するように彼に教えることができます。 この記事では、ボットの作成方法を説明し、数分で独自のボットを作成する方法を示します。 私たちのコンピューターは、 Tronというゲームのクローンをプレイします。つまり、バイクで敵を倒す必要がある部分をプレイします。

画像
cat gifファイルの下で、10メガバイト。


ゲームについて


ゲームでは、光の壁を残すオートバイを制御します。 競技場は限られており、ライバルは同じバイクを持っています。 バイクは絶えず乗るので、方向転換することしかできません。 フィールドの空いている席がなくなり、障害物を回避することがより困難になります。 最も長く続く人が勝ちます。 node.jsとsocket.ioを使用して、ゲームクローンブラウザベースのマルチユーザーを作成しました。 2つのボタンからの制御-左折と右折。

ボットインターフェイス


私はsocket.ioを使用しているため、サーバー上のプレーヤーの処理は、socket.ioが作成する特別なソケットオブジェクトの配列に対する作業という形で行われました。 これらのオブジェクトのうち、id、emitおよびbroadcast関数のみを使用しました。 したがって、ゲーム自体に問題はありません。別のユーザーがプレイしているように、ソケットインターフェースを実装して処理に使用できます。 BotSocketクラスを呼び出しました。
ボットの発行(イベント、データ)メソッドは、サーバーからの着信データを使用して、クライアントとほぼ同じアクションを実行します。
  1. 追加すると、すべてのプレイ中のオートバイのデータを保存します
  2. バイクを追加するときに、バイクへのリンクを保持します
  3. 再生中のすべてのオートバイのデータを更新します。
  4. ゲームの再起動時に状態をリセットします

オートバイの制御コマンドをサーバーに送信するには、通常のユーザーからのコマンドを処理するゲームオブジェクトへのリンクを保存する必要がありました。 GameクラスのメソッドはonControl(ソケット、データ)と呼ばれるため、このメソッドをBotSocketに追加しました
BotSocket.prototype.control = function(data) { this.game.onControl(this, data); }; 

オートバイのデータ更新コマンドがサーバーから到着すると(移動がコミットされた)、制御されたオートバイがあるかどうか、衝突したか移動したかを確認し、成功した場合はAIが動作するためのメインメソッドを呼び出します- update()
インターフェイスの準備ができました。AIを追加できます。

人工知能


どんなに大きく聞こえても、コンピューターをプレイするプレイヤーのゲームでは、AIまたはボットと呼ぶのが慣習です。 BotSocketオブジェクトには、決定を下すために必要なゲームデータが含まれています。 考えられる解決策は3つだけです。
  1. 何もせず、まっすぐ運転
  2. 右折
  3. 左折


ボットを書くことにしたとき、私はこれを行う方法がわかりませんでした。 私は非常に簡単なコードを試しました:
 BotSocket.prototype.update = function() { var r = Math.random(); if (r > 0.95) { this.control({'button': 'right'}); } else if (r > 0.90) { this.control({'button': 'left'}); } } 

動作は次のようなものでした:
画像

私は彼を見て、大きな喜びを感じました。彼は今では独立しているように思えました。 彼自身が生きているかのようにそこを破って、生き残るための試みを探しているように見えました。 感動的な光景。

しかし、私は彼に可能な限り生きてほしかった。 AIがゲーム用にどのように書かれているかについての情報を探し始めました。 さまざまなアプローチを説明する記事 を見つけました 。 しかし、私は非常にシンプルなものを探していました。 私はハブでズマのようなゲームのボットについての記事の1つでウェーブメソッドの言及を見つけました。 彼はリーのアルゴリズムです。 それは非常にシンプルで適切なように思えました。 これは、セルがフリーまたはビジーのいずれかであるフィールド内のあるポイントから別のポイントへの最短パスを見つけるためのアルゴリズムです。 ポイントは簡単です。 宛先から開始し、値1を割り当て、すべての隣接する空きセルにもう1つの番号を付けます。 次に、すべての隣接する無料のマークされたものを取り、再びもう1つマークします。 したがって、目的地に到達するまでフィールド全体に展開します。 そして、隣接するものから検索してパスを構築し、1になるまで数を減らします。グラフ内の最短パスを見つけるためのアルゴリズムを調べましたが、これが最も適しているように思われました。

コピーペーストアルゴリズムをページからwikiに移動し、 BotSocket.prototype.algorithmLeeという名前を付けました 。 フィールドについては、最初に戦場オブジェクトを作成しました。このオブジェクトでは、更新のたびに、占有ポイントに座標をマークしました。 アルゴリズムでは、リーはこのフィールドを同じものに減らしましたが、ステップは1です。

どういうわけか宛先を決定する必要がありました。 特定の間隔でランダムに選択することにしました。 フィールド上のランダムな自由点を見つける方法を作成しました。
 BotSocket.prototype.getDesiredPoint = function() { var point = []; var H = Object.keys(this.battleground[0]).length - 1; var W = Object.keys(this.battleground).length - 1; var x, y, i, j; var found = false; var iter = 0; do { i = this.getRandomInt(1, W); j = this.getRandomInt(1, H); x = i * this.moveStepSize; y = j * this.moveStepSize; if (this.battleground[x][y] === this.BG_EMPTY) { found = true; } iter++; } while (!found && iter < 100); point = [x, y]; return point; }; 


今、私は更新を書き直すことができました:
 BotSocket.prototype.update = function() { if (!this.desiredPoint || this.movements % this.updDestinationInterval === 0) { this.desiredPoint = this.getDesiredPoint(); } if (!this.desiredPoint) { return; } var currentPoint = [this.myBike.x, this.myBike.y]; var path = this.algorithmLee(currentPoint, this.desiredPoint); if (path && typeof path[1] !== 'undefined') { this.moveToPoint(path[1]); } else { this.desiredPoint = this.getDesiredPoint(); } }; 

ここでmoveToPointメソッドについて説明します 。これは、必要に応じて回転し、現在の方向を考慮して最短パスから最初のポイントに到達します。

後で、ボットをより攻撃的にすることにし、ランダムな望ましいポイントの代わりに、敵の前のポイントを探して、彼らのパスをブロックしました。 または彼らが彼ら自身とそれほど長く遊ばないように。
画像

クライアント側のボット


ボットをクライアント部分に転送しようとすることにしました。 プロジェクトはnode.jsにあるため、ボットとクライアント側で記述されたコードを使用できます。 これを行うために、個別のクライアントファイルでBotSocketを拡張しました。このファイルは、ゲームオブジェクトを参照せずにサーバーと正しく対話するために、 emit()およびcontrol()メソッドを再定義しました。
ローカルでは、すべてが完全に機能し、リモートサーバーへの展開後、いくつかの奇妙な状況がありました。
画像

長く考えて、私はそれが遅れであることに気づきました。 ボットは回転コマンドを送信しましたが、サーバー上の位置を更新した後に到着したため、目的のポイントへの直接パスを取得できなかったことがよくありました。 しかし、クライアント側には通常のボットが必要でした。 そのため、遅延を考慮することにしました。 これを行うために、BotSocket 拡張機能を再度作成しました。 この記事は長いので、主な解決策を説明します。 Leeアルゴリズムを呼び出す前に、現在のポイントの代わりに、現在の位置と方向、および遅延係数を考慮して予測位置を置き換えました。 遅延乗数は、遅延がサーバー上の位置を更新する頻度を超える回数です。 まだmoveToPoint()メソッドで将来のポイントの予測が必要でした。

単独でプレイした場合、予測は機能しました。 しかし、他の参加者がいた場合、ボットはこれを考慮せず、しばらくして別のプレイヤーがすでに通過した場所に送信しました。 この問題を解決するために、使用中のフィールドのセルをマークする方法を変更しました。 私は、オートバイの特定の可動域で彼らを忙しくし始めました。 半径は遅延係数に依存します。
以前は、ボットにデバッグ機能を提供しました。デバッグ機能は、フィールド上で目的のポイントと占有ポイントを描画しました。 クライアントボットの私のバージョンは、遅延を考慮して、次のように移動します。
画像
私の小さな赤いもの、残りはサーバー側です。

最も重要なことは、自分でボットを作ろうとすることです


この記事の主な目的は、ボットを書くことに興味をそそることです。 私はあなたの怠defを打ち負かすために多くのことをしました。 これを行うために、ボットで独自のスクリプトをロードする機能を追加しました。これにより、基本クライアントクラスが拡張されます。 プロジェクトに移動して、「自分のボットがある部屋のオプションを表示する」というテキストをクリックしてから、「自分のボットをテストする部屋を作成する」ボタンをクリックします。 ボットを簡単に使用できる場所が作成されます。デフォルトでは、ボットは遅延を考慮せずにボットになります。 今こそあなたのコードの番です。
アクションでコードを使用するための2つの簡単なオプションは、次のいずれかを使用します。

  1. ブラウザで使用できるサーバーにjsファイルをアップロードします。 ゲームの「AIスクリプトをロード」ボタンの横にあるスクリプトにURLを貼り付けます。 このボタンをクリックすると、新しいbotSocketオブジェクトが作成および入力され start()メソッドが呼び出されます
  2. ブラウザコンソールを使用します(Firebug-F12、Firefox-Ctrl + Shift + K、Chrome-Ctrl + Shift + J、その他- ここ )。


コードの入力方法を決定した場合は、 BotSocketクラスのメソッドをオーバーライドしてみてください。 手始めに、最も簡単なもの:
 BotSocket.prototype.update = function() { var r = Math.random(); if (r > 0.95) { this.control({'button': 'right'}); } else if (r > 0.90) { this.control({'button': 'left'}); } } 


その後、次のように入力してbotSocketオブジェクトを再作成します
 botSocket = null; 

この場合、ページ自体のコードはオブジェクトを再作成して埋めます。 これにより、ボットのデフォルトの動作がランダムに変更されます。 そして、それはあなたの想像力か深い知識次第です。
また、ボットのURLにhttps://raw.github.com/rnixik/tronode-js/master/public/javascripts/MyBotSocketClient.jsを挿入することにより、遅延を考慮してボットの改善されたスクリプトを接続することもできます。

おわりに


サーバー上で自分のAIを作成した方法と、それをクライアントに転送する方法と、高いpingを考慮してプレイする方法を彼に教えようとした方法を説明しました。 私はあなたに興味を持てたことを本当に願っています。あなたが以前にやったことがないなら、あなたはあなたのAIを書き込もうとしました。 もちろん、ハイエンドゲームでは完全に異なるアプローチが使用されますが、小さなアプローチから始める価値があります。

Githubのソースコード: github.com/rnixik/tronode-js

node.jsが手元にない場合は、私がデプロイしたアプリケーションを使用できます。

1) tronode.livelevel.net -DigitalOceanで最も安いVPS、
2) tronode-js.herokuapp.comはHerokuの無料の仮想ユニットです。

最初の1つ、おそらく最初の1つは負荷に対処できず、一部のコンピューターの2つ目はxhrポーリングでsocket.io-transportをダンプします。これにより、ゲームが非常に遅れます。
ゲームロジックのプログラミング方法について詳しく知りたい場合は、 こちらで読むことができます 。 node.jsの開発についても、グラフィカル部分についても少し説明があります。

ハブにアカウントを持っていない場合は、dev @ 1i1.beで質問したり、興味深い提案を送ってください。

Source: https://habr.com/ru/post/J207648/


All Articles