ロボカップサッカーシミュレーションのエージェント作成

  1. 情報通信工学科4年 07ec024 沖谷 享弘
  2. 指導教員 坂本直志

目次

1 Robocup Soccerについて
1.1 ロボカップサッカーとは
1.2 シミュレーションリーグについて
2 javaでつくるRoboCupサッカー選手プログラム
2.1 本の紹介
2.2 各クラスの説明
2.3 プログラムの説明
3 戦略の改良
3.1 改良前の戦略
3.2 改良戦略の実装
4 評価
5 まとめ
6 付録
7 参考文献

1 Robocup Soccerについて

1.1 ロボカップサッカーとは

ロボカップサッカーは、人間のサッカーの試合と同じく、自分で考えて動く自律移動型ロボットを使った競技会形式で行われる。

競技会は研究成果の実験・発表の場であるとともに、一般の方も先端科学と技術に接することができる交流の場でもある。

小型ロボットリーグ

直径18cm高さ15cmの小型ロボット5台が1チームとなり、6.5mx4.5mのフィールドで試合をするリーグ。 フィールド全体を見渡すカメラ、あるいはロボット搭載カメラからの視覚情報をベースに、ロボット同士がいかにシステマチックなチームプレーを構築していくかが見所である。

中型ロボットリーグ

縦横50cm未満の中型ロボットが6台でチームを構成し18m×12m大のフィールドで試合をするリーグ。試合時間は15分ハーフ。 多くのチームが360度見渡せるカメラを搭載し、自分とボールの位置をすばやく判断して動く。

4足ロボットリーグ

四足のエンターテイメントロボットが4台1チームでサッカーをするリーグ。フィールドの大きさは4m×6m。ロボットの性能が同じであるため、各チームのプログラミングの優劣が勝敗を左右する。

ヒューマノイドリーグ

2002年の世界大会から正式種目となった自律型2足歩行ロボットによるリーグ。 3対3の複数ロボットによる試合形式の競技が行われている。

マイクロロボットリーグ

シチズン・大阪大学共同で開発した超小型ロボット「Eco-Be!」を利用するリーグ。 全ての競技の中で最も機体が小さいのが特徴である。

シミュレーションリーグ

実機を使うことなく,コンピュータ上の仮想フィールドで異なった人工知能プログラミングされた11対11のプレーヤーがサッカーを行うロボカップ最古参リーグ。 ドリブル等の個人技や、ワンツーパスといったチーム戦略の両面において洗練されたプレーを見ることができる。高さのない2次元フィールドでの競技と、ヒューマノイド型ロボットが技を競う3次元フィールドでの競技がある。

1.2 シミュレーションリーグについて

シミュレーションリーグの基本的なルールとしては、フィールドの大きさは縦68m×横105mのフィールド上にチーム11名の選手が参加できる。各選手とボールは、0.1秒を1ステップとする時刻ごとにフィールド上を移動する。選手は毎ステップごとにコマンドを実行することができ、コマンドの実行結果はフィールド上に5分ハーフの計10分間で行われるので各選手ごとに約6000手のコマンドを実行することで、チームの勝利を狙うことになる。

サーバーへの通信方法

シミュレーションリーグは、サーバ・クライアント方式を採用しており、作業を一つのプログラムで処理するのではなく、作業を依頼するクライアントと作業を処理するサーバという二種類のプロセスで分担して処理する。 サッカーシミュレータはサーバとクライアントがUDP/IPで通信するモデルとなっている。

1
図 1: ロボカップサッカーシミュレータの構成

図1のようにロボカップサッカシミュレータはサーバ・クライアントシステムで設計されている。試合を実行するには11体のプレイヤエージェントなどの動作を制御するクライアントプログラムを2チーム分用意し、サーバへ接続する。仮想的なフィールドはサッカーサーバとサッカーモニタとが通信することで視覚化される。 サーバとクライアントプログラムとの通信はUDP/IPによって行われる。通信プロトコルはLISPのS式という形式の文字列を摸したものとなっている。

サッカーシミュレーションの概要

サッカーの試合での試合状態はサッカーサーバではプレイモードという試合状態を表す文字列で示すようになっている。以下に試合開始から試合終了までのプレイモードの流れを示す。

2
図 2: サッカーシミュレーションの流れ

各プレイモードの詳細

before_kick_off→キックオフ前
kick_off→キックオフ
play_on→プレイ中
time_over→試合終了
goal_l_nまたはgoal_r_n→lはleft、rはright、nは背番号でゴールしたチームと背番号を示す
corner_kick→コーナーキック
goal_kick→ゴールキック
kick_in→キックイン
goalie_catch_ball→キーパーがボールをキャッチした時
free_kick→直接フリーキック
indirect_free_kick→間接フリーキック
penalty_kick→ペナルティキック

