package j7; // 「海ゲーム」サーバプログラムSeaGameServer.java // このプログラムは,海ゲームのサーバプログラムです // 使い方java SeaGameServer // 起動すると,ポート番号10000 番に対するクライアントからの接続を待ちます // プログラムを停止するにはコントロールC を入力してください import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.Hashtable; import java.util.Random; import java.util.StringTokenizer; public class SeaGameServer { static final int DEFAULT_PORT = 9999; //SeaGameServer接続用ポート番号 static ServerSocket serverSocket; static ArrayList<ClientProc> clientprocs; //クライアントとのコネクションを保持するVectorオブジェクト static ArrayList<int[]> energy_v; // 燃料タンクの位置情報リスト static Hashtable<String,Ship> userTable = null; // クライアント関連情報登録用テーブル static Random random = null; // addConnectionメソッド // クライアントとの接続をVectorオブジェクトconnectionsに登録します public static void addConnection(ClientProc s){ if (clientprocs == null){//初めてのコネクションの場合は, clientprocs = new ArrayList<ClientProc>();// connectionsを作成します } clientprocs.add(s); } // deleteConnectionメソッド // クライアントとの接続をconnectionsから削除します public static void deleteConnection(ClientProc s){ if (clientprocs != null){ clientprocs.remove(s); } } // loginUserメソッド // loginコマンドの処理として,利用者の名前や船の位置を登録します public static void loginUser(String name){ if (userTable == null){// 登録用テーブルがなければ作成します userTable = new Hashtable<String,Ship>(); } if (random == null){// 乱数の準備をします random = new Random(); } // 船の初期位置を乱数で決定します int ix = Math.abs(random.nextInt()) % 256; int iy = Math.abs(random.nextInt()) % 256; // クライアントの名前や船の位置を表に登録します userTable.put(name, new Ship(ix, iy)); // サーバ側の画面にもクライアントの名前を表示します System.out.println("login:" + name); System.out.flush(); } // logoutUserメソッド // クライアントのログアウトを処理します public static void logoutUser(String name){ // サーバ側画面にログアウトするクライアントの名前を表示します System.out.println("logout:" + name); System.out.flush(); // 登録用テーブルから項目を削除します userTable.remove(name); } // leftメソッド // ある特定の船を左に動かして,燃料タンクが拾えるかどうか判定します // 判定にはcalculationメソッドを使います public static void left(String name){ Ship ship = (Ship) userTable.get(name); ship.left(); calculation(); } // rightメソッド // ある特定の船を右に動かして,燃料タンクが拾えるかどうか判定します // 判定にはcalculationメソッドを使います public static void right(String name){ Ship ship = (Ship) userTable.get(name); ship.right(); calculation(); } // upメソッド // ある特定の船を上に動かして,燃料タンクが拾えるかどうか判定します // 判定にはcalculationメソッドを使います public static void up(String name){ Ship ship = (Ship) userTable.get(name); ship.up(); calculation(); } // downメソッド // ある特定の船を下に動かして,燃料タンクが拾えるかどうか判定します // 判定にはcalculationメソッドを使います public static void down(String name){ Ship ship = (Ship) userTable.get(name); ship.down(); calculation(); } // calculationメソッド // 燃料タンクと船の位置関係を調べて,燃料タンクが拾えるかどうか判定します static synchronized void calculation(){ if (userTable != null && energy_v != null){ // すべてのクライアントについて判定します for (String user : userTable.keySet()) { // 判定するクライアントの名前と船の位置を取り出します Ship ship = (Ship) userTable.get(user); // 燃料タンクすべてについて,船との位置関係を調べます ArrayList<int[]> toberemoved = new ArrayList<int[]>(); for (int[] e : energy_v) { // 燃料タンクの位置と船の位置を調べ,距離を計算します int x = e[0] - ship.x; int y = e[1] - ship.y; double r = Math.sqrt(x * x + y * y); // 距離"10"以内なら燃料タンクを取り込みます if (r < 10) { toberemoved.add(e); ship.point++; } } for(int[] rme : toberemoved) { energy_v.remove(rme); } } } } //--------------------------------------------------------------------- // STATコマンドを処理 // クライアントに船の情報(ship_info)と, // 海上を漂流している燃料タンクの情報を(energy_info)を送信 public static void statInfo(DataOutputStream pw) throws IOException{ // 船の情報(ship_info)の送信 pw.writeBytes("ship_info\n"); if (userTable != null){ for (String user : userTable.keySet()) { Ship ship = (Ship) userTable.get(user); pw.writeBytes(user + " " + ship.x + " " + ship.y + " " + ship.point+"\n"); } } pw.writeBytes(".\n");// ship_infoの終了 // 燃料タンクの情報(energy_info)の送信 pw.writeBytes("energy_info\n"); if (energy_v != null){ // すべての燃料タンクの位置情報をクライアントに送信します for (int[] e : energy_v) { pw.writeBytes(e[0] + " " + e[1] + "\n"); } } pw.writeBytes(".\n");// enegy_infoの終了 pw.flush(); } //--------------------------------------------------------------------- // 燃料タンクを1つだけ海上にランダムに配置 public static void putEnergy(){ if (energy_v == null){// 初めて配置する場合の処理 energy_v = new ArrayList<int[]>(); } if (random == null){// 初めて乱数を使う場合の処理 random = new Random(); } // 乱数で位置を決めて海上に配置します int[] e = new int[2]; e[0] = Math.abs(random.nextInt()) % 256; e[1] = Math.abs(random.nextInt()) % 256; energy_v.add(e); } // end of putEnergy //--------------------------------------------------------------------- // サーバソケットの作成とクライアント接続の処理 // および適当なタイミングでの燃料タンクの逐次追加処理 public static void main(String[] arg){ try {// サーバソケットの作成 serverSocket = new ServerSocket(DEFAULT_PORT); }catch (IOException e){ System.err.println("can't create server socket."); System.exit(1); } // 燃料タンクを順に追加するスレッドetを作ります Thread et = new Thread(){ public void run(){ while(true){ try { sleep(10000);// スレッドetを10000ミリ秒休止させます }catch(InterruptedException e){ break; } // 海上に1つ燃料タンクを配置します SeaGameServer.putEnergy(); } } }; // etをスタートします et.start(); // ソケットの受付と,クライアント処理プログラムの開始処理を行います while (true) {// 無限ループ try { Socket cs = serverSocket.accept(); // クライアント処理スレッドを作成します ClientProc cp = new ClientProc(cs); addConnection(cp);// ClientProcを登録します }catch (IOException e){ System.err.println("client socket or accept error."); } } } // end of main } // end of class putEnergy //============================================================================= class ClientProc implements Runnable { Thread thread; Socket s; // クライアント接続用ソケット BufferedReader in; // 入力ストリーム DataOutputStream out; // 出力ストリーム String name = null; // クライアントの名前 //-------------------------------------------------------------------- // ソケットを使って入出力ストリームを作成します public ClientProc(Socket s) throws IOException { this.s = s; in = new BufferedReader(new InputStreamReader(s.getInputStream())); out = new DataOutputStream(s.getOutputStream()); thread = new Thread(this); thread.start(); } //-------------------------------------------------------------------- public void run(){ //LOGOUTコマンド受信まで繰り返します // while(thread != null) { String line; try { while ((line = in.readLine()) != null) { // クライアントからの入力を読み取ります // nameが空の場合にはLOGINコマンドのみを受け付けます if (name == null){ StringTokenizer st = new StringTokenizer(line); String cmd = st.nextToken(); if ("login".equalsIgnoreCase(cmd)){ name = st.nextToken(); System.out.println(name); SeaGameServer.loginUser(name); }else{ // LOGINコマンド以外は無視 } } else { // nameが空でない場合はログイン済み.コマンドを受け付ける. StringTokenizer st = new StringTokenizer(line); String cmd = st.nextToken();// コマンドの取り出し // コマンドに対応する処理 if ("STAT".equalsIgnoreCase(cmd)){ SeaGameServer.statInfo(out); } else if ("UP".equalsIgnoreCase(cmd)){ SeaGameServer.up(name); } else if ("DOWN".equalsIgnoreCase(cmd)){ SeaGameServer.down(name); } else if ("LEFT".equalsIgnoreCase(cmd)){ SeaGameServer.left(name); } else if ("RIGHT".equalsIgnoreCase(cmd)){ SeaGameServer.right(name); } else if ("LOGOUT".equalsIgnoreCase(cmd)){ SeaGameServer.logoutUser(name); // LOGOUTコマンドの場合には繰り返しを終了 break; } } } } catch (IOException e) { e.printStackTrace(); } finally { // 登録情報を削除し,接続を切断 SeaGameServer.deleteConnection(this); // s.close(); } // } } // end of run } // enf of class clientProc //============================================================================= // 船の位置と,獲得した燃料タンクの数を管理 class Ship { int x, y; // 船の位置座標 int point = 0; // 獲得した燃料タンクの個数 //------------------------------------------------------------------- // 初期位置をセットします public Ship(int x, int y){ this.x = x; this.y = y; } //------------------------------------------------------------------- // 船を左に動かす public void left(){ x -= 10; if (x < 0) x += 256; // 左の辺は右の辺につながっている } //------------------------------------------------------------------- // 船を右に動かす public void right(){ x += 10; x %= 256; // 右の辺は左の辺につながっている } //------------------------------------------------------------------- // 船を上に動かす public void up(){ y += 10; y %= 256; // 上の辺は下の辺につながっている } //------------------------------------------------------------------- // 船を下に動かす public void down(){ y -= 10; if (y < 0) y += 256; // 下の辺は上の辺につながっている } } // end of class Ship