フラッシュゲーム用のボットを書く



なんで?



私は長い間、レーザー視力矯正について考えていましたが、今、最終的に手順を決定しました。 市場を簡単に調べた後(私はサンクトペテルブルクに住んでいます)、市内の価格はどこでもほぼ同じであり、医療観光にも意味がありませんでした(モスクワでは、それほど安くはありませんでした)。 ただし、操作を大幅に節約できることが判明しました。 診療所の1つは、サービスの広範な割引システムを提供しています。



もちろん、退役軍人や年金受給者への割引は興味がありませんでした。 しかし、私は「当社のWebサイトでフラッシュゲームをプレイし、獲得したポイントを割引に変換する」という珍しいプロモーションを利用することにしました。 プロセスの説明に取り組む。



一般的に、そのアイデアは最初はその不条理に驚いた-コンピューターゲームは視力に有害であると考えられているように見えます。 ゲーム自体も、その粘り強さで打たれたと言わざるを得ません-作者が暴力なしでゲームを作りたかったことは明らかです。そのため、伝説は次のように述べています。 さらに、アニメーションによって判断すると、近視の治療は患者を瞬時に蒸発させることによって行われます。



まあ、これは歌詞です。 実際、私はすぐに割引を取得しようとしましたが、ゲーム体験のすべてが初めて17%を獲得するのに役立ちませんでした。 何度かプレイしても、必要な17,000ポイントを獲得しましたが、20,000でさえ達成できないバーであり、25,000を切望していることは言うまでもありません。 この場合、ほくろの「治癒」には100〜200ポイントが与えられるため、それらを見逃すことはできません。 これが人間の力の範囲内かどうかはわかりません。



決定はすぐに思い浮かびました-私のためにゲームをプレイするボットを書く必要があります! ローリングによりC#でボットを記述するプロセス。



どうやって?



コンセプト


すでに多くの記事が書かれている戦闘について、2つの部隊が私で戦い始めました。 一方では、美しく、適切に設計されたアプリケーションを書きたいと思っていました。 一方、「コードは機能するはずで、他に何もするべきではない」という考えが頭の中を駆け巡りました。 一般に、実験のために、2番目の概念を完全に遵守することにしました。 さあ、行きましょう。





まず、OpenCVを使用して、画面からフレームをキャプチャし、オブジェクトを認識しましょう...停止します。 スーパーアプリケーションは必要ありません。この割引が必要です。 OpenCVに煩わされるべきですか? ストリームを開始する方が簡単かもしれません。無限ループでスクリーンショットを撮って表示しますか? たとえば、次のように:



Bitmap bmpScreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); Graphics g = Graphics.FromImage(bmpScreen); while (true) { g.CopyFromScreen(Screen.AllScreens[0].Bounds.X, Screen.AllScreens[0].Bounds.Y, 0, 0, bmpScreen.Size, CopyPixelOperation.SourceCopy); }
      
      







そして、ほくろを「治療」する方法は? 明らかに、起動時にブラウザウィンドウを見つけて、必要なパラメータを含むWM_CLICKメッセージを送信する必要があります。 ただし、すべてをよりシンプルにすることができます-カーソルを画面上の目的の場所に物理的に移動し、キーストロークをエミュレートします。



対応するWinAPI関数をインポートする



 [DllImport("user32.dll")] static extern bool GetCursorPos(ref Point lpPoint); [DllImport("user32.dll")] static extern bool SetCursorPos(int X, int Y); [DllImport("user32.dll")] static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, IntPtr dwExtraInfo);
      
      







クリックする関数を書きます



  static public void MouseClick() { Point ptCoords = new Point(); GetCursorPos(ref ptCoords); uint x = (uint)ptCoords.X; uint y = (uint)ptCoords.Y; System.IntPtr ptr = new IntPtr(); mouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, ptr); mouse_event(MOUSEEVENTF_LEFTUP, x, y, 0, ptr); }
      
      







すべてのユーティリティ関数が揃ったので、ロジックを記述する必要があります。





最初のラウンドを手動で数回通過した後、いくつかの観察を行いました。

  1. ほくろは同じ場所に表示されます。
  2. 追加のターゲット(ロボットとUFO)はほくろを追い払うので、そもそも「治療」する必要があります。 さらに、厳密に定義された場所に表示されます。
  3. ミサイルの場合、「銅」には500ポイントが与えられ、1か所でフリーズします




一般的に、目標は同じ場所に表示されるため、最初に頭に浮かぶのは、画面の参照スクリーンショットを作成し、特定のピクセルの色が変更されたかどうかを確認することです。