図2のようなプレイモードという文字列で表された状態によって、試合を進行する。 まず、サッカーサーバーが起動した時点ではプレイモードはbefore_kick_offになる。両チームの選手がフィールドに存在し、サッカーモニター上でキックオフを行うまではプレイモードはbefore_kick_offのままになる。キックオフを開始した場合には、プレイモードがkick_off_lまたはkick_off_rとなる。

次に、最初にボールを蹴る側のプレイヤーがキックオフ後にボールを蹴った時、プレイモードはplay_onになる。ここでプレイモードがplay_on中の試合状況によって図2のように、コーナーキック、ゴールキック、キックイン、フリーキックなどを行う。行った後に、また試合状況はplay_onに戻り、ハーフタイムまたは試合終了になるまでこの流れを繰り返していく。

サーバーからクライアントへのメッセージ

初期メッセージ
“init”

自陣がフィールドのどちら側にあるのか(左か右か)、選手の背番号、試合の現在の状態(キックオフ前など)などの情報が送られてくる。

例・・・(init r 1 before_kick_off)

この情報は、前半・後半の初めと、プレイヤーを起動させた直後にサーバから送られてくる。

体調・視覚・聴覚メッセージ
“see”・・・視覚情報

その視覚情報の時間、オブジェクトの名前(ボール、旗、選手)、そのオブジェクトとの相対距離、オブジェクトの相対的な方向、オブジェクトの相対的な位置の変化、オブジェクトの相対的な方向の変化、オブジェクト(選手)の体の相対方向、オブジェクト(選手)の頭の相対方向が情報として送られてくる。

例・・・(see 0 (goal r)61.6 36)

例のsee 0は、time=0の視覚情報を受信している。(goal r)61.6 36は、右サイドのゴール(goal_r)が61.6m離れたところに36度の位置に見えることを示している。

“sense_body”・・・感覚情報

その感覚情報の時間、視野の広さ、スタミネ、速さ、頭の方向、kickカウント、dashカウント、turnカウント、sayカウント、turn_neckカウントが情報として送られてくる。

“hear”・・・聴覚情報

その聴覚情報の時間、メッセージの送り手の方向、メッセージ内容が情報として送られてくる。

パラメータメッセージ
“server_param”・・・サーバーパラメータ

1ステップごとの選手の速度の減衰率、ダッシュ係数、スタミナ最大値、スタミナの減少境界、スタミナ減少値、リカバリー最小値、ダッシュ実行率減少の境界、ダッシュ実行率増加の境界、ダッシュ実行率の減少値、ダッシュ実行率の増加値が情報として送られてくる。

“player_param”・・・プレイヤーパラメータ

現在は未実装。

“player_type”・・・プレイヤータイプ

ダッシュ係数、 スタミナ回復最大値、 予備スタミナ、 ダッシュ実行率最大値、 ダッシュ実行率最小値が情報として送られてくる。

エラーメッセージ

選手がフィールドに登録されていない時などにエラーメッセージが送られてくる。

クライアントからサーバーへのコマンド
体の回転(turn moment)

選手の向いている方向をMomentに従って変化させる。Momentの範囲は-180〜180度でプラスは時計回り、マイナスは反時計回りに回る。

首の回転(turn_neck Angle)

Angleを選手の首の角度に加える。首の角度は体の角度に対して-90〜90の間でならない。体を回転させるturnコマンドが実行されると首のむいてる方向も変化する。

前進・後進(dash Power)

プレイヤーが向いている方向に向かって、Powerの値だけ速度を増加させる。実際の加速度はこの値に0.006倍をした値になる。Powerの範囲は-100〜100の間になっている。もし、パワーがマイナスの数なら、プレイヤーは後ろ向きにダッシュする。

ボールのキック(kick Power Direction)

ボールが十分近くにあるなら、Directionの方向にPowerの分だけボールを蹴る。実際の加速度の大きさは0.027倍の数値になる。Powerの範囲は0~100、Directionの範囲は-180〜180となっていまる。

瞬間移動(move X Y)

座標(X,Y)にプレイヤーを瞬間移動させる。原点はセンターマークであり、X軸は相手のゴールに向かって伸び、Y軸はX軸に対して右側のタッチラインに向かって伸びている。このコマンドは、キックオフ前のときと、キーパーがボールをキャッチした直後にのみ使用できる。

キャッチ(catch Direction)

Direction方向にあるボールをキャッチしようとする。このコマンドはキーパーだけに許されており、Directionの範囲は-180〜180。キーパーは幅1と長さ2で、方向がDirectionの長方形の中にボールがあるときにキャッチできる。

声を出す(say Message)

Messageを他のプレイヤーに伝えるために声を出す。Messageは英数字で10文字以下の文字列であり、受け取ることのできる最大距離がある。

視野の広さを変更する(change_view ANGLE_WIDTH QUALITY)

