電子辞書Brainをいじる (13) Brainuxでゲームを改造して遊ぶ (1) Balder 2D


◎ Balder 2D 無重力下の二次元シューティングゲーム
(改造版ならTrixieでも実行は可能)

スクリーンショット

ゲーム詳細

https://sourceforge.net/projects/balder

起動:cd balder2d/balder2d-1.0/bin; ./balder2d

●無重力下の二次元シューティングゲーム
小さなプローブを操り、他のプレイヤーを破壊します。
自分は紫のプローブです。
プローブの進む方向や速度は、壁や他のプローブにぶつからない限り変化しません。
弾丸を撃った時、逆方向の小さな反動があります。
【操作】[←]:左回転 [→]:右回転 [↑]:撃つ [↓]:壁にくっつく/離れる

オリジナルではコンピュータ(Python製AI)との対戦が可能です。
改造版では、C++に切り替えてありますので、Trixieでも実行は可能になります。

Brain上での実行では、動作はしますが、
800×600の画面を想定されているようで、全フィールドが表示されません。
ウィンドウを移動して、上側か下側が表示されない状態であれば、プレイできます。

AIとの対戦プレイも楽しめます。

このため、ゲームを改造して遊べるようにしてみました。
そのほか細かい部分も変更できますので、ソースを解析してみるのも面白いです。

Optionsで、Player数を指定できます(AIも含めた人数です)
他に以下の項目が変更できます。
・Game Setup  マップの選択、制限時間、残数等が設定できます。
         (マップはDefaultとかが遊びやすいかもしれません。)
・Player Setup  Human(キーバインド)かAI(3種類から選択)等プレイヤー設定が出来ます。
         (キーバインド設定は下のほうが表示されないので、
         (改造前に設定するか、)
         (config.xmlファイルを直接いじりましょう。)
・Sound & Video Fullscreen/Sound/Musicの設定ができます.
         (Fullscreenは現状エラーになります。)
         (Musicは速度に影響しますので、無効推奨です。)

守りに徹するか、リスクを覚悟で特攻するか
ゲームスタイルが分かれます。

 

インストール:改造後にビルドしたものを
zipで固めましたので、Brainuxに展開してみてください
こちらはTrixie上でも実行できるはずです。

https://nsb.homeip.net/Brain/Upload/balder2d/balder2d-bin.zip

展開後、
$ cd bin
$ ./balder2d
で起動できるはずです。
(TrixieではSDL1.2の自ビルドは必要です。)

 

balder2d改造版のソースを含んだものは以下になります。

https://nsb.homeip.net/Brain/Upload/balder2d/balder2d-kai.zip

 

インストール:ソースからインストール  + 改造

debian11ならビルド可能です。

debian13ではpython2が削除されているため、ビルド不可になります。
(AIがpython2製です)
AIをC++に移植した改造版であれば、
debian13でもビルドしたものの動作はしました。

インストール手順詳細(クリックで表示)

cd
mkdir balder2d
cd balder2d

ソースをダウンロードして、フォルダに保存
https://sourceforge.net/projects/balder/files/Balder%202D/balder2d1.0/balder2d-1.0.tar.gz/download

tar zxvf balder2d-1.0.tar.gz
cd balder2d-1.0

sudo apt install scons \
libsdl1.2-dev libsdl-image1.2-dev libsdl-mixer1.2-dev \
libsdl-ttf2.0-dev libboost-all-dev python2-dev

sudo apt install libguichan-dev

sudo apt install scons
scons

 

#エラーの対策

vi SConstruct

-includepath = ['/usr/include/python2.4','include']
+includepath = ['/usr/include/python2.7','include']


-libs = Split('guichan_sdl guichan SDL_image SDL_mixer SDL_gfx python2.4 physfs')
+libs = Split('guichan_sdl guichan SDL SDL_image SDL_mixer SDL_gfx python2.7 physfs')

vi bin/scripts/test/

-env = Environment(CPPPATH='/usr/include/python2.4')
+env = Environment(CPPPATH='/usr/include/python2.7')

-env.Program('runtests', 'runtests.cpp', LIBS='python2.4')
+env.Program('runtests', 'runtests.cpp', LIBS='python2.7')

scons -c
scons

cd balder2d/balder2d-1.0/bin/; ./balder2d

 

このままだと、プレイ画面の解像度が大きすぎてゲーム画面が全部表示されないので、
表示範囲を調整してみます。ついでに、SDLのモードを16bit化
(本来はマップサイズから画面サイズを掲載しているのですが、
 強制的に画面サイズを変えているので、不具合が出るかもしれません。)

vi src/main.cpp

-render->SetScreen(800,600);
+render->SetScreen(850,480);

vi src/renderer.cpp

-SetScreen(800, 600);
+SetScreen(850, 480);

vi src/gamemanager.cpp

 mapwidth = statbox_x = mp.GetMapWidth();
+if (mapwidth > 850) mapwidth = statbox_x = 850;
 mapheight = statbox_y = mp.GetMapHeight();
+if (mapheight+STATHEIGHT > 480){ 
+    mapheight = 480-STATHEIGHT-20;
+    statbox_y = 480-STATHEIGHT;
+}
-render->SetScreen(mp.GetMapWidth(), mp.GetMapHeight()+STATHEIGHT,
+render->SetScreen(tatbox_x, statbox_y+STATHEIGHT,

vi src/collisionmask.cpp

 #include "imageloader.h"
+#include "gamemanager.h"


        if (!field) throw "invalid surface used to create collision mask";
        width = field->w;
        height = field->h;
+       if (width > 850) width = 850;
+       if (height+STATHEIGHT > 480) height= 480-STATHEIGHT-0;

vi src/menu/options.cpp   # (Backボタンの場所を変更)

-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (150 + backButton->getHeight()));

vi src/menu/gamesetup.cpp # (Backボタンの場所を変更(場所がなくて不格好))

-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+0), page->getHeight() - (150 + backButton->getHeight()));

