Newer
Older
NWP / src / j6 / SeaGameServer.java
@Motoki Miura Motoki Miura on 22 Sep 2020 12 KB first commit for NWP exp
package j6;

// 「海ゲーム」サーバプログラム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.InetSocketAddress;
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();
	    serverSocket.bind(new InetSocketAddress("0.0.0.0", 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ミリ秒(=10秒)休止させます
			}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