視野の視野角度と視覚情報の品質を変えるコマンド。ANGLE_WIDTHはwide(180度) Normal(90度)narrow(45度)のうちいずれか一つを選ぶ。QUALITYはhighかlowのどちらかを選ぶ。highクオリティの場合、サーバから物体の距離・方向の情報を受け取れるが、lowクオリティの場合、物体の距離情報が落ち、方向の情報しか選手は受け取れない。初期値は各選手ともnormalとhighになっている。

そのほかにもタックル、腕を動かす動作、チーム監督・コーチの実装や選手の交代などがある。

2 javaでつくるRoboCupサッカー選手プログラム

2.1本の紹介

本研究では、「JavaでつくるRoboCupサッカー選手プログラム」のプログラムを基に研究に取り組んだ。この本は全部で30章まであり、第5章から第30章にかけてプログラムの説明をしている。 第1章ではロボカップサッカーシミュレーションリーグだけでなく、その他のロボカップサッカーの種目について紹介してある。 第2章ではロボカップサッカーシミュレーション部門での試合ルールなどが説明されている。 第3章では選手の基本的な動作のルールについて説明されている。 第4章ではサッカークライアントを開発、シミュレーションを行うために事前に行っておく作業などが説明されている。

2.2各クラスの説明

このプログラムはPlayerLv00クラスからPlayerLv27まで27個のクラスを00から順に継承しているプログラムである。 以下に簡潔にそれぞれのクラスの機能を書いていく。

PlayerLv00クラス
send()やreceive()などでサーバとの通信を行うことができる
PlayerLv01クラス
実行するサッカー選手を11人で登録できるようになる
PlayerLv02クラス
kick_off時にフォーメーションをとれるようになる
PlayerLv03クラス
審判からの笛を聴き試合状況を把握する
PlayerLv04クラス
選手が体を回転することによりボールを探索できるようになる
PlayerLv05クラス
すべての選手がボールに近づき相手ゴールへボールを蹴る
PlayerLv06クラス
ボールから一番近い選手だけがボールに近づき相手ゴールへボールを蹴る。また、ボールから遠い選手の行動を決めることができる
PlayerLv07クラス
選手の首が向いている方向をラインの角度から判断する
PlayerLv08クラス
選手がフィールド上の自分の座標を計算する
PlayerLv09クラス
ボールの位置情報を視覚情報から計算する。自分の守備位置を計算する
PlayerLv10クラス
計算した守備位置かボールへ移動する
PlayerLv11クラス
過去の視覚情報から現在のフィールド状況を予測する準備をする
PlayerLv12クラス
moveコマンド実行時に、フィールド上の自分の位置を予測する
PlayerLv13クラス
dashコマンド実行時にフィールド上の自分の位置をスタミナがある限り予測をする
PlayerLv14クラス
dashコマンド実行結果をスタミナ減少時にも予測できるように計算するためのパラメータを読み込む
PlayerLv15クラス
dashコマンド実行結果がダッシュ実行効率によって計算され、スタミナが減少したときでも正確な位置予測ができる
PlayerLv16クラス
自分の首と体の方向の予測をコマンド履歴を解析することで予測できるようにする
PlayerLv17クラス
次のステップのボールの位置予測をkickコマンドを解析し予測する
PlayerLv18クラス
自分の位置を計算した上で、視界に見えるボールの位置予測をする
PlayerLv19クラス
フィールド上で首の向きと視界の広さを操作することで、ボールを視界に捕らえ続ける
PlayerLv20クラス
キック、移動、視線の目標を決定できるようになる
PlayerLv21クラス
キック、移動、視線の行動目標座標情報にしたがってキック動作と移動動作および首の回転動作が同時にできるようになる
PlayerLv22クラス
自分の視界に入っているすべての選手の位置・速度を計算し、この結果を使ってボールのトラップ位置に一番近い味方選手は自分であるかどうかを判断できる
PlayerLv23クラス
ボールの将来位置を計算し、ボールをトラップする位置を計算する
PlayerLv24クラス
目標位置を指定時刻に通るキックをすることができる。ボールが体の方向よりどれだけ角度でずれているか、また、ボールと体の表面との距離がどれくらいか、現在のボール速度はいくらかをそれぞれ計算し、適切なパワーと角度を割り出す
PlayerLv25クラス
視覚情報の中のパスができる味方選手の位置を確認した後にパス候補のリストアップとパスの評価を行う
PlayerLv26クラス
トラップメッセージ、キックメッセージ、パス目標メッセージを発信する
PlayerLv27クラス
開発途中

2.3プログラムの説明

改良前プログラムではサーバーからのメッセージを試合が終わるまで受信し続けるrunメソッドを主としてそれぞれのサッカー選手の行動を決めていく。

runメソッド