vi src/menu/playersetup.cpp  # (Backボタンの場所を変更)

-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (150 + backButton->getHeight()));

vi src/menu/soundvideo.cpp  # (Backボタンの場所を変更)

-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (150 + backButton->getHeight()));

vi src/gamemanager.cpp #リスポーン時のバグ修正()

     srand(frame);  //  ・・・・

-           int x = rand()%collision_mask->GetWidth();
+           int x = 50 + rand()%(collision_mask->GetWidth()-100);
-           int y = rand()%collision_mask->GetHeight();
+           int y = 50 + rand()%(collision_mask->GetHeight()-100);
            int radius = p->GetWidth();
            // test and make sure this doesn't burry the probe too deeply in a wall
            if (collision_mask->TestPixel(x, y + radius)){collision=true;continue;}
+           if (collision_mask->TestPixel(x + radius, y + radius)){collision=true;continue;}
+           if (collision_mask->TestPixel(x - radius, y + radius)){collision=true;continue;}
-           if (collision_mask->TestPixel(x, x - radius)){collision=true;continue;}
+           if (collision_mask->TestPixel(x, y - radius)){collision=true;continue;}
+           if (collision_mask->TestPixel(x + radius, y - radius)){collision=true;continue;}
+           if (collision_mask->TestPixel(x - radius, y - radius)){collision=true;continue;}
            if (collision_mask->TestPixel(x + radius, y)){collision=true;continue;}
            if (collision_mask->TestPixel(x - radius, y)){collision=true;continue;}
            p->SetProbeState(x,y, 1-rand()%20/10.0, 1-rand()%20/10.0, false);

vi src/probe.cpp

 #include "configmanager.h"
+#include "collisionmask.h"
 #include <SDL/SDL_gfxPrimitives.h>


         // add power to push off with if pushing off
         if ( (PUSH_POWERING == pushOffState) && (pushOffSpeed < MAXSPEED) ) {
                 pushOffSpeed += MAXSPEED/POWERUPTIME;
         }

+    // --- 完全に囲まれているかチェック ---
+    int radius = p->GetWidth();
+    bool leftWall  = collision_mask->TestPixel((int)(x_position - 25), (int)y_position);
+    bool rightWall = collision_mask->TestPixel((int)(x_position + 25), (int)y_position);
+    bool upWall    = collision_mask->TestPixel((int)x_position, (int)(y_position - 25));
+    bool downWall  = collision_mask->TestPixel((int)x_position, (int)(y_position + 25));
+
+    if (leftWall && rightWall && upWall && downWall) {
+
+        // --- ランダム安全位置を探す ---
+        for (int i = 0; i < 20; i++) {
+            int rx = 50 + rand() % (collision_mask->GetWidth()-100);
+            int ry = 50 + rand() % (collision_mask->GetHeight()-100);
+
+            // 壁でない位置なら安全
+            if (!collision_mask->TestPixel(rx, ry)) {
+                x_position = rx;
+                y_position = ry;
+
+                // 速度リセット
+                x_velocity = 0;
+                y_velocity = 0;
+
+                return; // テレポート成功
+            }
+        }
+
+        // --- 安全位置が見つからなかった → 自爆 ---
+        Die();
+        return;
+    }
 }

scons

#高速化設定
#画面、サウンド、CPU最適化

vi src/renderer.cpp

-screen = SDL_SetVideoMode(width, height, 0, flags);
+screen = SDL_SetVideoMode(width, height, 16, flags);

vi src/soundmanager.cpp

-    Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,512);
+    Mix_OpenAudio(11025,MIX_DEFAULT_FORMAT,2,512);

vi SConstruct

-env = Environment(CPPPATH=includepath)
+env = Environment(
+    CPPPATH=includepath,
+    CCFLAGS='-march=armv5te -mtune=arm926ej-s -mfloat-abi=soft -O3 -fomit-frame-pointer -ffast-math'
+)

-    env.Append(CCFLAGS = '-g')
+    env.Append(CCFLAGS = ' -g')

vi include/probe.h

 #define PROBE_H

+extern float sin_table[360];
+extern float cos_table[360];

 #define RECHARGE_FRAME_INTERVAL_ON 50
 #define RECHARGE_FRAME_INTERVAL_OFF 3000

vi src/main.cpp

 #include "log.h"
+#include <math.h>

+float sin_table[360];
+float cos_table[360];



+for (int i = 0; i < 360; i++) {
+    float rad = i * M_PI / 180.0f;
+    sin_table[i] = sin(rad);
+    cos_table[i] = cos(rad);
+}
 while(menu->Run())

