【Processing】【AI】ProcessingでサッカーゲームのAIシミュレーションアプリを作る

#0.ソースコードのリンク
全体のソースコードは
↓リンク↓
http://qiita.com/66zaha_9su/items/3ebe6ba302b4627c53c6

#1.ことの発端

同胞で集まる定例会にて、内輪のPCを覗かせてもらった。
それが、AIを使った1対1のシューティングのデモだった。

それから着想を得て、「それに似たものを作りたいなぁ」と思い。
制作に至った。

#2.AIとは？
**A**rtificial **I**ntelligence の略。
日本語に訳せば、「人工的な知能」。

・
・
・

一括りに「**AI**」といっても、さまざま種類のAIが存在します。
大きく分けて2つ。
1つ目は「学習するAI」
もう1つは「学習しないAI」です。

1つ目の具体例として「Alpha Go」があり、
「Alpha Go」で囲碁プレーヤーを打ち負かしたことで
とても有名になりました。
他にも「Watson」など・・

もう1つの方は
各場面に応じて手を考える「オセロプログラムのAI」や
プレイヤーに対して絶対勝てる手を出す「じゃんけんプログラムの相手のAI」
などもその1つといえるでしょう。

#3.プログラムの紹介とその他の説明
サッカーのプレイヤーをAIとして作った。
サッカーの代表的なルールの
「オフサイド」
「イエローカード」
「レッドカード」
は無しとする。

そしてキーパーも居ない。


#4.ソース解説

プレイヤークラス