永久ループを作成し、スレッドの動作を終了させないようにする。このrunメソッドによって繰り返し他のメソッドを実行することによってプレイヤーの行動の決定や様々な予測を行っている。

このrunメソッド内にあるanalyzeMessageメソッドによってサーバーから送られてくるメッセージを解析し、それぞれの行動に分岐させる役割を持っている。

解析するメソッド

analyzeMessageメソッド
サーバから受け取った以下のメッセージを処理する
“init”の場合
analyzeInitialMessageメソッドを実行する
“see”の場合
analyzeVisualMessageメソッドを実行
“sense_body”の場合
analyzePhysicalMessageメソッドを実行する。視覚情報の時刻から現在までの予測をpredictメソッドで作り、予測を基にplayメソッドで行動をする
“hear”の場合
analyzeAuralMessageメソッドを実行
“server_param”の場合
analyzeServerParamメソッドを実行
“player_param”の場合
analyzePlayerParam メソッドを実行
“player_type”の場合
analyzePlayerTypeメソッドを実行

以上のような “init”、“see”、“sense_body”、“hear”、“server_param”、“player_param”、“player_type”がサーバーからメッセージとして送られてきた時に行動を分岐する。

analyzeInitialMessageメソッド・・・初期メッセージを解析する

メッセージからm_strSide(守るゴール)、m_iNumber(背番号)、m_strPlayMode(プレイモード)を切り取って取得。moveコマンドが実行される前に3m間隔で画面上部に並んだとき(選手登録前の状態)の座標を計算する。

analyzeVisualMessageメソッド・・・視覚メッセージを解析する

視覚情報が到着した時刻をm_iVisualTimeに保存する。getLandMarkerメソッドで大文字を解決し、首の方向と体の方向を計算し、自分の位置を計算する。次に視覚情報にボール情報があるか検査し、ボール情報があればパラメータを読み込み、相対座標を求める。自分の絶対座標と相対座標からボールの絶対座標とする。速度を求めるためにDistChangeとDirChangeをgetParamメソッドで受け取り、自分を中心とした極座標系へ変換する。フィールドの座標系上での相対速度に変換し、自分の速度を足してボールの絶対速度を求める。視覚情報到着時にボールを見失ったか検査する。ボールが見えている時は探索カウント(m_iSearchCount)を常に0にする。checkFreshメソッドでボールが見えていないか検査し、ボールが見えていない時は探索カウントを検査し、0まで減っているときはカウントを9とする。自分の絶対座標が正確に計算されているかチェックする(されていない場合はこのメソッドを動作しない)。視界にある選手情報をgetObjectListメソッドで抜き出し、変換してそれぞれリストに入れていく。

getLandMarkerメソッド・・・大文字の視覚情報を解決する
サーバーからのメッセージ
(B)→(b)に変える。
(F)→全ての旗候補の座標との距離をgetDistance()で計算し、一番近いと推測される旗を旗名(name)に変える。
(G)→全てのゴール候補から一番近いと推測されるゴール名(name)に変える。
getParam(String message,String keyword,int number)メソッド
message中から指定したキーワードの後ろにある数値を取り出す。
例・・・2.0や3.0などの数値
checkFreshメソッド

時刻が最近の時刻であるか検査する。最近ではない場合はfalse、最近の場合はtrue。

getObjectListメソッド

キーワードで始まる物体データをメッセージから順に取り出す。キーワード情報(strObject)を抜き出し、getObjectMessage(strObject)メソッドに渡して得た様々な情報を配列に入れていく。

getObjectMessage(String obj)メソッド

物体データのフィールド座標系での情報を計算する。対象のプレイヤーが敵か味方かでチーム文字列を分ける(味方・・・friend、敵・・・enemy)。通常の選手ならば背番号を抜き出し、ゴールキーパーならばgoalieと抜き出す。読み込んだ文字列表記のパラメータを数値に変換する。物体データ(プレイヤーデータ)のフィールドの位置を出した後、速度の計算を行う(極座標系→相対速度→絶対速度)。物体データ(プレイヤーデータ)の体と首の角度を計算する。全ての計算結果を文字列であらわす。

例・・・ (team friend) (number 6) (x -10) (y 5) (vx 1) (vy -2) (body 30) (neck 0)
味方 6番 X Y 速度X 速度Y
analyzePhysicalMessageメソッド・・・体調メッセージを解析する

sense_bodyの後ろにある情報をm_iTimeに入れる。ハーフタイムごとにスタミナ系の計算パラメータを全回復させる。getParam(speed)メソッドでパラメータの取得と速度の大きさの計算をし、X方向、Y方向の速度成分の計算をして地面に対するスピードの計算を行う。getParam(head_angle)メソッドで体を基準とした方向メッセージを解析し、体の方向の計算を行う。

analyzeAuralMessageメソッド・・・聴覚メッセージを解析する