ターゲット座標を保存する方法は? 一般に、それはそれほど単純ではありません。 画面の解像度、ページのスクロールレベルなどを考慮する必要があります。 つまり ゲームウィンドウのサイズを変更できるだけでなく、画面上のさまざまな場所に配置することもできます。 幸いなことに、私は普遍的な進歩のゲームを書いておらず、25,000ポイントを獲得する必要がありました。 そこで、フルスクリーンブラウザーでゲームを開き、ページの一番上までスクロールして、画面上のピクセルの物理座標としてターゲットの座標を記録することにしました。



そして、座標を記録する方法は? ユーザーにとって最も便利な方法は、たとえばファイルにカーソル座標が保存されている場所をクリックしてホットキーを作成することです。 次に、ほくろが表示されたら、その上にカーソルを合わせてホットキーを押す必要があります。 率直に言って、最初はそうしました。 その後、ほくろを使ってスクリーンショットを作成し、グラフィカルエディタで各ほくろの位置を測定する方がはるかに簡単であり、これらの座標は単純にハードコーディングされていることがわかりました。 このようなことが判明しました



 List<Point> m_lpTargets = new List<Point>(); m_lpTargets.Add(new Point(557, 623)); //  m_lpTargets.Add(new Point(261, 654)); //  m_lpTargets.Add(new Point(352, 486)); //   m_lpTargets.Add(new Point(450, 500)); // m_lpTargets.Add(new Point(592, 698)); //  m_lpTargets.Add(new Point(756, 631)); //  m_lpTargets.Add(new Point(373, 514)); // m_lpTargets.Add(new Point(481, 440)); //  
      
      







目的のボタンをフォームに追加します。これにより、参照スクリーンショットが取得されます



  private void btnAim_Click(object sender, EventArgs e) { m_bmpReference = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); Graphics g = Graphics.FromImage(m_bmpReference); g.CopyFromScreen(Screen.AllScreens[0].Bounds.X, Screen.AllScreens[0].Bounds.Y, 0, 0, m_bmpReference.Size, CopyPixelOperation.SourceCopy); }
      
      







ボットのコードをメインループに追加して実行します!



  foreach (Point pt in m_lptTargets) { if (m_bmpReference.GetPixel(pt.X, pt.Y) != bmpScreen.GetPixel(pt.X, pt.Y)) { MouseMoveTo(pt, 10, 200); MouseClick(); } }
      
      





どうしたの?



最初の結果


まあ、それはまったく機能しないということではありません...最初に、急いで、いつものように、いくつかのプログラムがユーザー入力をインターセプトするとき(特にそのような野barな方法で)、それを閉じることが問題になることを忘れました。 ボットを開始した後、マウスを競技場の外に移動することはできません。タスクマネージャーが起動した場合でも、常に指定したポイントにジャンプします。 私は再起動の助けを借りてこの状況から抜け出したことを認めます...実際、正直に言うと、これがロボットが世界を捕らえる方法だと思います。



メインスイッチ


おそらく、ゲームが終了したことを判断する何らかの賢明なアルゴリズムを作成する価値があります。また、たとえば、ゲームのあるウィンドウが最小化されたときにマウスを離します。 しかし、幸いなことに、要求に応じてボットを停止する方法を見つける必要があり、Windowsホットキーの知識がこれに役立ちます。 シューティングコードでストリームを停止するフォームサイズ変更ハンドラーを追加するだけです。



  private void Form1_Resize(object sender, EventArgs e) { if (WindowState == FormWindowState.Minimized) { m_objAimingThread.Abort(); } }
      
      







今、ボットを停止するには、Win + Dを押すだけです。



最初の問題


しかし、これは最も重要なことすらありません。 問題は、ボットが正常にモグラを標的にしているにもかかわらず、何かが間違っているということです。モグラにぶつかると、メガネがノックアウトされますが、モグラ自体は消えず、または白いシルエットが残ります。 この場合、ボットがこの時点で無期限に勝ち続けることは明らかです(色が変更されました)。



率直に言って、私はこの問題にかなり長い間苦労しました。 結果として、私はこのゲームがなんとなくモグラのレンダリングとアカウンティングを実装しているという結論に達しました。 つまり 穴から完全に出てくる前にほくろに着くと、ポイントが与えられますが、同時にそれはクロールアウトし続けます。 そして、ほくろが「癒された」とマークされると、再びそのポイントを得ることができなくなります。 ほくろの状態にはいくつかの変数が関与しており、一貫性が侵害されていることがわかります。 この問題が発生したのは、4つの穴からはみ出すほくろだけであることが重要です。フィールドを飛び越えて宇宙に飛び込むのに困難はありませんでした。



手動モードではこのような問題は発生しないため、一時停止するか、ターゲットへのマウスの動きをエミュレートするだけで済みます。



モグラへのポインタを250ミリ秒にわたって修正する修正プログラムを起動すると、消えないモグラの問題はなくなりました。 しかし、別のことが明らかになりました-画面の下部にある跳ねるほくろはあまりにも速く移動するため、250msで視界を変換する時間を持つことができません。 結局のところ、モグラが固定点に現れ、しばらくそこに座っているのは穴からであり、ジャンプするモグラは私たちの「照準点」を非常に素早くすり抜けます。