vi src/probe.cpp

 void Probe::DrawOnto(SDL_Surface* destination)
 {

  ・・・・

         // Experimental aiming indicator
         Uint32 aimerColor = 0xFFD700FF;
-        float x_direction = cos(facing_angle);
-        float y_direction = sin(facing_angle);
+        // ---- angle → table index ----
+        int ang = (int)(facing_angle * 180.0f / M_PI);
+        ang = (ang % 360 + 360) % 360;
+        float x_direction = cos_table[ang];
+        float y_direction = sin_table[ang];
         int x_pos = int(x_position + x_direction*PROBEWIDTH*3.0/5);
         int y_pos = int(y_position + y_direction*PROBEHEIGHT*3.0/5);
         circleColor(destination, x_pos, y_pos, 1, aimerColor);
         if (powerups & EFFECT_SPREADFIRE) { // draw extra guns
-            x_direction = cos(facing_angle + M_PI/48);
-            y_direction = sin(facing_angle + M_PI/48);
+            // +3.75° → +4°で近似
+            int ang_plus = ang + 4;
+            if (ang_plus >= 360) ang_plus -= 360;
+            x_direction = cos_table[ang_plus];
+            y_direction = sin_table[ang_plus];
-            x_pos = int(x_position + cos(facing_angle + M_PI/12)*PROBEWIDTH*3.0/5);
-            y_pos = int(y_position + sin(facing_angle + M_PI/12)*PROBEHEIGHT*3.0/5);
+            // muzzle offset +15°
+            int muzzle_plus = ang + 15;
+            if (muzzle_plus >= 360) muzzle_plus -= 360;
+            x_pos = int(x_position + cos_table[muzzle_plus] * PROBEWIDTH * 3.0f / 5);
+            y_pos = int(y_position + sin_table[muzzle_plus] * PROBEHEIGHT * 3.0f / 5);
             circleColor(destination, x_pos, y_pos, 1, aimerColor);
+            // -3.75° → -4°で近似
-            x_direction = cos(facing_angle - M_PI/48);
-            y_direction = sin(facing_angle - M_PI/48);
+            int ang_minus = ang - 4;
+            if (ang_minus < 0) ang_minus += 360;
+            x_direction = cos_table[ang_minus];
+            y_direction = sin_table[ang_minus];
-            x_pos = int(x_position + cos(facing_angle - M_PI/12)*PROBEWIDTH*3.0/5);
-            y_pos = int(y_position + sin(facing_angle - M_PI/12)*PROBEHEIGHT*3.0/5);
+            // muzzle offset -15°
+            int muzzle_minus = ang - 15;
+            if (muzzle_minus < 0) muzzle_minus += 360;
+            x_pos = int(x_position + cos_table[muzzle_minus] * PROBEWIDTH * 3.0f / 5);
+            y_pos = int(y_position + sin_table[muzzle_minus] * PROBEHEIGHT * 3.0f / 5);
             circleColor(destination, x_pos, y_pos, 1, aimerColor);
         }
 
  ・・・・
 }


void Probe::DrawAmmo(SDL_Surface* destination)
{
    if (GetCharge() > 0) {
        SDL_Surface* s = Renderer::sprite_registry["probe"];
        if (!s) throw "no sprite found for entity!";
        SDL_Rect sourceRect = {0, PROBEHEIGHT*2, AMMOWIDTH, AMMOHEIGHT};

        ammo_angle += M_PI/48;
        if (ammo_angle > 2*M_PI) { ammo_angle -= 2*M_PI;}
        // initialize calculation variables.
        double ammoAngle = 0;
        SDL_Rect target_rect;

        for (int currentCharge = 0;currentCharge < GetCharge(); currentCharge++) {
            ammoAngle = ((2*M_PI)/GetCharge())*currentCharge;
            ammoAngle += ammo_angle;

-           float x_direction = cos(ammoAngle);
-           float y_direction = sin(ammoAngle);
+           // ---- angle → table index ----
+           int ang = (int)(ammoAngle * 180.0f / M_PI);
+           ang = (ang % 360 + 360) % 360;
+           float x_direction = cos_table[ang];
+           float y_direction = sin_table[ang];
            float x_pos = x_position + x_direction*PROBEWIDTH*3/8; // 3/4 probe radius
            float y_pos = y_position + y_direction*PROBEHEIGHT*3/8;
            target_rect.x = Sint16(x_pos - AMMOWIDTH/2);
            target_rect.y = Sint16(y_pos - AMMOHEIGHT/2);

            // draw ammo frames.
            SDL_BlitSurface( s, &sourceRect, destination, &target_rect);
        }
    }
}


 void Probe::PushOff (  )
 {
         if (stuck){
-                x_velocity = pushOffSpeed*cos(facing_angle);
-                y_velocity = pushOffSpeed*sin(facing_angle);
+                int ang = (int)(facing_angle * 180.0f / M_PI);
+                ang = (ang % 360 + 360) % 360;
+                if (pushOffSpeed < 1.0f)
+                    pushOffSpeed = 1.0f;
+                x_velocity = pushOffSpeed*cos_table[ang];
+                y_velocity = pushOffSpeed*sin_table[ang];
                 pushOffSpeed = 0;
                 game_manager->PlaySound("probe push");
         }
         stuck = false;
         pushOffState = 0;
 }


 void Probe::Fire (  )
 {
     // make sure you can't fire when you're dead
     if (death_frame) { return;}
         if(charge_remaining > 1){
                 charge_remaining -= 1;

+        // ---- angle → table index ----
+        int ang = (int)(facing_angle * 180.0f / M_PI);
+        ang = (ang % 360 + 360) % 360;
         // fire the projectile
-        float x_direction = cos(facing_angle);
-        float y_direction = sin(facing_angle);
+        float x_direction = cos_table[ang];
+        float y_direction = sin_table[ang];
         float x_pos = x_position + x_direction*2*PROBEWIDTH/3;
         float y_pos = y_position + y_direction*2*PROBEHEIGHT/3;
         if (!Projectile::Add_Probe_Velocity) game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED, y_direction*Projectile::SPEED);
         else game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED+x_velocity, y_direction*Projectile::SPEED+y_velocity);
                 if (powerups & EFFECT_SPREADFIRE) { // fire 2 bonus shots
-                        x_direction = cos(facing_angle + M_PI/48);
-                        y_direction = sin(facing_angle + M_PI/48);
-            x_pos = x_position + cos(facing_angle + M_PI/12)*2*PROBEWIDTH/3;
-            y_pos = y_position + sin(facing_angle + M_PI/12)*2*PROBEHEIGHT/3;
+                        // + angle
+                        int ang2 = ang + 4;  // M_PI/48 ≒ 3.75°
+                        if (ang2 >= 360) ang2 -= 360;
+                        x_direction = cos_table[ang2];
+                        y_direction = sin_table[ang2];
+            x_pos = x_position + cos_table[(ang + 15) % 360]*2*PROBEWIDTH/3;
+            y_pos = y_position + sin_table[(ang + 15) % 360]*2*PROBEHEIGHT/3;
             if (!Projectile::Add_Probe_Velocity) game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED, y_direction*Projectile::SPEED);
             else game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*4+x_velocity, y_direction*Projectile::SPEED+y_velocity);