メッセージからstrSpeaker(発言者)、strContent(内容)を切り取って取得。もしレフェリーが笛を鳴らした場合、プレイモードの内容を解析。味方プレイヤーの声を抜き取り解析する。トラップ成功の聴覚メッセージanalyzeTrapMessageメソッドとパス地点を示す聴覚メッセージanalyzePassMessageメソッドで解析する。

analyzeTrapMessageメソッド・・・トラップ成功の聴覚メッセージを解析する

トラップしていない時は何も返さない。視界にいる全ての選手の中でトラップ中の味方にパスが可能かどうかcheckPassableメソッドで検査する。trueの場合sayPassMessageメソッドでパスのための座標を発信する。

checkPassableメソッド・・・パスが可能かを検査する

トラップ中の味方から自分へのパス価値の情報を集め、パスを出す選手位置の価値を計算する。パスの価値が現在のボールの位置の価値より大きいならばtrue、小さいならfalseを返す。

sayPassMessageメソッド・・・パス目標メッセージを発信する

パスする目標座標をsendメソッドでサーバーに送る。

analyzePassMessageメソッド・・・パス地点を示す聴覚メッセージを解析する

“p”から始まる場合・・・パス地点を配列に格納する。

analyzeServerParamメソッド・・・サーバパラメータを解析する

メッセージをm_strServerParamに代入する。サーバーパラメータ内でこのクラスの計算に必要なもの{player_decay(減衰率),dash_power_rate(ダッシュ係数)}を得る。stamina_max(スタミナ最大値),recover_dec_thr(スタミナの減少境界),recover_dec(スタミナ減少値),recover_min(リカバリー最小値),effort_dec_thr(ダッシュ実行率減少の境界),effort_inc_thr(ダッシュ実行率増加の境界),effort_dec(ダッシュ実行率の減少値),effort_inc(ダッシュ実行率の増加値)の取り出し。getParam(maxneckang)メソッドで首が時計方向へ回る最大値の取り出し、getParam(minneckang)メソッドで首が反時計方向へ回る最小値の取り出しを行う。getParam(ball_decay)メソッドでボールの1ステップあたりの速度減衰率、getParam(player_decay)メソッドでプレイヤー速度のステップごとの減衰率、getParam(ball_size)メソッドでボールの大きさ、getParam(kick_power_rate)メソッドでキックパワーが加速度に変更されるときの係数を取得する。

analyzePlayerParamメソッド・・・プレイヤーパラメータを解析する

現在は空であり、開発途中である。

analyzePlayerTypeメソッド・・・プレイタータイプを解析する

id(背番号)の後ろにある情報をそれぞれのプレイヤータイプ配列に入れていく。dash_power_rate(ダッシュ係数),stamina_inc_max(スタミナ回復最大値),extra_stamina(予備スタミナ),effort_max(ダッシュ実行率最大値),effort_min(ダッシュ実行率最小値)の取り出しを行う。getParam(inertia_moment)メソッドで回転モーメントの取り出しを行う。getParam(player_size)メソッドでプレイヤーの大きさ、getParam(kickable_margin)メソッドでキック可能距離の取り出しを行う。getParam(player_speed_max)メソッドでplayer_speed_maxの取り出しを行う。

今回のドリブルの実装には“sense_body”で始まるメッセージがサーバから送られてきたときのplayメソッドで実行する。まず、playを行う前に予測を行うpredictメソッドについて以下に説明する。

predictメソッド・・・フィールドの予測を作る

predictMoveCommandメソッド,predictDashCommandメソッド,predictTurnCommandメソッド,predictKickCommandメソッドを実行する。それぞれMoveコマンド、Dashコマンド、Turnコマンド、Kickコマンドの解析を行う その解析結果によって予測を作る。

その後、playメソッドで様々な状況での選手の行動を行うようにしている。playメソッドについては以下に説明する。

Playメソッド・・・行動を決定する

checkInitialModeメソッドで初期フォーメーションに移動するか検査し、キックオフ前やゴール直後に移動するようにする。通常の動作では、ボールが視野に入っているかcheckFreshメソッドで検査し、入っている場合はplayWithBallメソッドを行う。入っていない場合は、searchBallメソッドを行う。

checkInitialModeメソッド
初期フォーメーションを取るかどうか検査(T or F)
before_kick_off,goal_l,goal_rの場合・・・true
他・・・false
searchBallメソッド・・・ボールを探す

カウント(m_iSearchCount)が9の場合に首を右に、6の場合に首を左に、3の場合に後ろを向く行動をとる。

