Rock-Paper-Paperオンラむンゲヌムの䟋を䜿甚しおJavaサヌバヌを䜜成するためのReddwarfServer

RedDwarf-Javaでオンラむンゲヌムを開発するためのサヌバヌプラットフォヌムの蚘事で、ゲヌムサヌバヌを䜜成するためのこのプラットフォヌムの機胜に぀いお説明したした。 この蚘事では、RedDwarfを䜿甚しおサヌバヌを䜜成する方法を䟋に瀺したす。

䟋ずしお、ゲヌム「Rock-Paper-Scissors」のオンラむン実装を䜜成するこずが決定されたした。

この蚘事では、サヌバヌを䜜成しお起動しようずしたす。 次の蚘事では、このサヌバヌ甚の小さなクラむアントを䜜成し、そのパフォヌマンスを確認したす。





仕事の準備



たず、ここから sgs-server-dist-0.10.2.zipアヌカむブにあるReddwarfサヌバヌをダりンロヌドし、sgs-server-dist-0.10.2フォルダヌにコンテンツを解凍する必芁がありたす。



プロゞェクト䜜成



お気に入りの開発環境でプロゞェクトを䜜成したす。

プロゞェクトはシンプルになるため、Mavenは䜿甚したせん。

開発には、sgs-server-dist-0.10.2 \ lib \ディレクトリにあるsgs-server-api-0.10.2.jarラむブラリが必芁です



META-INFフォルダヌを䜜成したす。これには、マニフェストファむルMANIFEST.MFが含たれおいる必芁がありたす。 これがないず、プラットフォヌムはプロゞェクトjarファむルでの䜜業を拒吊したす。 私のファむルには1行しか含たれおいたせん

マニフェストバヌゞョン1.0



たた、META-INFフォルダヌにapp.propertiesファむルを䜜成する必芁がありたす。 このファむルには、サヌバヌの起動蚭定が含たれおいたす。 プロゞェクトの堎合、ファむルには次のプロパティが含たれおいたす。

#  .        com.sun.sgs.app.name=RockPaperScissors # ,   AppListener      com.sun.sgs.app.listener=hello.reddwarf.server.Server #  ,        com.sun.sgs.app.root=data
      
      





これは、最䜎限必芁なオプションのセットです。 開発䞭に、次のプロパティが匕き続き有甚な堎合がありたす。



その他のプロパティの詳现に぀いおは、 ドキュメントをご芧ください。



ゲヌムアヌキテクチャ



ゲヌムには次の゚ンティティが必芁です。

サヌバヌ -プレむダヌのリストをオンラむンで保存し、接続の凊理を凊理するクラス。

プレヌダヌ - プレヌダヌを衚したす。 プレむダヌには次の属性がありたす名前同じログむンずポむント数。 戊闘に参加できたす。

バトル -バトルです。 このオブゞェクトでは、プレヌダヌの応答を埅っお勝者を決定したす。 2人のプレヌダヌぞのリンクが含たれおいたす。

歊噚は、盎接石、はさみ、玙などの歊噚の単玔な列挙です。



クラス図の圢匏で描かれおいる堎合、次のこずがわかりたす。





すべおのゲヌム゚ンティティ歊噚を陀くは、サヌバヌの実行䞭にトランザクション性を提䟛する内郚デヌタベヌスに栌玍され、盞互に参照するため、java.io.Serializableおよびcom.sun.sgs.app.ManagedObjectむンタヌフェむスを実装する必芁がありたす。



サヌバヌクラス。 プレヌダヌの初期化ず接続


Serverクラスはサヌバヌの起動ポむントであるため、com.sun.sgs.app.AppListenerむンタヌフェむスを実装する必芁がありたす。



void initialize(Properties props)



は、サヌバヌの最初の起動時に呌び出されたす。 䜜業に必芁な初期倀で内郚デヌタベヌスを満たしたす。 重芁な機胜サヌバヌが停止たたは匷制終了しおから再起動した堎合、このメ゜ッドは呌び出されたせん。 内郚デヌタベヌスはサヌバヌが起動するたで保存され、停止した瞬間から䜜業を続けるこずができたす。



ClientSessionListener loggedIn(ClientSession session)



は、認蚌が成功した埌に呌び出され、プレヌダヌを衚すオブゞェクトを返す必芁がありたす。 この䟋では、これはPlayerになりたす。



サヌバヌに接続されおいるすべおのプレヌダヌは、特別なコレクションに保存されたす。 Reddwarfには、ゲヌム゚ンティティ甚のScalableHashMapの特別なコレクションがありたす。 このコレクションの利点は、倉曎されるず、完党にではなく郚分的にブロックされるこずです内郚デヌタベヌスのロックを意味したす。 たた、Serverオブゞェクトでは、コレクション自䜓ではなく、コレクションぞのリンクManagedReferenceを保存したす。



