鬼ごっこ
本当のマルチプレイヤーゲームへの第一歩。
3人でやる疑似鬼ごっこのサンプルを示す。サーバはデータを集中管理し、クライアントはユーザに対応する。スケルトンのみ示すから、諸君は、鬼役をきちんと導入したり、衝突などの処理をきちんとできるよう改善してみてはどうか。
サーバのサンプルコードは以下の通り。クライアントからの接続要求を受け付けるServerSocketと実際のセッションの通信に使うSocketの区別をしっかりつける必要がある。このサンプルではSocketを生に扱うことはせず、BufferedReaderやPrintWriterにラップして受け渡すようにしている。Reader, Writerでやるということは、あまり速度性能を重視していないということだが、性能評価は上級の課題としてとっておく。デバッグ用のコードがそのまま残っているが、動作の確認のため、利用してほしい。クライアントのサンプルも同様。
package tag3; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; public class Tag3Server { final int PORT = 11000; // Model Part Player[] players; int num; // Number of players /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub (new Tag3Server(3)).start(); } Tag3Server(int num) { this.num = num; players = new Player[num]; } void start() { registerPlayers(); // players 登録 kickOff(); // 開始 } // players 登録 void registerPlayers() { ServerSocket soc0=null; try { soc0 = new ServerSocket(PORT); for (int i = 0; i < num; i++) { Socket soc = soc0.accept(); BufferedReader reader = new BufferedReader( new InputStreamReader(soc.getInputStream())); PrintWriter writer = new PrintWriter(new OutputStreamWriter( soc.getOutputStream())); String name = reader.readLine(); players[i] = new Player(this, i, name, reader, writer, i, i); writer.println("Hello, " + name + "!"); writer.println("Your ID is " + i + ". Please wait a second."); writer.flush(); } soc0.close(); // ServerSocket は用済み } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); try{if(soc0!=null) soc0.close();}catch(IOException ie){/* do nothing */} System.exit(-1); } } // 開始の合図 void kickOff() { System.out.println("kickOff is called."); for (int i = 0; i < num; i++) { System.out.printf("kickOff: %d/%d\n", i,num); players[i].writer.println("Go ahead!"); players[i].writer.flush(); } for(int i=0; i<num; i++){ (new Thread(players[i])).start(); // client 通信の監視スレッド System.out.printf("Player thread %d/%d\n", i, num); } } // client への更新通知 void update(int id){ System.out.println("server.update is called"); for(int i=0; i<num; i++){ System.out.printf("message: id = %d\n", i); players[i].writer.printf("%d %d %d\n", id, players[id].x, players[id].y); players[i].writer.flush(); } } } class Player implements Runnable { Tag3Server server; int id; String name; BufferedReader reader; // client からの reader PrintWriter writer; // client への writer int x; int y; Player(Tag3Server server, int id, String name, BufferedReader reader, PrintWriter writer, int x, int y) { this.server = server; this.id = id; this.name = name; this.reader = reader; this.writer = writer; this.x = x; this.y = y; } @Override public void run() { // TODO Auto-generated method stub while (true) { try { String line = reader.readLine(); Scanner scan = new Scanner(line); x = scan.nextInt(); y = scan.nextInt(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } server.update(id); System.out.printf("id = %d, x = %d, y = %d\n",id, x, y); } } }
以下はクライアントのサンプルである。
package tag3; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; import javax.swing.JButton; import javax.swing.JFrame; public class TagClient { final int PORT = 11000; String name; int id; String hostname = "localhost"; Socket soc; BufferedReader reader; PrintWriter writer; TagPanel panel; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub // args[0] should be player's nickname // The second argument to the constructor is // the number of clients, which must be more flexible // in a final version. (new TagClient(args[0], 3)).start(); } TagClient(String name, int num) { this.name = name; panel = new TagPanel(name, num); } void start() { register(); // players 登録 kickOff(); // 開始 } void register() { try { soc = new Socket(hostname, PORT); reader = new BufferedReader(new InputStreamReader( soc.getInputStream())); writer = new PrintWriter(new OutputStreamWriter( soc.getOutputStream())); writer.println(name); writer.flush(); // expect "Hello, ....\n" String line = reader.readLine(); System.out.println(line); // for debug // expect "Your ID is #. Please wait a second.\n" line = reader.readLine(); System.out.println(line); // for debug id = Integer.parseInt(line.substring(11, 12)); panel.id = id; panel.reader = this.reader; panel.writer = this.writer; // expect "Go ahead!\n" line = reader.readLine(); System.out.println(line); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } void kickOff() { new Thread((new SocketListener(reader, panel))).start(); } } class TagPanel { JFrame frame; JButton[][] buttons; int[] x; // i-th player's horizontal position -- left to right int[] y; // i-th player's vertical position -- up to bottom int id; BufferedReader reader; PrintWriter writer; TagPanel(String name, int num) { frame = new JFrame(name); frame.setSize(400, 400); buttons = new JButton[8][8]; x = new int[num]; y = new int[num]; frame.getContentPane().setLayout(new GridLayout(8, 8)); for (int j = 0; j < 8; j++) { for (int i = 0; i < 8; i++) { buttons[j][i] = new JButton(); buttons[j][i].addActionListener(new PositionGetter(this, i, j)); frame.getContentPane().add(buttons[j][i]); } } frame.setVisible(true); } // myUpdate is called by the action listener of a button. void myUpdate(int newX, int newY) { /* buttons[y[this.id]][x[this.id]].setText(" "); x[this.id] = newX; y[this.id] = newY; buttons[newY][newX].setText(String.valueOf(this.id)); */ writer.printf("%d %d\n", newX, newY); writer.flush(); System.out.printf("myUpdate is called. %d %d\n", newX, newY); } // update is called by the socket listener. void update(int id, int newX, int newY) { buttons[y[id]][x[id]].setText(" "); x[id] = newX; y[id] = newY; buttons[newY][newX].setText(String.valueOf(id)); } } class SocketListener implements Runnable { BufferedReader reader; TagPanel panel; SocketListener(BufferedReader reader, TagPanel panel) { this.reader = reader; this.panel = panel; } @Override public void run() { // TODO Auto-generated method stub while (true) { try { String line = reader.readLine(); Scanner scan = new Scanner(line); int id = scan.nextInt(); int x = scan.nextInt(); int y = scan.nextInt(); panel.update(id, x, y); } catch (IOException e) { System.out.println("read error"); System.exit(-1); } } } } class PositionGetter implements ActionListener { TagPanel panel; int x; int y; PositionGetter(TagPanel panel, int x, int y) { this.panel = panel; this.x = x; this.y = y; } @Override public void actionPerformed(ActionEvent arg0) { // TODO Auto-generated method stub panel.myUpdate(x, y); } }
実行例
4つのコマンドプロンプト(シェルのウィンドウ)を立ち上げておく。いずれも
...\workspace\prog3\bin
にカレントディレクトリを移動しておく。
java tag3/Tag3Server
java tag3/TagClient betty
java tag3/TagClient jack
java tag3/TagCleint tom
のように実行すると以下の図のようにゲームが始まる。
commandPrompt.png
gameSession.png