Playメソッドで行っている大まかな本プログラムの選手行動は
checkInitialModeメソッドがtrue(試合状況がキックオフ前など)の際に、初期フォーメーションを取る。
次にcheckFreshメソッドで時刻が最近の時刻でボールが視界にあるかを検査する。
checkFreshメソッドがtrue(視界に入っている時)の時はplayWithBallメソッドを行う。
playWithBallメソッドでは、checkKickableメソッドでキック可能か検査する。
checkKickableメソッドがtrue(キック可能)の時はgetPassValueメソッドでパス価値を決定し、パスする選手を setKickTargetメソッドでキックする選手を決め、 checkKeepableメソッドを行う。
checkKeepableメソッドがtrue(キープ可能)の時はキープをし続ける。
checkKeepableメソッドがfalse(キープ不可能)の時はパスを行う。
checkKickableメソッドがfalse(キック不可能)の時は setMoveTargetメソッドで選手が動く位置を設定し移動する。
checkFreshメソッドがfalseの時(視界に入っていない時)はsearchBallメソッドでボールを探索する。

この本プログラムの選手の試合を見た評価としては、選手がボールを持った際に相手選手が来るまでどのような状況でもキープをし続ける。ボールをキープしている選手の特定の範囲内に相手が入ってきたらパスを行うという行動をしている。この相手選手が来るまでどのような状況でもキープし続ける行動を変えることで、もう少しいい試合運びができると感じた。

3 戦略の改良

3.1 改良前の戦略

2章で述べた通り改良前プログラムでは主にボールを持っている選手はパス価値に添って最適だと判断された選手に向かってパスを行う。そして、パスを受けた選手は相手選手が決められた範囲内に近づくまでキープし続けるといったものである。主にパスが主体となっており、どんな状況でもパスかシュートしか行わず相手ゴールに向かう戦略としては不十分であった。例えば、図3では、黄色の9番の選手がボールを持っている。9番にとってはパス相手がいなく、相手選手との間に空間がある。この時、相手選手が近づいてくるまでボールをキープし近づいてきたらパスをするといった行動をする。

3
図 3

ここで、このような状況になった時、パスをするまでそこにキープし続けるのではなく、ドリブルを行い相手を抜いたりパスをするタイミングをずらすことで改良する前のプログラムより相手ゴールに近づく可能性が増える。図4と図5のように、9番の選手が相手選手との間にある空間に向かってドリブルを行い、味方の8番の選手のパス価値をあげることによって相手ゴールへと近づくことができたらベストである。

4
図 4
5
図 5

このような行動を起こす回数を増やせることによってシュートをする回数が増え、より相手のプログラムに勝つ確率が上がると考えた。

3.2 改良戦略の実装

まず、ドリブルを実装するためにどういった試合状況でドリブルするかを考えた。ここで、元のプログラムにある選手がボールを持った時、他の味方選手に対してのパスの価値を計算するメソッドがある。このパスの価値を計算するメソッドの内容が以下の通りである。

ドリブルを行う際に関係するメソッドの説明

getPassValueメソッド・・・パスの価値を計算する

ボールの移動先の座標と相手ゴールの座標の距離を計算し、この距離が大きいほど価値を低くする。パスの途中のコースに相手選手がいたら価値を0にする。

主にこのgetPassValueメソッドで決められる選手の価値の範囲は0~100であり、100に一番近い選手が一番パスをするのに最適であることを意味する。元のプログラムではパス価値の計算結果からそれぞれの行動を行う。パス価値が0~100の中で100に近い選手を1人だけ選びその選手にパスを行い、0の場合(価値を計算した選手全員が0)、相手ゴールに向かって蹴る動作を行う。

今回のプログラムの改良で、このパス価値を利用し、パス価値が低い場合の時にドリブルを行うということを実装した。

checkKickableメソッド・・・キックできるか検査する

getDistanceメソッドでボールとの距離を計り、キック可能距離よりもボールとの距離が大きかった場合はfalseを、小さかった場合はtrueを返す。

getDistance(double x0,double y0,double x1,double y1)メソッド

x0,y0とx1,y1との距離を計算する。

改良・実装したメソッドの説明

playWithBallメソッド

まず、このメソッドではmaxgetValueメソッドを呼び出し、最大となる価値を求める。そして、その求めた最大価値を使いキック可能な時とキック不可能な時の行動を分岐した。キック可能な時(checkKickableがtrueの時)にパス価値が一定の値(本プログラムでは20と設定)以下の時にドリブルを行うということを実装した。また、ドリブルをある目標地点まで行うことにするとシュートを打てる場面でもシュートを打たないので、相手ゴールエリア内にボールを持っている選手が入ったらシュートを行うためにシュートメソッドも実装した。シュート・ドリブル行動を行わないとき(2つの行動の条件に当てはまらない時)はパス価値に沿ってパスを行うようにした。

maxgetValueメソッド

ボールを持っている選手の視界に見える自分から5~45mの範囲内の全味方選手の中でパス価値をgetPassValueメソッドで求めていく。その求めた味方選手のそれぞれの価値を比べて一番大きい値になった選手のパス価値を求める。

