Webアニメーションを実行したり、モバイルプラットフォーム用のアプリケーションを作成したりしたくなかったため、お気に入りのシステム管理者ツールであるtelnetをクライアントとして使用することにしました。
起こったことは次のとおりです。
技術の選択
Telnet
telnetを使用するアプリケーションについて長い間聞いたことがあります。 たとえば、スターウォーズからの小さな抜粋:
telnet towel.blinkenlights.nl
しかし、なぜtelnetなのですか? 結局のところ、netcatは他のものよりもはるかにクールでモダンです。 誰もが知っているわけではありませんが、telnetはTCP接続を確立するためのユーティリティだけではありません。 これは、たとえばEnterキーを押さずに入力されたデータをサーバーに送信することを可能にするプロトコル全体です(ANS IIの改行)。
マシンを制御するために矢印を使用し、毎回Enterキーを押して文字シーケンスをサーバーに送信したくないため、telnetはこの問題を解決するのに最適です。
または、次を使用できます
stty -icanon && nc <host> <port>
しかし、これは率直に言って松葉杖です。
行く
なぜ行くの? スキルを強化したいという事実に加えて、GoにはGoroutinesがあり、マルチスレッドアプリケーションの作成に最適です。 そして、多くの人に並行してプレイしてほしいので、Goを使用しない理由はありませんでした。
私もこの記事を少し前に読んで、アプリケーションでゴルーチンを視覚化することに興味がありました。
ゲームデザイン
ゲームプレイ
ゲームは、ラウンドで最大5人までのマルチプレイヤーです。 プレイヤーの数が少ない場合、残りのスロットはボットで表されます。
ライバルを破壊するには、プレイして生き残る必要があります。 プレイヤーはボーナスを収集したり、時間の経過とともに蓄積する爆弾を散布することができます。
ダメージシステム
興味深い論争の的となっている点の1つは、損傷システムです。 私たちは、速度の違いと衝突した機械の部分を考慮することにしました。
速度は異なる場合があります。 衝突すると、1にリセットされます。事故のない運転中、しばらくすると5に増加します。
次に、あるプレイヤーが別のプレイヤーに追いつき、車の後ろに衝突する状況を考えてみましょう。 プレイヤーのダメージを計算する公式は次のとおりです。
if player.Car.Borders.intersects(&opponent.Car.Borders) { switch player.Car.Borders.nextTo(&opponent.Car.Borders, 0) { case LEFT: // switch player.Car.Direction { ... case RIGHT: // player.Health -= DAMAGE_BACK * (maxSpeed - player.Car.Speed) ... case RIGHT: // switch player.Car.Direction { case RIGHT: // player.Health -= DAMAGE_FRONT * player.Car.Speed ... ... }
つまり、プレーヤーAが4の速度で動いており、プレーヤーBが5の速度で追いついた場合、プレーヤーAは2 *(5-4)= 2を失います。 ただし、攻撃しているプレイヤーは4 * 5 = 20を失います。
最も深刻な損傷は、側面衝突DAMAGE_SIDE = 6で 、24に達する可能性があります。
開発
ゲームがトレーニング演習として作成されたことをもう一度思い出します。 改善のための提案があれば、喜んで考慮します。
ソースコードはgithubにありますが、要点を見ていきましょう。
Telnet
すでに述べたように、telnetプロトコルのサポートが必要です。 これを行うには、アプリケーション側で、バイトシーケンスを使用してクライアントに「挨拶」する必要があります。
telnetOptions := []byte{ 255, 253, 34, // IAC DO LINEMODE 255, 250, 34, 1, 0, 255, 240, // IAC SB LINEMODE MODE 0 IAC SE 255, 251, 1, // IAC WILL ECHO } _, err := conn.Write(telnetOptions)
答えを数えます(250から始まります)。
その後、telnetクライアントは必要なプロトコルサポートで動作します。 詳細については、 ソースコードを参照してください。
RFC854から取得したデータ。
ラウンド
新しいプレイヤーがゲームに参加するとき、ラウンドに追加する必要があります。 基準は非常に異なる場合があります-私たちにとって-ちょうど空き領域の可用性。 ここのラウンドはプレイヤーの配列です。 構造全体は次のようになります。
type Round struct { Players []Player FrameBuffer Symbols ... }
十分なプレーヤーがいない場合は、ボットを追加します。
チャンネル
Goのチャンネルは、Gorountine間で同期する非常に便利な手段です。 これらを使用してラウンドを準備し、プレーヤーを配布します。
func (p *Player) checkBestRoundForPlayer(compileRoundChannel chan Round) { foundRoundForUser := false for i := 0; i < len(compileRoundChannel); i++ { select { case r := <-compileRoundChannel: // if len(r.Players) < maxPlayersPerRound && !p.searchDuplicateName(&r) { ... r.Players = append(r.Players, *p) compileRoundChannel <- r foundRoundForUser = true break } else { compileRoundChannel <- r } default: } } if !foundRoundForUser { // ... r := Round{...} r.Players = append(r.Players, *p) compileRoundChannel <- r } }
この関数は別個のゴルーチンで実行されるという事実にもかかわらず、Mutexの使用は必須ではないことに注意してください。 Goのチャンネルでは、オブジェクトを複数回読み取ることができないため、この場合、1つのオブジェクトの変更は除外されます。
フレームバッファ
すべてのプレイヤーは、例外なく、ゲームで何が起こっているかについての完全な情報を受け取る必要があります。 みんなに同じ画像を送ってみましょう。 FrameBufferタイプは元々[]バイトでしたが、ある時点で絵文字を追加してゲームをよりカラフルにすることにしました。 そして、そのような文字はそれぞれ1バイト以上を占めるため、実際にはSymbolsはバイト配列の配列です。
type Symbol struct { Color int Char []byte } type Symbols []Symbol
したがって、FrameBufferコンテンツ生成アルゴリズムの例を次に示します。
- 事前に準備した地図をコピーする
- プレーヤーデータを追加(健康、爆弾の数...)
- ボーナスと爆弾をマップに追加します(ある場合)
- 車を追加する
ボット
もちろん、ここには人工知能はありません。 しかし、ボットはいくつかの基本的なアクションで訓練されています:
- チェイスプレイヤー(ボットではありません)
- 近くにボーナスがある場合-集める
- 途中で障害物がある場合(他のプレイヤー)-回ります
一般的に、ボットはかなり面倒なので、プレイするのがより面白くなります。
可視化
gotraceが必要です 。これは実際、すべての作業を行います。 サンプルと、アプリケーション内のルーチンをトレースするためにDockerを実行する方法の詳細な説明が満載です。 何が起こったかは短いビデオで見ることができます:
ここでは、いくつかのチャネルと、ゲームロジックと、それらが情報を交換するために使用するチャネル間のチャネルを管理するGoroutineの束があります。
Goの主な信条の1つは、ここで完全に尊重されています。
メモリを共有して通信するのではなく、通信してメモリを共有します。
遊び方
残念ながら、Windowsでは、ターミナルの絵文字に問題があります。 MacOSとLinuxを使用しているため、理解するのに十分なモチベーションがありませんでした。 しかし、誰かが互換モードまたは何か他のものを知っていれば-私は助言することをうれしく思います。
他の皆のために:
telnet protury.info 4242
まとめ
- Goroutineは21世紀のすばらしいツールです。 関数の並列実行の実装は、開発者にとってほとんどオーバーヘッドを負担しません
- Visualization Goroutineは美しい抽象化です。 残念ながら、アプリケーションではうまく機能せず、「Hello、World!」よりも少し複雑で、コンテナ内のGo 1.5の古いバージョンに関連付けられています
- Telnetなどの古代のプロトコルでさえ、2017年の思慮深さに驚かされることがあります。
- Goは、Webアプリケーションだけでなく素晴らしいものです。 バックエンドサービスの作成にはさらに優れています。
- ターミナルのテキストモードでも、カラフルなオブジェクトを表示できます-拡張ASCIIまたは絵文字の文字を使用するだけです
ボーナス
実際、これは私が書いた2番目のゲームです。 最初はシングルユーザーでしたが、非常に興味深いものでもありました。
プレイ:
telnet protury.info 4243
ありがとう
InnoDayを提供するInnoGamesに感謝します。InnoDayは、そのようなことができる週に1日中、あらゆる方法で私を助けてくれた同僚、Pavel UsovとKajetan Staszkiewiczに感謝します。 そして、最後まで読んでくれたみんなにも!