こんにちは、ゆたかみわーくです。

今回の記事ではまたしてもグーグルのブロックリー・ゲームを取り上げたいと思います。

ブロックリー・ゲームは過去に2回、別々の問題を解く記事を書かせていただきました。

この記事ではブロックリー・ゲームの最後の問題になる「ポンド」を取り上げます。

ポンドは、その前の問題である「ポンド・チューター」の応用問題で、特徴的な動きをする3つの敵の攻撃から逃れつつ、敵を撃破することがゴールになります。

ヒントはありません。

プログラムのためのブロックの構築か、Javascriptを直接書いて自機の動きをプログラミングして敵を倒します。

とにかく敵をすべて倒せばクリアです。

答えは数通りになると思います。

今回は、そんなポンドを、Javascriptでプログラミングすることで、できるだけ勝率の高いプログラムを作るようにしていきたいと思います。

【勝率を上げてみる】グーグルのブロックリー・ゲームのポンドを攻略

まずは次の動画をご覧ください。

今回用意したスクリプトで10回勝負してみたところ、接触から抜け出せずダメージが重なった2回を除いて8回勝利することができました。

勝率8割、結構高い率だと思います。

多くのポンドのベストアンサーとされているスクリプトは次のような動きを繰り返しています。

  •  ランダムに周囲をスキャン
  •  敵機を見つけたらそちらに直進しつつ攻撃を放ち続ける

具体的には次のようなコードを書きます。

var angle = Math.random() * 360;
while (true) {
  while(scan(angle) == "Infinity"){
    angle = Math.random() * 360;
  }
  swim(angle);
  cannon(angle,scan(angle));
}

実際のところ、これでもかなり自機を強くすることができます。

これに対して今回用意したコードは「逃げる」という動作を追加してみました。

それでは具体的にはみていきましょう。

逃げるコードを追加する

まず、具体的にどのような逃げる行動を加えたかは次の通りです。

  1. Scanした敵機との距離が40より下の場合(近い場合)逃げる
  2. ダメージを食らったら逃げる
  3. 壁が近くなったらぶつからないように逃げる

具体的には今回用意した次のスクリプトのコメントをご覧ください。

var enemyAngle; //敵機の方角を格納する変数
var pHealth = health(); //自機の以前のヒットポイントを格納する変数
var x = getX(); //x軸位置初期値
var y = getY(); //y軸位置初期値
var min = 5; //x,y軸位置の最低値
var max = 95; //x,y軸位置の最高値
function scan10(x){ //10度の範囲内で敵機をスキャン
  var ret = 10;
  var result = scan(x,ret);
  if(result != "Infinity"){
    cannon(x,result); //Infinity(敵機が見つからない)でなければその方向に球を打つ
    if(result >= 40) {
      swim(x,50); //距離が40以上離れている場合、速度50で敵機の方角に前進
    }
  }
  if(result < 40) {
      ret = -1;  //40より下の場合 スキャンをやめて【逃げる】フラグ「-1」を戻り値にセット
  } else {
    ret+= x; //10にスキャン角度を足した値を戻り値にセット
  }
  return ret;
}

//360度をスキャンする処理
function scanAll(){
  var i = 0;
  while (i < 360 && i >= 0){
    x = getX();
    y = getY();
    if(!damage()){
      i = scan10(i); //ダメージを喰らわない限りスキャンを続け、iは+10され続ける
    } else {
      i = -2; //ダメージを喰らったら【逃げる】ためにiが-2になりwhile文から抜ける
      pHealth = health(); //ヒットポイントが更新される。
    }
    if(x<min||x>max||y<min||y>max){
      swimTo(-1); //壁際に近い位置にいたら、【逃げる】ために遠のくように動く
    }
  }
  return i;
}

//ダメージの判定。
function damage() {
  var ret = false;
  //現在のヒットポイントがpHealthに格納する以前のヒットポイントを下回った
  if(health() < pHealth){
    ret = true;
  }
  return ret;
} 

//壁にぶつからない角度を求めて移動する
function swimTo(enemyAngle){
  var swAngle;
  var flagX = false;
  var flagY = false;
  while(!(flagX && flagY)) {
    swAngle = Math.random() * 360;
    if(x < min && (swAngle > 270 || swAngle < 90)) {
      flagX = true;
    }
    if(x > max && (swAngle < 270 && swAngle > 90)) {
      flagX = true;
    }
    if(y < min && (swAngle > 0 && swAngle < 180)) {
      flagY = true;
    }
    if(y > max && (swAngle < 360 && swAngle > 180)) {
      flagY = true;
    }
    if(x > min && x < max){
      flagX = true;
    }
    if(y > min && y < max){
      flagY = true;
    }
    if ((enemyAngle-10 < swAngle && swAngle < enemyAngle + 10) && enemyAngle !== -1) {
      flagX = false;
      flagY = false;
    }
  }
  swim(swAngle,50);
}

//実行
while(true){
  enemyAngle = scanAll();
  if(enemyAngle == -1){
    swimTo(enemyAngle);
  } else if(enemyAngle == -2 && Math.random() >=0.3) { //ランダム値を用いて格率でダメージを喰らっても逃げない可能性を作る
    swimTo(-1);
  }
  
  if (damage() && Math.random() >=0.5) {
    stop();
  }
}

これにより、距離を見極め、まるで撃っては逃げてのヒットエンドランができるようになりました。

1のケースの場合は、スキャンした方向とは違う方向に動いて逃げるようにはしているのですが・・・

接触ダメージからの抜け出しが課題

スキャンしていない方向から追突された際の抜け出しがこのスクリプトの課題です。

先の動画でもやはりぶつかってしまって抜け出せず体力を失うケースが多くありました。

ダメージを食らうためランダムな方向に移動しようとしますが、スキャンしていないため、必ず敵機以外の方向を向くことができるわけではないうえ、敵機も前進方向するため、回避が前進に阻まれてなかなか抜け出せない・・・そういう状態になってしまうとどうしてもダメージが蓄積されます。

これを何とかできるスクリプトが用意できていないことが課題となりました。

もしかしたら、逃げるのではなくスキャンを続けて最も近い位置にいる敵を倒すようにすることが一番の解決策かもしれないですね。

そんなことを考えると、ポンドはまだまだ改良を進めることができます。

最後に

いかがでしたでしょうか?

スクリプトで工夫できる限り何をやってもよいポンドなので、作るのも難しい反面、非常に追求しがいがありますね。

先にあげたような課題もありますし、本記事のスクリプトにもまだまだ改良の余地があります。

本記事で用意したスクリプトはご自由に利用していただいて構いません。

より良いものができたら教えていただけると嬉しいです。

この記事は以上になります。

最後まで読んでいただきありがとうございました。