しふぁいしゃ。

風邪でダウンしたものの、寝過ぎてしまいサッパリ眠れないので、CodeRulerを開始。

IBMの記事は入門としては悪く無いものの、真面目にコードを組もうと思ってもあれじゃあ辛い。

と言う訳で、入門の一歩先を記事にしてみたいと思ったり。
基本的に、CodeRulerの開発環境は作成済みで、サンプルコードを一度は動かしてみた人向け。

CodeRulerはパフォチューゲー。

あんまりにもサラっと書いてあるから、見逃しがちだけど、
CodeRulerはパフォーマンスチューニングゲーム。お忘れなきよう。

  1. 初期化1000ms
  2. ターン行動500ms
で収めないと、自分の処理が途中で落とされます。

長いと言えば長いし、短いと言えば短い。外部に対するI/Oを行う暇は無い。

特に何も考えず、全ての触れるリソースにfor文で頭からなめると、
後半処理時間が足りなくなる為、サボる自軍のコマを眺めることが出来るですよ。

処理時間だけのチューニングになるので、メモリをガシガシ使うのはオッケー。
メモリ確保にかかる時間が気になるけれどネ。
FullGCの時には、タイマースレッドも止まる筈なので、GCのコストは余り計算しなくて良い感じ。

動作を理解出来ないとゲームにならないAPI

まぁ記事にもボチボチ書いてあるので、おさらいする必要があるのか微妙だけど…。
  • Ruler
    • abstract String getRulerName()
    • abstract String getSchoolName()
    • IKnight[] getKnights()
    • IPeasant[] getPeasants()
    • capture(IKnight knight, int direction)
    • void move(IKnight knight, int direction)
    • void move(IPeasant peasant, int direction)
    • abstract void orderSubjects(int lastMoveTime)

Rulerはゲームに参加する為に実装するMyRulerの継承親です。
残念な事にRulerを継承していないMyRulerはゲームに参加させてもらえません。
状態を表すinterfaceをorderSubjectsに引き渡してくれりゃぁイイのに…。

getRulerNameとgetSchoolNameは、画面内で自分の色が何色で、勝ってるのか負けてるのかを確認する為に必要です。
ここで、どんなリテラルをメソッドの戻り値に記述したのかお忘れ無きよう。

getKnightsとgetPeasantsは、それぞれ自軍の騎士と農民全体の参照を引き渡してくれるです。
これで、取得した参照を次の、captureやmoveに引き渡す事でゲームが進んでいきます。
内部では、シミュレータが抱える参照のうち、呼出元のMyRulerが保持している事になっている参照を逐次的にチェックしながら
新しい配列に格納して返すので、何度も呼ぶのはパフォーマンス的に良く無いです。

captureとmoveで指定する1番目の引数は、移動する向きを決めたい参照を引き渡すですよ。
2番目の引数は、移動する方角。記事にもあるけど、
Ruler.MOVE_N(1)からRuler.MOVE_NW(8)の範囲で渡すと、その方角にシミュレータが移動させてくれるです。

シミュレータはプレイヤーにターンがまわって来た事をorderSubjectsの呼出によって通知するです。
引数に入ってくるintは、前回の処理にかかった時間なので無視して良いと思うですよ。
このメソッドの処理を500ms以内に終わらせなければ、シミュレータが強制的に処理を止めてくるですよ。

<

動作を理解するとゲームが面白くなるAPI

こっちもかなり記事とネタが被ってるけど、思いつくまま書く感じで。
  • World
  • static ICastle[] getOtherCastles()
  • static IKnight[] getOtherKnights()
  • static IPeasant[] getOtherPeasants()
  • static Point getPositionAfterMove(int x, int y, int direction)

Worldにはstaticメソッドがゴッソリと詰まってるです。
getOther〜メソッドは、敵の参照が取れるです。
後述するattackToメソッドと組み合わせれば、騎士を敵に突撃させられます。

getPositionAfterMoveは、(x,y)座標にいる参照を、directionの方角に移動させると、
画面上のどの位置に移動する事になるかを計算してくれるです。
ポイントは、nullが返って来たとき、その方角はゲーム盤面の外だっつう事だす。
そっちに向かって移動する事は出来ません。転進しませぅ。

<

ウソとマコト。