-                        x_direction = cos(facing_angle - M_PI/48);
-                        y_direction = sin(facing_angle - M_PI/48);
-            x_pos = x_position + cos(facing_angle - M_PI/12)*2*PROBEWIDTH/3;
-            y_pos = y_position + sin(facing_angle - M_PI/12)*2*PROBEHEIGHT/3;
+                        // - angle
+                        int ang3 = ang - 4;
+                        if (ang3 < 0) ang3 += 360;
+                        x_direction = cos_table[ang3];
+                        y_direction = sin_table[ang3];
+            x_pos = x_position + cos_table[(ang + 360 - 15) % 360]*2*PROBEWIDTH/3;
+            y_pos = y_position + sin_table[(ang + 360 - 15) % 360]*2*PROBEHEIGHT/3;
             if (!Projectile::Add_Probe_Velocity) game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED, y_direction*Projectile::SPEED);
             else game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED+x_velocity, y_direction*Projectile::SPEED+y_velocity);
                 }
                 // now the reaction from firing if not stuck to a wall
                 if (!stuck){
                         x_velocity -= x_direction/4;
                         y_velocity -= y_direction/4;
                 }
                 game_manager->PlaySound("fire");
         }
 }

scons -c
scons

 

改造のお時間

ここからは改造の時間。必須ではないのでお好みで。

#弾を撃った時の反動を強くする。
#(オリジナルだとほとんど反動が無いので、元の8倍の反動にする場合)

vi src/probe.cpp

                if (!stuck){
-                        x_velocity -= x_direction/4;
-                        y_velocity -= y_direction/4;
+                        x_velocity -= x_direction*2;
+                        y_velocity -= y_direction*2;
                }
                game_manager->PlaySound("fire");

scons

 

#弾?のチャージ速度を上げる[もしかしたらシールドかも]。
#(デフォルトの2倍のチャージ速度):

vi inclide/probe.h

-#define RECHARGE_FRAME_INTERVAL_ON 50
-#define RECHARGE_FRAME_INTERVAL_OFF 3000

+#define RECHARGE_FRAME_INTERVAL_ON 25
+#define RECHARGE_FRAME_INTERVAL_OFF 1500

scons

 

#最大弾数、弾のチャージ速度の調整(xmlファイルの変更で対応可能)
maxcharge  :最大弾数     例:5->10
rechargetime :弾のチャージ速度 例:400->200

bin/config.xml

    <Probe>
        <maxhits value="3" />
        <maxcharge value="5" />
        <rechargetime value="400" />
        <maxspeed value="3" />
        <poweruptime value="10" />
        <turningspeed value="0.041666667" />
    </Probe>

 

#最大弾数の2倍弾を打てるようにする
#(ただし敵の弾も大量に出るようになるので難易度も上がります。)

vi src/probe.cpp

-charge_remaining -= 1
+charge_remaining -= 0.5

scons

AIのC++移植

AIは、Python2.4のC APIを利用しているため、
Teixieで手動使用可能なPython2.7では稼働しないようです。
Python2.4は開発用ソースすら依存関係で導入できないので、
このままではAIは利用できません。

ですので、Python2.4製AIコードをC++に移植してみます。

balderaiAI(balderai.py の完全 C++ 移植)
パワーアップ優先+射撃間隔調整+スタック脱出

RandomAI(randomai.py の C++ 移植)
完全ランダム

SimpleAimerAI(simple_aimer.py の C++ 移植)
最も近い敵に向けて撃つ

追加

AggressiveAI(攻撃特化)
敵に積極的に接近し常に射撃を優先。回避より攻撃します。