dribleメソッド

今回実装したdribleメソッドはまず、パス価値が低いときのドリブルする位置を決める。今回はそのドリブルする位置を相手ゴールライン上のX座標と相手ゴール付近の2つのコーナーフラッグの間をY座標として設定した。そして、その2つの座標とボールを持っている選手との距離を計算し、距離が短いほう(座標が近いほう)に向かってドリブルをするように実装した。

shootメソッド

このメソッドではそれぞれの守るゴールに対しての相手ゴール位置を設定する。その設定した相手ゴール位置に向かって一番強いシュート(kick 100)を行う。

4 評価

本プログラムで試合を行ったところ次のような局面が現れた。図から見てわかるが、ボールを持っている6番の選手以外で今相手ゴールに近い味方選手(以下黄色とする)は7番、8番、10番である。ここで図6から、黄色7番はボールを持っている選手に近すぎており、相手の10番(以下青色とする)も黄色7番に近いためパス価値がないと判断できる。また、黄色8番の選手も間に青色10番がいるためパス価値がないと判断でき、黄色10番も青色8番が近くにいるためパス価値がないと判断できる。

6
攻めるゴール→→→
図 6

この場面で実装したドリブルが機能するので図7と図8のように、黄色6番は青色9番の選手が近づくまでドリブルを行う。ここで、図8を見てわかるが図6の時と違って黄色6番の選手がドリブルをしたことによって黄色8番の選手がパスをする選手の候補としてあがったことがわかる。

7
図 7
8
図 8

結果的に図9、図10のように黄色6番の選手は味方選手の中でパス価値を計算し、一番高いパス価値になった黄色8番の選手にパスをした。黄色6番がドリブルを行い、ドリブルを行う前より違った状況にすることで新たにパスをする候補を見つけることができた。そして、8番の選手がフリーでボールを持つことができ、より相手ゴールに近づいていくチャンスが生まれた。

9
図 9
10
図 10

次に、改良したプログラムと改良前のプログラムとの試合結果を表で記す。結果は、左が改良前プログラムの得点であり右が改良前プログラムの得点である。ドリブルは、その試合でドリブルを行うことにより相手選手を抜いたり、相手ゴールに近づけた時の回数である。今回は20試合行った。

表1.試合結果
試合 結果 ドリブル 試合 結果 ドリブル
1 2-2 8回 11 0-2 5回
2 1-0 6回 12 1-1 6回
3 2-3 4回 13 2-3 7回
4 1-0 6回 14 2-2 5回
5 2-0 5回 15 1-2 5回
6 1-0 6回 16 2-1 10回
7 1-0 7回 17 1-1 8回
8 2-2 9回 18 2-1 8回
9 2-2 7回 19 1-2 4回
10 3-1 8回 20 1-0 6回

結果としては、改良したプログラムは9勝5敗6引き分けとなった。表1から勝った試合や引き分けの試合の時に、ドリブルが有効になった回数が多いことがわかる。また、勝ち負けだけを判断すると改良前のプログラムより良くなっていることがわかる。

5 まとめ

改良したプログラムでは改良する前のプログラムより前に向かうプレーが多くなり得点を決めるチャンスも多くなった。実際に行った試合結果で、得点を決めた所では、ドリブルからシュートをして得点を決めたり、ドリブルシュートから繋がった得点などもあった。また、シュートの判断をする範囲を改良する前のプログラムより広くしたことも得点に繋がっていると思う。

しかし、今の改良したプログラムでは、パス価値が0の時に相手ゴール付近のある点に向かってドリブルをするので、ドリブルする道上に相手がいる場合すぐにボールをとられてしまうことが多い。表1の試合結果の中で負けた試合では、無理なドリブルを行い相手選手にボールを取られてしまい、得点を決められてしまう場面が多かった。また、基本的には改良前のプログラムと同じなので、ドリブル以外では行動が同じであり、パスの具合によってもドリブルができるかできないかが分かれていた。

ドリブルをする際に、相手の位置をドリブルする選手が把握して一番最適だと思われるドリブルをすることや、相手の次に移動する位置を予測しながら相手にカットされない位置までドリブルをしてその後のプレイに繋ぐことによってもっと良いドリブルを行うことができると思った。

6 付録

playWithBallメソッド