```
class Player{
  float p_radius = 28;
  float p_xPos;
  float p_yPos;
  float p_xVec;
  float p_yVec;
  float p_spd;
  float p_angl;
  float p_power;
  
  int p_siya;
  int p_siya_d;
  
  Player(){    //mainly initiallize process    initial position determine another process
  p_spd=12*random(0.6,0.9);
  p_angl=0;
  p_xPos=0;
  p_yPos=0;
  
  p_siya=(int) random(2,12);
  p_siya_d=(int) random(160,600);
  
  p_power=random(12,32);
  }
  
  
  void P_initialize(int _number){
    int p_number = _number;
    
    if(p_number%2 ==0){
      
      if(p_number == 0){
        fill(#ff7fff);
        ellipse(40, 80, p_radius, p_radius);
        p_xPos=40; p_yPos=80;
      }
      
      if(p_number == 2){
        fill(#ff7fff);
        ellipse(200, 80, p_radius, p_radius);
        p_xPos=200; p_yPos=80;
      }
      
      if(p_number == 4){
        fill(#ff7fff);
        ellipse(360, 80, p_radius, p_radius);
        p_xPos=360; p_yPos=80;
      }
      
      if(p_number == 6){
        fill(#ff7fff);
        ellipse(150, 350, p_radius, p_radius);
        p_xPos=150; p_yPos=350;
      }
      
      if(p_number == 8){
        fill(#ff7fff);
        ellipse(250, 350, p_radius, p_radius);
        p_xPos=250; p_yPos=350;
      }
    }
    
    else if(p_number%2 ==1){
      
      if(p_number == 1){
        fill(#00bfff);
        ellipse(40, 720, p_radius, p_radius);
        p_xPos=40; p_yPos=720;
      }
      
      if(p_number == 3){
        fill(#00bfff);
        ellipse(200, 720, p_radius, p_radius);
        p_xPos=200; p_yPos=720;
      }
      
      if(p_number == 5){
        fill(#00bfff);
        ellipse(360, 720, p_radius, p_radius);
        p_xPos=360; p_yPos=720;
      }
      
      if(p_number == 7){
        fill(#00bfff);
        ellipse(150, 450, p_radius, p_radius);
        p_xPos=150; p_yPos=450;
      }
      
      if(p_number == 9){
        fill(#00bfff);
        ellipse(250, 450, p_radius, p_radius);
        p_xPos=250; p_yPos=450;
      }
    }
    
  }

  
  void update(int num){
    float p_dist;
    float x_dist;
    float y_dist;
    float kakudo;
    
    float p_siya_f;    //f means from
    float p_siya_e;    //e means end
    
    float p_x_ran;
    float p_y_ran;
    
    
    p_dist=sqrt((p_xPos-eb_xPos)*(p_xPos-eb_xPos)+(p_yPos-eb_yPos)*(p_yPos-eb_yPos));
    x_dist=eb_xPos-p_xPos;
    y_dist=eb_yPos-p_yPos;
    
    kakudo=degrees(atan2(x_dist,y_dist));
    
    if(kakudo < 0){
      kakudo+=kakudo+360;
    }
    
    p_angl=kakudo;
    
    p_siya_f = 360 * ( (float)frame % (float)p_siya ) / (float)p_siya;
    p_siya_e = 360 * ( (float)frame % (float)p_siya +1) / (float)p_siya;
    

    if( p_dist < ((float)p_siya_d) && p_siya_f < kakudo && kakudo < p_siya_e){

      
      if(x_dist<=0 && y_dist <=0){
        p_x_ran=p_spd*(abs(cos(kakudo*PI/180)))*-1;
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*-1;
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      else if(x_dist>=0 && y_dist <=0){
        p_x_ran=p_spd*abs(cos(kakudo*PI/180));
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*-1;
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      if(x_dist<=0 && y_dist >=0){
        p_x_ran=p_spd*abs(cos(kakudo*PI/180))*-1;
        p_y_ran=p_spd*abs(sin(kakudo*PI/180));
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      else if(x_dist>=0 && y_dist >=0){
        p_x_ran=p_spd*abs(cos(kakudo*PI/180));
        p_y_ran=p_spd*abs(sin(kakudo*PI/180));
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      //hamidasi
      if(p_xPos>400-p_radius) p_xPos=400-p_radius;
      if(p_xPos<p_radius) p_xPos=p_radius;
      if(p_yPos>800-p_radius) p_yPos=800-p_radius;
      if(p_yPos<p_radius) p_yPos=p_radius;
      
      dist[num]=sqrt((p_xPos-eb_xPos)*(p_xPos-eb_xPos)+(p_yPos-eb_yPos)*(p_yPos-eb_yPos));
    }


    else if(p_dist < ((float)p_siya_d) ){
      if(x_dist<0 && y_dist <0){
        p_x_ran=p_spd*abs(cos(kakudo*PI/180))*-1*random(-0.75,0.75);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*-1*random(-0.75,0.75);
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      else if(x_dist>0 && y_dist <0){
        p_x_ran=p_spd*abs(cos(kakudo*PI/180))*random(-0.75,0.75);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*-1*random(-0.75,0.75);
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      if(x_dist<0 && y_dist >0){
        p_x_ran=p_spd*abs(cos(kakudo*PI/180))*-1*random(-0.75,0.75);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*random(-0.75,0.75);
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      else if(x_dist>0 && y_dist >0){
        p_x_ran=p_spd*abs(cos(kakudo*PI/180))*random(-0.75,0.75);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*random(-0.75,0.75);
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      //hamidasi
      if(p_xPos>400-p_radius) p_xPos=400-p_radius;
      if(p_xPos<p_radius) p_xPos=p_radius;
      if(p_yPos>800-p_radius) p_yPos=800-p_radius;
      if(p_yPos<p_radius) p_yPos=p_radius;
      
      dist[num]=sqrt((p_xPos-eb_xPos)*(p_xPos-eb_xPos)+(p_yPos-eb_yPos)*(p_yPos-eb_yPos));
      
    }

    else if(p_dist > ((float)p_siya_d) ){
      
            if(x_dist<=0 && y_dist <0){
        p_x_ran=p_spd*(abs(cos(kakudo*PI/180))+random(-5.0,5.0))*-1*random(0,0.4);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*-1*random(0,0.4);
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      else if(x_dist>=0 && y_dist <0){
        p_x_ran=p_spd*(abs(cos(kakudo*PI/180))+random(-5.0,5.0))*random(0,0.4);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*-1*random(0,0.4);
       
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      if(x_dist<=0 && y_dist >0){
        p_x_ran=p_spd*(abs(cos(kakudo*PI/180))+random(-5.0,5.0))*-1*random(0,0.4);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*random(0,0.4);
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      else if(x_dist>=0 && y_dist >0){
        p_x_ran=p_spd*(abs(cos(kakudo*PI/180))+random(-5.0,5.0))*random(0,0.4);
        p_y_ran=p_spd*abs(sin(kakudo*PI/180))*random(0,0.4);
        
        p_xPos+=p_x_ran;
        p_yPos+=p_y_ran;
      }
      
      //hamidasi
      if(p_xPos>400-p_radius) p_xPos=400-p_radius;
      if(p_xPos<p_radius) p_xPos=p_radius;
      if(p_yPos>800-p_radius) p_yPos=800-p_radius;
      if(p_yPos<p_radius) p_yPos=p_radius;
      
    }
    
  }
  
  void p_kick_hantei(int num){
    float min=min(dist);
    if(dist[num]<p_radius && min == dist[num]){
      kicked=true;
      ball.kick(p_angl,p_power,num);    //ball.kick method uses as  ball.kick(ANGLE,POWER);
    }
  }
  
  

  void display(int num){
    float hani =(float) p_siya;
    float siya1 =(float) (frame%p_siya);
    float siya2 =(float) ((frame+1)%p_siya);
    float siya_d=p_siya_d;
    float kakudo=360.0;
    
    
    
    if(num%2 == 0){
      fill(#ff7fff,255);
      ellipse(p_xPos,p_yPos,p_radius,p_radius);
      fill(#ff7fff,63);
      arc( p_xPos, p_yPos, siya_d*2, siya_d*2, radians(kakudo*siya1/hani),radians(kakudo*siya2/hani));
      alpha(0);
    }
    
    else{
      fill(#00bfff,255);
      ellipse(p_xPos,p_yPos,p_radius,p_radius);
      fill(#00bfff,63);
      arc( p_xPos, p_yPos, siya_d*2, siya_d*2, radians(kakudo*siya1/hani),radians(kakudo*siya2/hani));
      alpha(0);
    }
  }
  
  void p_goal_init(int num){
    float x_pos=random(p_radius*2,400-p_radius*2);
    float y_pos=random(p_radius*2,800-p_radius*2);
    
    p_xPos=x_pos;
    p_yPos=y_pos;
  }
}
```