そして最後に、綿密に調べてみると、大気中に飛び込むモグラは実際には異なるX座標を持っていることがわかったので、その位置を単純にリストするのは面倒です。 概念を変更する必要があるようです...



新しいコンセプト


したがって、ほくろはさまざまな場所に表示されるため、座標で追跡することはできません。 パターン認識は本当に必要ですか? したくない この前にもう1つの「愚かな」実装を試みます。 すべてのほくろの共通点は何ですか? スーツとブーツは誰にも見えません。 しかし、すべてのほくろはヘルメットに頭を持っています。 さらに、ヘルメットは非常に珍しい色をしています...この青みがかった色の画像全体をスキャンするとどうなりますか? やってみましょう!



 Color clHelmet = Color.FromArgb(102, 142, 193); for (int j = 400; j < 880; j += 1) for (int i = 200; i < 850; i += 1) { if (bmpScreen.GetPixel(i, j) == clHelmet) { Point ptTarget = new Point(i, j); MouseMoveTo(ptTarget); MouseClick(); } } }
      
      







スクリーンショットでは、フラッシュドライブの作業領域の座標とヘルメットの色を見ました。 さて、私のCore i5では、超制動GetPixelメソッドを使用してすべてのピクセルをループし、色が参照と一致したときに撮影します。 このアプローチでは、1サイクルの実行時間は約200ミリ秒であり、有効な値のようです。 発射!



そして新しい問題




すべてがそれほど単純ではないことが判明しました-ヘルメットに基準色のポイントが複数あるため、ほくろが現れると、ボットは他の患者を無視して、かなりのペニーのように白い光に剥離し始めます。 解決策は非常に簡単に見つかりました-最後のショットの座標を保存し、現在のフレームを処理するとき、これらの座標の近くのピクセルを無視します。



このアプローチの2番目の問題は、ほくろが動くことです。そのため、処理中にほくろが光子の癒しの束から逃げることができます。 さらに、選択した色はヘルメットの端にあるため、少し遅れても患者を失います。



まず、フレームの処理時間を短縮します。 一般的に、このためには、メモリへの直接アクセスの領域に画像をコピーし、バイトを直接操作する必要があります...しかし、スキャンサイクルのステップを2だけ変更するだけで、画像のスキャン速度が4倍になります。 ステップの強い増加はもはや痛みがない-ピクセルの一部の領域はスキップされます。



ほくろを確実に治療するための2番目のステップは、基準色のポイントを検出するだけでなく、検出された領域をすぐにオンにすることです。 入力して実行するコードは次のとおりです。



  Point ptLastHit = new Point(0,0); for (int j = 400; j < 880; j += 2) for (int i = 200; i < 850; i += 2) { if (bmpScreen.GetPixel(i, j) == clHelmet) { Point ptTarget = new Point(i, j); if (Distance(ptLastHit,ptTarget) > 70) { ptLastHit = ptTarget; MouseMoveTo(ptTarget); MouseClick(); ptTarget.Offset(20, 20); MouseMoveTo(ptTarget); MouseClick(); ptTarget.Offset(-40, 0); MouseMoveTo(ptTarget); MouseClick(); } } }
      
      







患者ソート


これですべてが正常になりました-ほくろは正常に治癒しましたが、穴からのほくろの問題は復活しました。 覚えておいてください、それらを遅滞なく撃っても消えませんでしたか? ショットの遅延を250ミリ秒に戻しましたが、このような遅延があると、実行中のモグラに入ることができなくなります...解決策はすぐに見つかりました-コード内の穴の座標を忘れてしまい、ターゲットがこれらの領域に現れた場合にのみ、撮影の遅延を与えます。



ジャンプモグラでの射撃の効果を高めるには、最初に見つかったターゲットにヒットした後、フレームの処理を停止する価値があります。 説明しましょう-画面に2つのほくろがあり、一度に1つずつ撮影している間に、2番目のほくろはすでに少しシフトしますが、古いフレームにはまだ古いイメージがあります。 現時点では、すべてのCライクな言語のグローバルな問題の1つに直面しています。これは、breakコマンドで2つのネストされたループを中断できないことです。 そのような場合、goto演算子を使用してひどいカルマ犯罪を犯します。