01: protected void playWithBall() {
02: int t = m_iTime;
03: double a = 0.0;
04: a =maxgetValue(a);
05: if (checkKickable()) {
06:    if(m_strSide.startsWith("r") && m_dBallX[t] < -30.0 && (m_dBallY[t] > -20.0 && m_dBallY[t] < 20.0) || m_strSide.startsWith("l") && m_dBallX[t] > 30.0 && (m_dBallY[t] > -20.0 && m_dBallY[t] < 20.0)){
07:         shoot();
08:     }else if(a < 20.0 && m_strPlayMode.startsWith("play_on") && m_iNumber != 1 && m_iNumber != 2 && m_iNumber != 3 && m_iNumber != 4 && m_iNumber != 5){
09:         drible();
10:     }else{
11:         setKickTarget();
12:         kick();
13:     }
14:
15: }
16: else {
17:     setMoveTarget();
18:     move();
19:     }
20: predict(t, t + 1);
21: setFaceTarget();
22: lookAt(m_dFaceX[t], m_dFaceY[t]);
23:}

maxgetValueメソッド

01:protected double maxgetValue(double a) {
02: int t = m_iTime;
03: int i;
04: for (i = 0; i < m_listPlayer.size(); i++) {
05:     String player1 = m_listPlayer.get(i).toString();
06:     double friendDir = OUT_OF_RANGE;
07:     double friendDist = OUT_OF_RANGE;
08:     double max_score = 0.0;
09:     if (player1.indexOf("friend") > -1) {
10:         double friendX = getParam(player1, "x", 1);
11:         double friendY = getParam(player1, "y", 1);
12:         friendDir = getDirection(m_dX[t], m_dY[t], friendX, friendY);
13:         friendDist = getDistance(m_dX[t], m_dY[t], friendX, friendY);
14:         if (friendDist < 5.0 || friendDist > 45.0)
15:             friendDist = friendDir = OUT_OF_RANGE;
16:         double score = getPassValue(m_dX[t], m_dY[t], friendX, friendY);
17:         if (score > max_score) {
18:             max_score = score;
19:             a = max_score;
20:         }
21:     }
22: }
23: return a;
24:}

dribleメソッド

01:protected void drible(){
02: int j;
03: int t = m_iTime;
04: double goalX,goalY,driX = 0.0,driY = 0.0;
05: if(m_strSide.startsWith("r")){
06:     double dist = getDistance(m_dBallX[t], m_dBallY[t], -52.5, 20.0);
07:     double dist1 = getDistance(m_dBallX[t], m_dBallY[t], -52.5, -20.0);
08:     if(dist > dist1){
09:         driX = -52.5;driY = -20.0;
10:     }else{
11:         driX = -52.5;driY = 20.0;
12:     }
13:         goalX = -52.5;goalY = 0.0;
14: }else{
15:     double dist = getDistance(m_dBallX[t], m_dBallY[t], 52.5, 20.0);
16:     double dist1 = getDistance(m_dBallX[t], m_dBallY[t], 52.5, -20.0);
17:     if(dist > dist1){
18:         driX = 52.5;driY = -20.0;
19:     }else{
20:         driX = 52.5;driY = 20.0;
21:     }
22:     goalX = 52.5;goalY = 0.0;
23: }
24: lookAt(goalX,goalY);
25: DecimalFormat f = new DecimalFormat("###0.00");
26: double kickAX = driX - m_dBallX[t] - m_dBallVX[t];
27: double kickAY = driY - m_dBallY[t] - m_dBallVY[t];
28: double rad = Math.atan2(kickAY, kickAX);
29: double kick_dir = normalizeAngle(Math.toDegrees(rad) - m_dBody[t]);
30: m_strCommand[t] =
31:      "(kick 10 "+ f.format(kick_dir) + ")";
32:}

shootメソッド

01:protected void shoot(){
02: int t = m_iTime;
03: double goalX,goalY;
04: if(m_strSide.startsWith("r")){
05:     goalX = -52.5;goalY = 5.0;
06: }else{
07:     goalX = 52.5;goalY = 5.0;
08: }
09: if(m_strPlayMode.startsWith("play_on")){
10:     DecimalFormat f = new DecimalFormat("###0.00");
11:     double kickAX = goalX - m_dBallX[t] - m_dBallVX[t];
12:     double kickAY = goalY - m_dBallY[t] - m_dBallVY[t];
13:     double rad = Math.atan2(kickAY, kickAX);
14:     double kick_dir = normalizeAngle(Math.toDegrees(rad) - m_dBody[t]);
15:     m_strCommand[t] =
16:         "(kick 100 "+ f.format(kick_dir) + ")";
17: }
18:}

7 参考文献

  1. [1] javaでつくるRoboCupサッカー選手プログラム 大島 真樹 森北出版(2005)
  2. [2] RoboCupではじめるエージェントプログラミング 高橋友一 伊藤暢浩 共立出版(2001)
  3. [3] ロボカップサッカー/ロボカップ日本委員会
  4. http://www.robocup.or.jp/soccer.html
  5. [4] サッカーシミュレーションwiki
  6. http://rctools.sourceforge.jp/pukiwiki/
  7. [4] RoboCup Soccerにおける単純認識・判断に基づくエージェントの作成と評価  新島宇裕