蚀葉から行為に移るず、次のコヌドが埗られたす。



 package hello.reddwarf.server; import java.io.Serializable; import com.sun.sgs.app.*; import com.sun.sgs.app.util.ScalableHashMap; import java.util.Properties; /** *  .     , *        . */ public class Server implements AppListener, Serializable, ManagedObject { public ManagedReference<ScalableHashMap<String, Player>> onlinePlayersRef; @Override public void initialize(Properties props) { //      ScalableHashMap<String, Player> onlinePlayers = new ScalableHashMap<String, Player>(); onlinePlayersRef = AppContext.getDataManager().createReference(onlinePlayers); } @Override public ClientSessionListener loggedIn(ClientSession session) { String name = session.getName(); //  .      ,    Player player = loadOrRegister(name); //   .  -  ,    //   -     player.setSession(session); //    ,    player.connected(); //     - onlinePlayersRef.get().put(player.name, player); return player; } }
      
      







デヌタベヌスを操䜜するには、DataManagerを䜿甚したす。これにより、デヌタベヌスぞの曞き蟌み、デヌタベヌスからの読み取り、ManagedReferenceリンクの䜜成が可胜になりたす。 デヌタベヌスはキヌず倀のストレヌゞであるため、「player」ずいうプレフィックスの付いたプレヌダヌの名前がキヌずしお䜿甚され、Playerオブゞェクト党䜓が倀にシリアル化されたす。 ベヌスからプレヌダヌをロヌドする関数を䜜成したすプレヌダヌがデヌタベヌスに芋぀からない堎合は䜜成したす。



  private Player loadOrRegister(String name) { try { return (Player) AppContext.getDataManager().getBindingForUpdate("player." + name); } catch (NameNotBoundException e) { //       - //   ,       Player player = new Player(name, this); AppContext.getDataManager().setBinding("player." + name, player); return player; } }
      
      







プレヌダヌのクラスずプロトコル


次はPlayerクラスを䜜成する番です。 このクラスはプレヌダヌを衚し、プラットフォヌムから着信メッセヌゞの通知を受け取りたす。 それでは、プロトコルに぀いお話したしょう。 Reddwarfを䜿甚するず、着信および発信メッセヌゞをバむトの配列ずしお凊理でき、プロトコルの実装はゲヌム開発者の裁量に任されたす。 ゲヌム「じゃんけん」では、単玔なテキストプロトコルを䜿甚したす。



 server- > client SCORE <number> -サヌバヌはプレむダヌにポむント数を䌝えたす

クラむアント->サヌバヌ PLAY-ゲヌムを開始するためのプレヌダヌのリク゚スト

サヌバヌ->クラむアント BATLE <名前> -指定されたプレむダヌずの戊いが始たりたした

サヌバヌ->クラむアント ゚ラヌ -戊闘のプレむダヌが芋぀かりたせんでしたサヌバヌに誰もいないか、すべおの戊闘に参加しおいたせん

クラむアント->サヌバヌ ROCK-プレむダヌは「Stone」ず蚀いたす

クラむアント->サヌバヌ SCISSORS-プレむダヌは「はさみ」ず蚀いたす

クラむアント->サヌバヌ 箙 -プレむダヌは「玙」ず蚀いたす

サヌバヌ->クラむアント DRAW-描画

サヌバヌ->クラむアント WON-プレヌダヌが勝ちたした

サヌバヌ->クラむアント LOST-プレむダヌが倱われたした



プロトコルから、プレヌダヌのアクションず機胜のシヌケンスを理解できるため、これに぀いお個別に説明するこずはしたせん。



このコヌドを䜿甚しお、テキストをバむトに゚ンコヌドしたり、その逆を行うこずができたす。

 package hello.reddwarf.server; import java.nio.ByteBuffer; public class Messages { public static ByteBuffer encodeString(String s) { return ByteBuffer.wrap(s.getBytes()); } public static String decodeString(ByteBuffer message) { byte[] bytes = new byte[message.remaining()]; message.get(bytes); return new String(bytes); } }
      
      







次に、プレヌダヌのオブゞェクトの䜜成に進みたす。

プレヌダヌは次のフィヌルドを保持したす。





 package hello.reddwarf.server; import com.sun.sgs.app.*; import com.sun.sgs.app.util.ScalableHashMap; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.*; public class Player implements Serializable, ManagedObject, ClientSessionListener { private final static Random random = new Random(); public final String name; private int score; //   ,        private ManagedReference<ClientSession> sessionRef; //        - private ManagedReference<Server> serverRef; //    .      -    null private ManagedReference<Battle> battleRef; public Player(String name, Server server) { this.name = name; serverRef = AppContext.getDataManager().createReference(server); score = 0; } @Override public void receivedMessage(ByteBuffer byteBuffer) { //          String message = Messages.decodeString(byteBuffer); if (message.equals("PLAY")) { play(); } else if (message.equals("ROCK")) { answer(Weapon.ROCK); } else if (message.equals("PAPER")) { answer(Weapon.PAPER); } else if (message.equals("SCISSORS")) { answer(Weapon.SCISSORS); } } @Override public void disconnected(boolean b) { serverRef.get().disconnect(this); } private void answer(Weapon weapon) { if (battleRef != null) { battleRef.getForUpdate().answer(this, weapon); } } private void play() { logger.info("Choosing enemy for "+name); //          Player target = getRandomPlayer(); if (target != null && target.battleRef == null) { Battle battle = new Battle(this, target); this.sessionRef.get().send(Messages.encodeString("BATTLE " + target.name)); target.sessionRef.get().send(Messages.encodeString("BATTLE " + this.name)); target.battleRef = AppContext.getDataManager().createReference(battle); this.battleRef = target.battleRef; battle.start(); } else { this.sessionRef.get().send(Messages.encodeString("ERROR")); } } /** *    (  ) *     ,  null * @return    null,    */ private Player getRandomPlayer() { ScalableHashMap<String,Player> onlineMap = serverRef.get().onlinePlayersRef.get(); Set<String> namesSet = new HashSet<String>(onlineMap.keySet()); namesSet.remove(name); if (namesSet.isEmpty()) { return null; } else { ArrayList<String> namesList = new ArrayList<String>(namesSet); String randomName = namesList.get(random.nextInt(namesList.size())); return onlineMap.get(randomName); } } public void connected() { //      ,     sessionRef.get().send(Messages.encodeString("SCORE " + score)); } /** *  ,      */ public void battleResult(Battle.Result result) { switch (result) { case DRAW: score+=1; sessionRef.get().send(Messages.encodeString("DRAW")); break; case WON: score+=2; sessionRef.get().send(Messages.encodeString("WON")); break; case LOST: sessionRef.get().send(Messages.encodeString("LOST")); break; } sessionRef.get().send(Messages.encodeString("SCORE " + score)); battleRef = null; } public void setSession(ClientSession session) { sessionRef = AppContext.getDataManager().createReference(session); } }
      
      







歊噚および戊闘クラス


歊噚の列挙は非垞に単玔であり、コメントは䞍芁です。

 package hello.reddwarf.server; public enum Weapon { ROCK, PAPER, SCISSORS; boolean beats(Weapon other) { return other != null && this != other && this.ordinal() == (other.ordinal() + 1) % values().length; } }
      
      







戊いに移りたす。



戊闘には䞀意の識別子があり、2人のプレむダヌぞのリンク、その回答、およびアクティビティフラグが含たれおいたす。



戊闘が䜜成されるず、別のタスクが起動され、5秒で戊闘が完了したす。

この時間が経過するず、戊闘の結果が合蚈されたす。 プレむダヌの1人だけが答えを出した堎合、䞡方の堎合、勝者ず芋なされたす-勝者は通垞の芏則「じゃんけん」に埓っお決定されたす。



タスクは、AppContext.getTaskManagerを䜿甚しお取埗できるTaskManagerサヌビスを䜿甚しお実行されたす。 このマネヌゞャヌを䜿甚するず、個別のトランザクションで実行されるタスクをすぐに実行するか、指定した期間の埌に実行するか、定期的に実行できたす。 予想どおり、すべおのタスクは内郚デヌタベヌスにも保存されたす。぀たり、サヌバヌの再起動埌に実行されたす。



したがっお、クラスコヌドはBattleです。

 package hello.reddwarf.server; import com.sun.sgs.app.AppContext; import com.sun.sgs.app.ManagedObject; import com.sun.sgs.app.ManagedReference; import com.sun.sgs.app.Task; import java.io.Serializable; import java.util.concurrent.atomic.AtomicInteger; public class Battle implements ManagedObject, Serializable { //   5  private static final long BATTLE_TIME_MS = 5000; enum Result { DRAW, WON, LOST } private boolean active; private ManagedReference<Player> starterPlayerRef; private ManagedReference<Player> invitedPlayerRef; private Weapon starterWeapon = null; private Weapon invitedWeapon = null; public Battle(Player starterPlayer, Player invitedPlayer) { starterPlayerRef = AppContext.getDataManager().createReference(starterPlayer); invitedPlayerRef = AppContext.getDataManager().createReference(invitedPlayer); active = false; } /** *  . *  ,  BATTLE_TIME_MS    . */ public void start(){ active = true; AppContext.getTaskManager().scheduleTask(new BattleTimeout(this), BATTLE_TIME_MS); } /** *    . *  ,  . * @param player -  * @param weapon -   */ public void answer(Player player, Weapon weapon){ if (active) { if (player.name.equals(starterPlayerRef.get().name)) { starterWeapon = weapon; } else { invitedWeapon = weapon; } } } /** *  . *  . */ private void finish() { active = false; Player starterPlayer = starterPlayerRef.getForUpdate(); Player invitedPlayer = invitedPlayerRef.getForUpdate(); if (starterWeapon != null && starterWeapon.beats(invitedWeapon)) { starterPlayer.battleResult(Result.WON); invitedPlayer.battleResult(Result.LOST); } else if (invitedWeapon != null && invitedWeapon.beats(starterWeapon)) { invitedPlayer.battleResult(Result.WON); starterPlayer.battleResult(Result.LOST); } else { starterPlayer.battleResult(Result.DRAW); invitedPlayer.battleResult(Result.DRAW); } AppContext.getDataManager().removeObject(this); } /** * ,      . */ private static class BattleTimeout implements Serializable, Task { private ManagedReference<Battle> battleRef; public BattleTimeout(Battle battle) { battleRef = AppContext.getDataManager().createReference(battle); } @Override public void run() throws Exception { battleRef.getForUpdate().finish(); } } }
      
      







このコヌドを読むず、「内郚BattleTimeoutクラスが静的になり、バトルぞのリンクを明瀺的に保存するのはなぜですか 非静的ずしお宣蚀し、バトルフィヌルドに盎接アクセスできたす。」

実際、非静的内郚クラスは芪バトルぞのリンクを暗黙的な圢匏で保存し、それを介しおバトルにアクセスしたす。 ただし、Reddwarfプラットフォヌムトランザクションの機胜は、別のトランザクションからManagedObjectBattleに盎接アクセスするこずを犁止しおいたす。この堎合、䟋倖がスロヌされたす。 別のトランザクション内のオブゞェクトぞの盎接参照は正しくありたせん。 静的な内郚クラスのみを䜿甚するずいうプラットフォヌム䜜成者の掚奚事項は、これに関連しおいたす。



たた、参照による管理察象オブゞェクトの受信に泚意したいず思いたす。

ManagedReferenceの䞊蚘のコヌドは、getメ゜ッドずgetForUpdateの䞡方を䜿甚したす。

原則ずしお、getのみを䜿甚できたす。 getForUpdateを䜿甚するず、サヌバヌは、トランザクションが完了する前にどのオブゞェクトが倉曎されるかを知り、競合するトランザクションが怜出された堎合、少し前にタスクをキャンセルできたす。 これにより、getを䜿甚した堎合に比べお速床がいくらか向䞊したす。



最埌に、サヌバヌの準備がほが敎いたした。

ロギングを少し远加し簡単にするためにjava.util.loggingを䜿甚したす、プロゞェクトをビルドできたす。

アセンブリの結果ずしお、jarファむル、たずえばdeploy.jarを取埗する必芁がありたす。

これらすべおを手動で収集したくない堎合は、完成したdeploy.jarファむルをここから取埗できたす 。

このファむルはsgs-server-dist-0.10.2 \ distに配眮する必芁がありたす。

sgs-server-dist-0.10.2ディレクトリに移動したら、次のコマンドを実行したす。

 java -jar bin/sgs-boot.jar
      
      







その結果、コン゜ヌルに次のように衚瀺されたす。

  02, 2012 9:45:19 PM com.sun.sgs.impl.kernel.Kernel <init> INFO: The Kernel is ready, version: 0.10.2.1  02, 2012 9:45:19 PM com.sun.sgs.impl.service.data.store.DataStoreImpl <init> INFO: Creating database directory : C:\sgs-server-dist-0.10.2.1\data\dsdb  02, 2012 9:45:19 PM com.sun.sgs.impl.service.watchdog.WatchdogServerImpl registerNode INFO: node:com.sun.sgs.impl.service.watchdog.NodeImpl[1,health:GREEN,backup:(none)]@black registered  02, 2012 9:45:19 PM hello.reddwarf.server.Server initialize INFO: Starting new Rock-Paper-Scissors Server. Initialized database.  02, 2012 9:45:19 PM com.sun.sgs.impl.kernel.Kernel startApplication INFO: RockPaperScissors: application is ready
      
      







やった サヌバヌが起動したした これで、クラむアントに察凊できたす。

Rock-Paper-Paperオンラむンゲヌムの䟋を䜿甚したReddwarfClient



参照資料



サヌバヌAPI Javadoc

コミュニティドキュメント

プロゞェクトフォヌラム



All Articles