最後に、特定の場所で厳密に追跡する必要がある空飛ぶ円盤、ロボット、ロケットについて忘れないでください。 その結果、サイクルは次のようになり始めました。



  g.CopyFromScreen(Screen.AllScreens[0].Bounds.X, Screen.AllScreens[0].Bounds.Y, 0, 0, bmpScreen.Size, CopyPixelOperation.SourceCopy); foreach (Point pt in m_lptTargets) { if (m_bmpReference.GetPixel(pt.X, pt.Y) != bmpScreen.GetPixel(pt.X, pt.Y)) { Point ptMouse = new Point(); GetCursorPos(ref ptMouse); if (ptMouse != pt) { MouseMoveTo(pt); MouseClick(); } } } // Rectangle hole1 = new Rectangle(539, 612, 60, 50); Rectangle hole2 = new Rectangle(577, 690, 60, 50); Rectangle hole3 = new Rectangle(738, 621, 60, 50); Rectangle hole4 = new Rectangle(243, 641, 60, 50); // Rectangle hole5 = new Rectangle(379, 415, 45, 40); int iDelay = 5; Color clHelmet = Color.FromArgb(102, 142, 193); DateTime tmNow = DateTime.Now; Point ptLastHit = new Point(0,0); for (int j = 400; j < 880; j += 2) for (int i = 200; i < 850; i += 2) { if (bmpScreen.GetPixel(i, j) == clHelmet) { Point ptTarget = new Point(i, j); if (hole1.Contains(ptTarget) || hole2.Contains(ptTarget) || hole3.Contains(ptTarget) || hole4.Contains(ptTarget) || hole5.Contains(ptTarget)) { iDelay = 200; } if (Distance(ptLastHit,ptTarget) > 70) { ptLastHit = ptTarget; Thread.Sleep(iDelay); MouseMoveTo(ptTarget); MouseClick(); ptTarget.Offset(20, 20); MouseMoveTo(ptTarget); MouseClick(); ptTarget.Offset(-40, 0); MouseMoveTo(ptTarget); MouseClick(); goto next; } } } next: }
      
      







結果は何ですか?



一般に、このプログラムはすでに21〜22,000ポイントを獲得しています。 25を得るには、「ラインナップ」の遅延やショットの座標など、いくつかの魔法の値を変更する必要がありました。 ある瞬間、星が無事に形成され、私は25,000の大切なマークを超えました。これには数十回の打ち上げが必要でした。



プロジェクトが作成されてからクーポンが割引で印刷されるまで、すべての作業を行うのに2晩かかりました。 アルゴリズムを事前に考えておけば、作業を減らすことができることを理解しています。 しかし、人のためのゲームになる製品を作成するというアイデアを思いついたとき、私は常に自分自身を引っ張っていたという事実のために、多くの時間を節約しました。



最初に、最小限の時間で特定の問題を解決しようとして、アーキテクチャと機能の点で悪いコードを書きました。 このプログラムは、私のモニター構成でのみ機能し、非常に多くのコンピューティングリソースを必要としますが、問題を解決します。これで十分です。 実際、この投稿はこのアプローチの有効性の実例として考えられました。 過去には、問題を解決することをするのではなく、「完璧主義」と完璧なコードを書きたいという願望のために、多くの機会を逃してしまいました。



ゲームの作成者が期待していたことは、私には完全には明らかではありません。手動でゲームを進めることは不可能だと思われるからです。 おそらく、この概念は、ボットを書くことができる赤い目だけが25%の割引を受ける価値があることを示唆しているだけです。



UPD。 コメントは、割引を得る方法も示しています。 残念なことに、私はそれらの最も単純なものすら考えていませんでした...する必要のないことの例としての投稿は難しいことがわかりました。 確かに、ボットは書けませんでした...これが、私だけでなく、50年前に特定され分類されたコンピューター病に苦しんでいる残りの不幸な人々へのレッスンとして役立つことを願っています。



このすべての活動を始めたフレンケル氏については、彼はコンピューター病に苦しみ始めました-コンピューターで働いていた誰もが今日それを知っています。 これは非常に深刻な病気であり、それを扱うことは不可能です。 コンピューターの問題は、コンピューターで遊ぶことです。 それらはとても美しく、多くの可能性があります-偶数であれば、それが奇数であればそれを行い、それを行います、そしてあなたが十分に賢ければ、すぐに単一のマシンでますます洗練されたことができます。



しばらくして、システム全体が崩壊しました。 フレンケルは彼女に何の注意も払わず、彼は他の誰も指揮しなかった。 システムは非常にゆっくりと動作し、そのとき、彼は部屋に座って、タブの1つでアークタンジェントxを自動的に印刷する方法を考えました。 次に、タブレータがオンになり、列が印刷され、bam、bam、bam-統合によってアークタンジェントが自動的に計算され、1回の操作でテーブル全体がコンパイルされました。



まったく役に立たない活動。 結局、すでにアークタンジェントのテーブルがありました。 しかし、コンピューターを使用したことがある場合は、それがどのような病気であるかを理解できます。どれだけのことができるかを見ることができるという賞賛です。 フレンケルは初めてこの病気を拾いました、貧乏人。 このすべてを発明した貧しい人。



All Articles