staticイニシャライザ(初期化ブロック)とコンストラクタは定義しても良い。筈…。
少なくともシミュレータによってその動作を停止させられたりはしない。
禁止の理由が不明。確かにイニシャライザ内で例外が発生するとエライ面倒な事になるけど、
シミュレータが止まるような事をしなければ、エエんでないかい?
逆に、MyRuler(戦術を記述したクラス)のインスタンスが作れないから、デフォルトコンストラクタは必ず定義する事、
って言うなら分るんだけどなぁ…。
引数のあるコンストラクタは、確かに定義しても意味は無いネ。シミュレータから呼び出せないし。

スレッドも立てられる。そもそも、スレッド禁止の理由が良く分らないナリヨ。
まぁ、ゲームがデッドロックで先に進まなくなったりする可能性があるからなのかなぁ…。

コマの移動以外の処理、特に周りの情報を集めて状況を整理する為に、
スレッド立てたって良いと思うんだけどな…。ダメかな。

外部リソースへのI/Oは、まぁ確かに違法だけれどもコストがかかり過ぎるので、ゲーム的にNGかな。
スレッドを立ててそいつが外部リソースとやりとりするとなると話が変わってくるねぇ…。
処理を設定ファイルに落とし込む程度の事をするなら、ハードコーディングしてしまった方が良いです、ハイ。

GUIコンポーネントの起動はNGらしいんだけど、起動してどうするんやろか…。
マップ広げるとか?まぁ邪魔なので無しかな。

確かにメンバ変数へのリフレクションは使えないようになっとるようやね。
まぁ、勝つ為にシミュレータをハックしたりすんのはヤメようよ。ゲームなんだし。
参加者全員でシミュレータをハックしあうなら話は別かな。
そっちの方がエキサイトしそうな気も…。でも、ゲーム内でコマが全く動いてないのに、
得点だけバカみたいに撥ね上がったり、自キャラが全部死んでるのに、
土地の保有数だけがやたら増えたりしたら、「ある意味」面白いかも。

<

基準。

くだんの記事の中でキチンと説明していないけど、どんなコードを書くにせよ基準となるような情報が必要だと思うですよ。
ちょっと思いつく範囲でまとめてみたり。

  • 他のコマがある方向には移動しない。隣あっていても、それぞれ違う方向に移動するなら無問題。
  • 横72マス x 縦64マスより外側に向かって移動する事は出来ない。
  • 騎士は、捕獲/移動のどちらか一方しか1つのターンの間に行う事が出来ない。
  • 特定の自分のキャラの周りに居る敵を認識する為に、情報収集するのはターン行動に含まれない(処理コストは当然かかる)
  • 幾つかの対象の中で、最も近いものを選ぶのは以外と簡単。
  • デフォルトパッケージのMyRulerクラスをシミュレータは動かそうとするので、これを消すとゲーム出来ない。
サンプルコード

サックリとまぁコードを書いてみたですよ。参考にしてくれると嬉しいです、ハイ。
searchEnemyや、getClosestは、もっと効率の良いコードが書けるような気もするんだけど、
アルゴリズムと数学が空っきしなので逐次的にやってるです。
もっと早いコードが書けるって方は、是非教えて下さい。よろしくお願いします。

<
  /**
   * 騎士が対象に向かって突撃する。
   * 周りに捕獲出来る敵がいたら捕獲する。居なければ突撃対象に一歩近づく
   * @param myknight 突撃する騎士
   * @param enemy 突撃対象となる敵
   */
  public final void attackTo(IKnight myknight,IObject enemy) {
    int dir = searchEnemy(myknight);
    if(0 < dir) {
      super.capture(myknight,dir);
      return ; // 捕獲、移動はどちらか一方しか出来ない為、処理を終了する。
    }
    if(enemy != null) {
      super.move(myknight,myknight.getDirectionTo(enemy.getX(),enemy.getY()));
    }
  }
  
  /**
   * 引数に渡した参照の周りをチェックし敵がいる場合、その方向を返す。
   * 周りに敵のオブジェクトが存在しない場合、-1を返す。
   * @param mine チェック対象となる参照
   * @return 敵の居る方向。‐1なら、付近に敵は居ない。
   */
  public static final int searchEnemy(IObject mine) {
    for (int i = Ruler.MOVE_N; i <= Ruler.MOVE_NW; i++) {
      Point np = World.getPositionAfterMove(mine.getX(), mine.getY(), i);
      if (np != null) {
        IObject capture = World.getObjectAt(np.x, np.y);
        if (capture != null && !capture.getRuler().equals(mine.getRuler()))
          return i;
      }
    }
    return -1;
  }
  
  /**
   * fromに渡した対象とtargetsに渡した対象を逐次的に距離計算し、
   * targetsの中で、fromに最も近い位置に参照を返す。
   * @param from 距離計算の基準となる参照
   * @param targets 距離計算の対象となる参照の集合
   * @return fromから最も近い位置にいる参照
   */
  public static final IObject getClosest(IObject from, IObject[] targets) {
    if (from == null || targets == null || targets.length == 0)
      return null;
    IObject closest = null;
    int dist = 1000; // これより遠いのは無視。
    for (int i = 0; i < targets.length; i++) {
      int dist2 = from.getDistanceTo(targets[i].getX(), targets[i].getY());
      if (closest == null || dist2 < dist) {
        closest = targets[i];
        dist = dist2;
      }
    }
    return closest;
  }