解説として、
①・ボールが視程の範囲内かつ視野のレーダーの中に入っていれば、活発に動き、
②・レーダーの中にボールが入っていないが、視程の範囲内であれば上の条件ほどではないが動く。
1，2にも満たさない場合は、少しずつボールに近づいていく。




ボールクラス

```
class Ball{
  float b_radius;
  float b_xPos;
  float b_yPos;
  
  Ball(){
    b_radius=18.0;
    b_xPos=200;
    b_yPos=400;
  }
  
  void B_initialize(){
    fill(#000000);
    ellipse(200, 400, b_radius, b_radius);
    b_xPos=200; b_yPos=400;
    eb_xPos=200; eb_yPos=400;
  }
  
  void display(){
    fill(#000000);
    ellipse(b_xPos, b_yPos, b_radius, b_radius);
    eb_xPos=b_xPos; eb_yPos=b_yPos;
  }
  
  void kick(float kakudo,float power,int num){
    float p_x_kick=(cos(kakudo*PI/180)+0.2)*power;
    float p_y_kick=(sin(kakudo*PI/180)+0.2)*power;
    
    
    
    if(num%2 ==0){
      b_xPos+=p_x_kick;
      b_yPos+=p_y_kick;
      
      if(b_xPos>400-b_radius) b_xPos=400-b_radius;  //ball is out of vesel (not goal)
      if(b_xPos<b_radius) b_xPos=b_radius;  //ball is out of vesel (not goal)
      
      if(b_yPos>800-b_radius){
        p_point++;
        b_yPos=400;
        b_xPos=200;
        
        for(int i=0; i<players.length; i++){
          players[i].p_goal_init(i);
        }
    }
      if(b_yPos<b_radius){
        b_point++;
        b_yPos=400;
        b_xPos=200;
        
        for(int i=0; i<players.length; i++){
          players[i].p_goal_init(i);
        }
    }
      
      eb_xPos=b_xPos;
      eb_yPos=b_yPos;
    }
    
    else if(num%2 ==1){
      b_xPos-=p_x_kick;
      b_yPos-=p_y_kick;
      
      if(b_xPos>400-b_radius) b_xPos=400-b_radius;  //ball is out of vesel (not goal)
      if(b_xPos<b_radius) b_xPos=b_radius;  //ball is out of vesel (not goal)
      
      if(b_yPos>800-b_radius){
        p_point++;
        b_yPos=400;
        b_xPos=200;
        
        for(int i=0; i<players.length; i++){
          players[i].p_goal_init(i);
        }
    }
      if(b_yPos<b_radius){
        b_point++;
        b_yPos=400;
        b_xPos=200;
        
        for(int i=0; i<players.length; i++){
          players[i].p_goal_init(i);
        }
    }
      
      eb_xPos=b_xPos;
      eb_yPos=b_yPos;
    }
  }
}
```