DefensiveAI(回避・生存特化)
敵から距離を取壁や弾を避ける。射撃より生存優先でパワーアップを積極的に取りに行く

SniperAI
最も近い 1 体だけをターゲットにする「一点集中型」「視野が狭いが精密」なため近づくと危険

 

新規追加

mkdir src/ai
vi src/ai/BaseAI.h

#ifndef BALDER_BASEAI_H
#define BALDER_BASEAI_H

#include <vector>
#include "balder2dtypes.h"

namespace Balder {

class Probe;
class Entity;
struct CollisionMask;

class BaseAI {
public:
    virtual ~BaseAI() {}

    virtual input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    ) = 0;
};

}

#endif

vi src/ai/DefaultAI.h

#ifndef BALDER_DEFAULTAI_H
#define BALDER_DEFAULTAI_H

#include "BaseAI.h"

namespace Balder {

class DefaultAI : public BaseAI {
public:
    input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    ) override;
};

}

#endif

vi src/ai/DefaultAI.cpp

#include "DefaultAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>

using namespace Balder;

static Probe* FindClosestProbe(Probe* self, const std::vector<Probe*>& probes) {
    Probe* best = nullptr;
    float bestDist2 = 0.0f;
    float x = self->GetX();
    float y = self->GetY();

    for (Probe* p : probes) {
        if (p->GetOwner() == self->GetOwner()) continue;
        float dx = p->GetX() - x;
        float dy = p->GetY() - y;
        float d2 = dx*dx + dy*dy;
        if (!best || d2 < bestDist2) {
            best = p;
            bestDist2 = d2;
        }
    }
    return best;
}

static Entity* FindClosestEntity(float x, float y, const std::vector<Entity*>& list) {
    Entity* best = nullptr;
    float bestDist2 = 0.0f;

    for (Entity* e : list) {
        float dx = e->GetX() - x;
        float dy = e->GetY() - y;
        float d2 = dx*dx + dy*dy;
        if (!best || d2 < bestDist2) {
            best = e;
            bestDist2 = d2;
        }
    }
    return best;
}

input_states DefaultAI::Update(
    Probe* self,
    const std::vector<Probe*>& probes,
    const std::vector<Entity*>& projectiles,
    const std::vector<Entity*>& powerups,
    CollisionMask* geometry
) {
    float x = self->GetX();
    float y = self->GetY();

    input_states inputs = 0;

    // powerup 優先
    if (!powerups.empty() && self->GetStuckStatus()) {
        Entity* pu = FindClosestEntity(x, y, powerups);
        if (pu) {
            float dx = pu->GetX() - x;
            float dy = pu->GetY() - y;
            float targetAngle = std::atan2(dy, dx);
            float currentAngle = self->GetRotationAngle();
            float diff = targetAngle - currentAngle;

            while (diff >  M_PI) diff -= 2*M_PI;
            while (diff < -M_PI) diff += 2*M_PI;

            if (diff > 0.1f) return RIGHT;
            if (diff < -0.1f) return LEFT;

            return STICK;
        }
    }

    // 敵を狙う
    Probe* target = FindClosestProbe(self, probes);
    if (!target) return 0;

    float dx = target->GetX() - x;
    float dy = target->GetY() - y;
    float targetAngle = std::atan2(dy, dx);
    float currentAngle = self->GetRotationAngle();
    float diff = targetAngle - currentAngle;

    while (diff >  M_PI) diff -= 2*M_PI;
    while (diff < -M_PI) diff += 2*M_PI;

    if (diff > 0.1f) return RIGHT;
    if (diff < -0.1f) return LEFT;

    return FIRE;
}

vi src/ai/BalderAI.h

#ifndef BALDER_BALDERAI_H
#define BALDER_BALDERAI_H

#include "BaseAI.h"

namespace Balder {

class BalderAI : public BaseAI {
public:
    input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    ) override;
};

}

#endif

vi src/ai/BalderAI.cpp

#include "BalderAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>

using namespace Balder;

static Probe* FindClosestProbe(Probe* self, const std::vector<Probe*>& probes) {
    Probe* best = nullptr;
    float bestDist2 = 0.0f;
    float x = self->GetX();
    float y = self->GetY();

    for (Probe* p : probes) {
        if (p->GetOwner() == self->GetOwner()) continue;
        float dx = p->GetX() - x;
        float dy = p->GetY() - y;
        float d2 = dx*dx + dy*dy;
        if (!best || d2 < bestDist2) {
            best = p;
            bestDist2 = d2;
        }
    }
    return best;
}

static Entity* FindClosestEntity(float x, float y, const std::vector<Entity*>& list) {
    Entity* best = nullptr;
    float bestDist2 = 0.0f;

    for (Entity* e : list) {
        float dx = e->GetX() - x;
        float dy = e->GetY() - y;
        float d2 = dx*dx + dy*dy;
        if (!best || d2 < bestDist2) {
            best = e;
            bestDist2 = d2;
        }
    }
    return best;
}