更に、個人的な考察。

僕が、ちょいと触ってて感じた事を、まとめてみるですよ。
  • 農民は一定以上のコマを動かしても余りコスト対効果が良くない。
  • 土地はポイントの為では無く、戦力を増強するまでの時間を短縮する為に確保するもの。
  • 最序盤(保有する土地数が124以下)で、農民を全部狩られると、
    城があってもコマを生産出来なくなるので序盤の土地確保はかなり重要。
  • 騎士は勝てば勝つ程Streangthが上がる為、死にずらくなる。
    よって、敵を大量に捕獲した後の騎士はそうでない騎士よりも強い。(かもしれない…微妙)
  • 的にするなら、農民か城が良い感じ。どちらも特に苦労せず狩れる為。
    特に、農民を中心に敵を狩るのは戦術の基本。
  • 戦術を組み立てる際には、少なくとも農民用と騎士用は分けて考える方が良い。
    動作アルゴリズムが一緒になる事はあり得ない。
  • 効率の良い城用戦術を実装するのが一番難しいかも…。

みんなで遊ぶ為に。

サーバを立てると、トーナメントが出来たりするです。ハイ。
トーナメントの処理自体は自動で終了する感じ。試合のリプレイは、.datファイルと言うログが残るので後で見れるです。

サーバ関連は、付属してるドキュメントが英語な上に間違ってるから、
ハマりそうな気もするのでコマンドを書いておくですよ。
でも、僕も1人でしか遊んだ事が無いので、トーナメントが通信対戦がどういう風に挙動するか、
実はよく分ってないです。ハイ。

<
@REM コマンドの概要
@REM -〜以後のオプションの説明
javaコマンド

コマンドの構成はこんな感じ。
1行目に3行目のコマンドを実行すると何が起きるかが書いてあるです。
2行目は、-〜(-serverとか)より後に付けるオプションの説明が半角スペース区切りでかいてあるです。
3行目は、実行コマンド。CodeRulerをインストールしたディレクトリに、games.jarはあるです。
同じディレクトリ内にCodeRuler.jarとか、CodeRally.jarとかあるから分る筈。

他にもあったり、間違ってたりするかもしれないので注意。
特に、間違ってたら教えて欲しいです。ハイ。んで、↓がコマンド一覧。

<
@REM コードの受付用サーバの起動
@REM 受付たファイルを格納するディレクトリパス 受付ポート番号
@java -Dgame=CodeRuler -jar games.jar -server rulerserver 1234

@REM トーナメントの開始
@REM トーナメントを行うディレクトリパス トーナメント名 試合数
@java -Dgame=CodeRuler -jar games.jar -tournament rulerserver "トーナメント名" 8

@REM リプレイの開始
@REM トーナメントを行ったディレクトリパス(〜.datがある)
@java -Dgame=CodeRuler -jar games.jar -playback rulerserver

@REM デモモード(受付用サーバに登録されているコードがテキトーに動く)
@java -Dgame=CodeRuler -jar games.jar -spectate rulerserver spectate
幾つかテストモードもあるみたいなんだけど、全然動かないので注意。

最後に。

音がそれなりに鳴る筈なんだけど、僕の環境ではウンともスンとも言いません。
「音が鳴った」と言う方、どうやったのか、教えて下さい。よろしくお願いします。