その他


```
long frame =0;
float eb_xPos;
float eb_yPos;
boolean kicked=false;
int b_point=0;
int p_point=0;

float[] dist;

Player[] players =new Player[22];
Ball ball =new Ball();
 
void setup() {
  size(400, 800);  
  background(255);
  smooth();
  noStroke();
  fill(0);
  frameRate(-60);
  
  dist=new float[players.length];
  
  for (int i = 0; i < players.length; i++) players[i] = new Player();
  for (int j = 0; j < players.length; j++) players[j].P_initialize(j);
  for (int k = 0; k < players.length; k++)dist[k]=9999.9;
  
  ball=new Ball();
  ball.B_initialize();
}
 
void draw() {
  background(255);
  frame++;
  
  
  for (int i = 0; i < players.length; i++) {
    dist[i]=9999.9;
    players[i].update(i);
    players[i].p_kick_hantei(i);
    players[i].display(i);
    
    //circles[i].update();
    //circles[i].display();
  }
  ball.display();
  kicked=false;
  
  fill(0);
  text(frame+"F", 5, 10);
  text(nfs(frameRate, 3, 1)+"FPS", 80, 10);
  text(p_point+"-"+b_point, 160, 10);
}
```
##4.1　変数などの説明


初期宣言
![image](https://qiita-image-store.s3.amazonaws.com/0/139300/5e2f70dd-01cd-2d7a-282c-ba20bc1fa840.png)

Playerクラス
![image](https://qiita-image-store.s3.amazonaws.com/0/139300/3093b31b-e898-2f9e-2f79-d6edff9a6c5f.png)

Ballクラス
![image](https://qiita-image-store.s3.amazonaws.com/0/139300/8ab97697-6676-988e-fb9f-b819ae6e26e8.png)


プレイヤーのプロパティ
![image](https://qiita-image-store.s3.amazonaws.com/0/139300/6a07aa9b-961d-1ec2-0dc9-c053f1875137.png)

Play.updateの変数解説
![image](https://qiita-image-store.s3.amazonaws.com/0/139300/2cd0acbf-caff-b93a-6589-9742408d4b30.png)


##4.2　スナップショット
![image](https://qiita-image-store.s3.amazonaws.com/0/139300/0efea239-0788-6231-6507-479337407a27.png)


#5　改善点
ボールが壁に行きやすい
ボールとプレイヤーがほぼ一直線を描いて、動きにくくなる。

青チームと桃チームの戦力差
↑プレイヤーの人数が少ないときはとりわけ著しくなる。

戦力差はあっても、長時間シミュレーションをしていくと、段々2チームの差は数字に現れなくなる。
![image](https://qiita-image-store.s3.amazonaws.com/0/139300/f7e917d9-cef4-11dc-ce20-0d7398650dec.png)
↑視野の扇形が一部描写されていませんが、更新処理よるもので、実際にはすべて見えています。↑

#6　感想
少し不満は残ったものの、概ね満足できるものができた。

パラグラフ4で説明している、不具合を解決した場合、
コメントで改善箇所などをコメントしていただくと、
大変嬉しゅうございます。

*これは本当に声を大にして「人工知能(AI)」と呼べるのだろうか......*

#7　出典
https://processing.org/
↑Processing.orgのリファレンス↑

詳細記事も書ければ書く予定であります。

#8 omake

![image](https://qiita-image-store.s3.amazonaws.com/0/139300/1ba65ff0-ca1a-ccb6-03f9-1f03c9985e36.png)
33-4よりもひどい・・