input_states BalderAI::Update(
    Probe* self,
    const std::vector<Probe*>& probes,
    const std::vector<Entity*>& projectiles,
    const std::vector<Entity*>& powerups,
    CollisionMask* geometry
) {
    float x = self->GetX();
    float y = self->GetY();

    input_states inputs = 0;

    // powerup 優先
    if (!powerups.empty() && self->GetStuckStatus()) {
        Entity* pu = FindClosestEntity(x, y, powerups);
        if (pu) {
            float dx = pu->GetX() - x;
            float dy = pu->GetY() - y;
            float targetAngle = std::atan2(dy, dx);
            float currentAngle = self->GetRotationAngle();
            float diff = targetAngle - currentAngle;

            while (diff >  M_PI) diff -= 2*M_PI;
            while (diff < -M_PI) diff += 2*M_PI;

            if (diff > 0.1f) return RIGHT;
            if (diff < -0.1f) return LEFT;

            return STICK;
        }
    }

    // 敵を狙う
    Probe* target = FindClosestProbe(self, probes);
    if (!target) return 0;

    float dx = target->GetX() - x;
    float dy = target->GetY() - y;
    float targetAngle = std::atan2(dy, dx);
    float currentAngle = self->GetRotationAngle();
    float diff = targetAngle - currentAngle;

    while (diff >  M_PI) diff -= 2*M_PI;
    while (diff < -M_PI) diff += 2*M_PI;

    if (diff > 0.1f) return RIGHT;
    if (diff < -0.1f) return LEFT;

    return FIRE;
}

vi src/ai/RandomAI.h

#ifndef BALDER_RANDOMAI_H
#define BALDER_RANDOMAI_H

#include "BaseAI.h"
#include <cstdlib>

namespace Balder {

class RandomAI : public BaseAI {
public:
    input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    ) override {
        return rand() % 17;
    }
};

}

#endif

vi src/ai/SimpleAimerAI.h

#ifndef BALDER_SIMPLEAIMERAI_H
#define BALDER_SIMPLEAIMERAI_H

#include "BaseAI.h"

namespace Balder {

class SimpleAimerAI : public BaseAI {
public:
    input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    ) override;
};

}

#endif

vi src/ai/SimpleAimerAI.cpp

#include "SimpleAimerAI.h"
#include "probe.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>

using namespace Balder;

input_states SimpleAimerAI::Update(
    Probe* self,
    const std::vector<Probe*>& probes,
    const std::vector<Entity*>&,
    const std::vector<Entity*>&,
    CollisionMask*
) {
    float x = self->GetX();
    float y = self->GetY();

    Probe* target = nullptr;
    float bestDist2 = 0.0f;

    for (Probe* p : probes) {
        if (p->GetOwner() == self->GetOwner()) continue;
        float dx = p->GetX() - x;
        float dy = p->GetY() - y;
        float d2 = dx*dx + dy*dy;
        if (!target || d2 < bestDist2) {
            target = p;
            bestDist2 = d2;
        }
    }

    if (!target) return 0;

    float dx = target->GetX() - x;
    float dy = target->GetY() - y;
    float targetAngle = std::atan2(dy, dx);
    float currentAngle = self->GetRotationAngle();
    float diff = targetAngle - currentAngle;

    while (diff >  M_PI) diff -= 2*M_PI;
    while (diff < -M_PI) diff += 2*M_PI;

    if (diff > 0.1f) return RIGHT;
    if (diff < -0.1f) return LEFT;

    return FIRE;
}

vi src/ai/AggressiveAI.h

#ifndef BALDER_AGGRESSIVEAI_H
#define BALDER_AGGRESSIVEAI_H

#include "BaseAI.h"

namespace Balder {

class AggressiveAI : public BaseAI {
public:
    input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    ) override;
};

}

#endif

vi src/ai/AggressiveAI.cpp

#include "AggressiveAI.h"
#include "probe.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>

using namespace Balder;

input_states AggressiveAI::Update(
    Probe* self,
    const std::vector<Probe*>& probes,
    const std::vector<Entity*>&,
    const std::vector<Entity*>&,
    CollisionMask*
) {
    float x = self->GetX();
    float y = self->GetY();

    Probe* target = nullptr;
    float bestDist2 = 0.0f;

    for (Probe* p : probes) {
        if (p->GetOwner() == self->GetOwner()) continue;
        float dx = p->GetX() - x;
        float dy = p->GetY() - y;
        float d2 = dx*dx + dy*dy;
        if (!target || d2 < bestDist2) {
            target = p;
            bestDist2 = d2;
        }
    }

    if (!target) return 0;

    float dx = target->GetX() - x;
    float dy = target->GetY() - y;
    float targetAngle = std::atan2(dy, dx);
    float currentAngle = self->GetRotationAngle();
    float diff = targetAngle - currentAngle;

    while (diff >  M_PI) diff -= 2*M_PI;
    while (diff < -M_PI) diff += 2*M_PI;

    const float turnThresh = 0.05f;

    if (diff > turnThresh) return RIGHT;
    if (diff < -turnThresh) return LEFT;

    return FIRE;
}

vi src/ai/DefensiveAI.h

#ifndef BALDER_DEFENSIVEAI_H
#define BALDER_DEFENSIVEAI_H

#include "BaseAI.h"

namespace Balder {

class DefensiveAI : public BaseAI {
public:
    input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    ) override;
};

}

#endif

vi src/ai/DefensiveAI.cpp

#include "DefensiveAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>

using namespace Balder;

input_states DefensiveAI::Update(
    Probe* self,
    const std::vector<Probe*>& probes,
    const std::vector<Entity*>&,
    const std::vector<Entity*>& powerups,
    CollisionMask*
) {
    float x = self->GetX();
    float y = self->GetY();

    // パワーアップ優先
    if (!powerups.empty()) {
        Entity* best = nullptr;
        float bestDist2 = 0.0f;

        for (Entity* e : powerups) {
            float dx = e->GetX() - x;
            float dy = e->GetY() - y;
            float d2 = dx*dx + dy*dy;
            if (!best || d2 < bestDist2) {
                best = e;
                bestDist2 = d2;
            }
        }

        if (best) {
            float dx = best->GetX() - x;
            float dy = best->GetY() - y;
            float targetAngle = std::atan2(dy, dx);
            float currentAngle = self->GetRotationAngle();
            float diff = targetAngle - currentAngle;

            while (diff >  M_PI) diff -= 2*M_PI;
            while (diff < -M_PI) diff += 2*M_PI;

            if (diff > 0.1f) return RIGHT;
            if (diff < -0.1f) return LEFT;

            return STICK;
        }
    }

    // 敵から逃げる
    Probe* closest = nullptr;
    float bestDist2 = 0.0f;

    for (Probe* p : probes) {
        if (p->GetOwner() == self->GetOwner()) continue;
        float dx = p->GetX() - x;
        float dy = p->GetY() - y;
        float d2 = dx*dx + dy*dy;
        if (!closest || d2 < bestDist2) {
            closest = p;
            bestDist2 = d2;
        }
    }

    if (!closest) return 0;

    float dx = x - closest->GetX();
    float dy = y - closest->GetY();
    float targetAngle = std::atan2(dy, dx);
    float currentAngle = self->GetRotationAngle();
    float diff = targetAngle - currentAngle;

    while (diff >  M_PI) diff -= 2*M_PI;
    while (diff < -M_PI) diff += 2*M_PI;

    if (diff > 0.1f) return RIGHT;
    if (diff < -0.1f) return LEFT;

    return STICK;
}

vi src/ai/WallFollowerAI.h

#ifndef BALDER_WALLFOLLOWERAI_H
#define BALDER_WALLFOLLOWERAI_H

#include "BaseAI.h"

namespace Balder {

class WallFollowerAI : public BaseAI {
public:
    WallFollowerAI() {}
    virtual input_states Update(
        Probe* self,
        const std::vector<Probe*>& probes,
        const std::vector<Entity*>& projectiles,
        const std::vector<Entity*>& powerups,
        CollisionMask* geometry
    );
};

}

#endif

vi src/ai/WallFollowerAI.cpp

#include "WallFollowerAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>

using namespace Balder;

input_states WallFollowerAI::Update(
    Probe* self,
    const std::vector<Probe*>&,
    const std::vector<Entity*>&,
    const std::vector<Entity*>&,
    CollisionMask* geometry
) {
    input_states out = 0;

    double x = self->GetX();
    double y = self->GetY();

    // 壁との距離チェック(12px 先を調べる)
    bool leftWall  = geometry->Collide(x - 12, y);
    bool rightWall = geometry->Collide(x + 12, y);
    bool upWall    = geometry->Collide(x, y - 12);
    bool downWall  = geometry->Collide(x, y + 12);

    // --- 壁沿い移動ロジック(右手法) ---
    // 1. 右側に壁がある → 前進(Stick)
    // 2. 右側に壁がない → 右に旋回
    // 3. 正面に壁がある → 左に旋回
    // 4. それ以外 → 前進

    // 正面の壁
    bool frontWall = geometry->Collide(
        x + cos(self->GetRotationAngle()) * 12,
        y + sin(self->GetRotationAngle()) * 12
    );

    // 右側の壁(右方向 90°)
    bool rightSideWall = geometry->Collide(
        x + cos(self->GetRotationAngle() + M_PI/2) * 12,
        y + sin(self->GetRotationAngle() + M_PI/2) * 12
    );

    if (!rightSideWall) {
        // 右に壁が無い → 右へ旋回
        out |= RIGHT;
    } else if (frontWall) {
        // 正面に壁 → 左へ旋回
        out |= LEFT;
    } else {
        // 壁に沿って前進(Stick)
        out |= STICK;
    }

    return out;
}

 

ソースファイル差し替え

cp include/computerprobecontrol.h include/computerprobecontrol.h.bkpy
vi include/computerprobecontrol.h

#ifndef BALDER_COMPUTERPROBECONTROL_H
#define BALDER_COMPUTERPROBECONTROL_H

#include "balder2dtypes.h"
#include <string>

namespace Balder {

class BaseAI;

struct ComputerProbeControl {
    player_id id;
    input_states inputs;
    BaseAI* ai;

    ComputerProbeControl(player_id pid, BaseAI* aiInstance)
        : id(pid), inputs(0), ai(aiInstance) {}

    ~ComputerProbeControl() {
        delete ai;
    }
};

}

#endif

cp src/computerprobecontrol.cpp src/computerprobecontrol.cpp.bkpy
vi src/computerprobecontrol.cpp

#include "computerprobecontrol.h"

using namespace Balder;

// 実装不要。
// すべての処理はヘッダ内のコンストラクタ/デストラクタで完結します。

cp src/artificialcontroller.cpp src/artificialcontroller.cpp.bkpy
vi src/artificialcontroller.cpp

#include "artificialcontroller.h"
#include "computerprobecontrol.h"
#include "gamemanager.h"
#include "collisionmask.h"
#include "entity.h"
#include "probe.h"

#include "ai/BaseAI.h"
#include "ai/DefaultAI.h"
#include "ai/BalderAI.h"
#include "ai/RandomAI.h"
#include "ai/SimpleAimerAI.h"
#include "ai/AggressiveAI.h"
#include "ai/DefensiveAI.h"
#include "ai/SniperAI.h"
#include "ai/WallFollowerAI.h"

#include <cmath>
#include <vector>

using namespace Balder;

GameManager* ArtificialController::game_manager = nullptr;
CollisionMask* ArtificialController::geometry = nullptr;
std::list<ComputerProbeControl*> ArtificialController::controlled_probes;

ArtificialController::ArtificialController() {}

void ArtificialController::Initialize()
{
    // Python は完全削除されたため、ここでは何もしない
}

void ArtificialController::ShutDown()
{
    for (auto* ctrl : controlled_probes)
        delete ctrl;

    controlled_probes.clear();
}

void ArtificialController::ControlProbe(player_id id, std::string aiName)
{
    BaseAI* ai = nullptr;

    if (aiName == "default")
        ai = new DefaultAI();
    else if (aiName == "balderai")
        ai = new BalderAI;
    else if (aiName == "randomai")
        ai = new RandomAI();
    else if (aiName == "simple_aimer")
        ai = new SimpleAimerAI();
    else if (aiName == "aggressive")
        ai = new AggressiveAI();
    else if (aiName == "defensive")
        ai = new DefensiveAI();
    else if (aiName == "sniperai")
        ai = new SniperAI();
    else if (aiName == "wallfollower")
        ai = new WallFollowerAI();
    else
        ai = new DefaultAI(); // fallback

    controlled_probes.push_back(new ComputerProbeControl(id, ai));
}

void ArtificialController::StopControlling(player_id id)
{
    for (auto it = controlled_probes.begin(); it != controlled_probes.end(); ++it) {
        if ((*it)->id == id) {
            delete *it;
            controlled_probes.erase(it);
            return;
        }
    }
}

void ArtificialController::DoControl(std::list<Entity*>& entities)
{
    // エンティティ分類
    std::vector<Probe*> probes;
    std::vector<Entity*> projectiles;
    std::vector<Entity*> powerups;

    for (Entity* e : entities) {
        if (e->GetType() == "probe") {
            if (Probe* p = dynamic_cast<Probe*>(e))
                probes.push_back(p);
        }
        else if (e->GetType() == "projectile") {
            projectiles.push_back(e);
        }
        else if (e->GetType() == "powerup") {
            powerups.push_back(e);
        }
    }

    // 各 AI を実行
    for (auto it = controlled_probes.begin(); it != controlled_probes.end(); ++it) {
        ComputerProbeControl* ctrl = *it;

        // 自分の Probe を探す
        Probe* self = nullptr;
        for (Probe* p : probes) {
            if (p->GetOwner() == ctrl->id) {
                self = p;
                break;
            }
        }

        if (!self)
            continue;

        // C++ AI を実行
        input_states newInputs =
            ctrl->ai->Update(self, probes, projectiles, powerups, geometry);

        // 入力が変わったら送信
        if (newInputs != ctrl->inputs) {
            ctrl->inputs = newInputs;
            game_manager->SendInputState(ctrl->id, ctrl->inputs);
        }
    }
}

void ArtificialController::SetGeometry(CollisionMask* cm)
{
    geometry = cm;
}

void ArtificialController::SetGameManager(GameManager* gm)
{
    game_manager = gm;
}


ソースの一部変更

vi src/menu/widgets/ailistmodel.cpp

 void AIListModel::FindScripts()
 {
-    std::string script_dir("scripts/");
-    // always show Human option as it disables any ai scripting so that a user may play.
-        scriptnames.push_back("Human");
-    ++numScripts;
-    char **files = PHYSFS_enumerateFiles("scripts/");
-    char **i;
-    std::string script;
-    for (i = files; *i != NULL; i++){
-        script = *i;
-        if (!PHYSFS_isDirectory((script_dir+script).c_str()) && !script.compare(script.size()-3, 3, ".py")){
-            scriptnames.push_back(script.substr(0,script.size()-3));
-            ++numScripts;
-        }
-    }
-    PHYSFS_freeList(files);
+   scriptnames.clear();
+    numScripts = 0;
+
+    // Human(人間プレイヤー)
+    scriptnames.push_back("Human");
+    ++numScripts;
+
+    // C++ AI を固定で追加
+    scriptnames.push_back("balderai");
+    scriptnames.push_back("randomai");
+    scriptnames.push_back("simple_aimer");
+    scriptnames.push_back("aggressive");
+    scriptnames.push_back("defensive");
+    scriptnames.push_back("sniperai");
+    numScripts += 6;
+    scriptnames.push_back("wallfollower");
+    ++numScripts;
+    scriptnames.push_back("default");
+    ++numScripts;
 }

vi SConstruct

includepath = ['/usr/include/python2.7','include']
includepath = ['include']

libs = Split('guichan_sdl guichan SDL SDL_image SDL_mixer SDL_gfx python2.7 physfs')
libs = Split('guichan_sdl guichan SDL SDL_image SDL_mixer SDL_gfx physfs')

SConscript('bin/scripts/test/SConscript')
#SConscript('bin/scripts/test/SConscript')

scons -c
scons

Views: 0

カテゴリー: 未分類 パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です