12/13 課題


1勝負50回の繰り返しじゃんけんをネットワークサーバにつないでプレイする プログラムを作れ.ただし,弱い相手に対しては大幅に勝ち越すものでなくて はいけない.

詳細


プロトコル(1行単位)

1. 「ユーザ名の登録」
サーバ -> クライアント: プログラム名
クライアント -> サーバ: ユーザ名(ここで嘘をつくと,勝っても正しく記録されない)
2. 「出す手を伝える」
クライアント -> サーバ: 手(0,1,2)
サーバ -> クライアント: 相手の手
3. 50回の勝負が終わる前は2に戻る.
4. 結果の表示
サーバ -> クライアント : 
5. 切断
 サーバの側から接続を切断する.
ヒント

プロトコルを理解するために,

telnet 133.11.171.98 3331
とやって,名前の入力,[0-2]の数字の入力をやってみるのも良い.
サンプルクライアントプログラム
// Randomクラスを利用するので java.util.*をインポート
import java.util.*;
// 入出力関係のクラスを利用するので java.util.*をインポート
import java.io.*;
// ネットワーク関係のクラスを利用するので java.net.*をインポート
import java.net.*;

class Janken{
  static void janken(BufferedReader in,PrintWriter out) throws IOException{
    Random r=new Random();
    String s=in.readLine();
    System.out.println("対戦相手は"+s);
    int index=(int)(r.nextDouble()*3.0);
    for(int i=0;i< 50;i++){
      out.println(index);
      // サーバからの入力をもらう
      if((s=in.readLine())==null){
	System.exit(1);
      }
      index=(int)(r.nextDouble()*3.0);
    }
    while((s=in.readLine())!=null)
      System.out.println(s);
  }
  public static void main(String args[]) throws IOException{
    if(args.length<2) usage();
    Socket sock=new Socket(args[0],Integer.parseInt(args[1]));
    BufferedReader in=new BufferedReader(new InputStreamReader(sock.getInputStream()));
    PrintWriter out=new PrintWriter(sock.getOutputStream(),true);
    out.println(System.getProperty("user.name"));
    janken(in,out);

  }
  static void usage(){
    System.err.println("使い方: java Janken ホスト名 ポート番号");
    System.exit(1);
  }
}

実行例は以下のようになる.
ktanaka@ux019> java Janken 133.11.171.98 3331
対戦相手はKodomo1
Kodomo1 22222222222222222222222222222222222222222222222222
ktanaka 21022210000012222212000221022222220120212200120220
Wed Dec 13 16:01:31 JST 2000
Kodomo1 が ktanakaに58 点で勝ちました
このプログラムは,完全に乱数で手を出す. プロトコルが決まっているので,スレッドを使う必要はない.

備考

じゃんけんサーバのプログラムは以下のようになっている(一部,隠してある).
  // 入出力ストリームを使うので,java.io.* を import
import java.io.*;
  // ソケットを使うので java.net.* を import 
import java.net.*;
// randomを使う
import java.util.*;

class JankenGame implements Runnable{
  Socket sock[]=new Socket[2];
  BufferedReader[] ins=new BufferedReader[2];
  PrintWriter[] outs=new PrintWriter[2];
  String names[]=new String[2];
  JankenServer js;
  JankenGame(JankenServer js,Socket sock0,Socket sock1){
    this.js=js;
    this.sock[0]=sock0;
    this.sock[1]=sock1;
  }
  public void run(){
    int i,j;
    try{
      for(i=0;i<2;i++){
	outs[i] = new PrintWriter(sock[i].getOutputStream(),true);
	ins[i] = new BufferedReader(new InputStreamReader(sock[i].getInputStream()));
	names[i]=ins[i].readLine();
      }
      for(i=0;i<2;i++)
	outs[i].println(names[1-i]);
      String[] s=new String[2];
      int[][] gameRec=new int[2][50];
      gameLoop: for(j=0;j<50;j++){
	for(i=0;i<2;i++){
	  if((s[i]=ins[i].readLine())==null){
	    break gameLoop;
	  }
	  gameRec[i][j]=Integer.parseInt(s[i]);
	}
	for(i=0;i<2;i++){
	  outs[i].println(gameRec[1-i][j]);
	}
      }
      if(j==50){
	String str=JankenRobot.recToStr(names[0],gameRec[0])+JankenRobot.recToStr(names[1],gameRec[1]);
	int w;
	for(w=0,i=0;i<50;i++)
	  w+=JankenRobot.win(gameRec[0][i],gameRec[1][i]);
	str=str+new Date()+"\n";
	if(w>0){
	  str=str+names[0]+" が "+names[1]+"に"+(w+50)+" 点で勝ちました";
	}
	else if(w<0){
	  str=str+names[1]+" が "+names[0]+"に"+(50-w)+" 点で勝ちました";
	}
	else
	  str=str+names[0]+" と "+names[1]+"は引き分けでした";
	for(i=0;i<2;i++)
	  outs[i].println(str);
	js.println(str);
      }
      else{
	for(i=0;i<2;i++)
	  outs[i].println("プログラムが途中で中断しました");
	js.println("プログラムが途中で中断しました");
      }
      sock[0].close();
      sock[1].close();
    }
    catch(IOException e){
      System.err.println(e);
    }
  }
}
class JankenRobot implements Runnable{
  Socket sock;
  JankenServer js;
  int type;
  PrintWriter out;
  BufferedReader in;
  String[] robotNames={"","Kodomo1","Kodomo2","Kodomo3","Otona1","Otona2"};
  JankenRobot(JankenServer js,Socket sock,int type){
    this.js=js;
    this.sock=sock;
    this.type=type;
  }
  // m0がm1に勝つ時は 1, 引き分けが0,敗けは -1
  static int win(int m0, int m1){
    if(m0-m1== -1 || m0-m1== 2) return 1;
    else if(m0==m1) return 0;
    else return -1;
  }
  static String recToStr(String name,int[] game){
    String s=(name+"        ").substring(0,8);
    int i;
    for(i=0;i<50;i++)
      s=s+game[i];
    return s+"\n";
  }
  Random r=new Random();
  int ir;
  int robot(int i,int[][] rec){
    if(i==0){
      ir=(int)(r.nextDouble()*3.0);
      return ir;
    }
    switch(type){
    case 1: /* いつも同じものを出す */
      return ir;
    case 2: 
      /* 削除 */
    case 3: 
      /* 削除 */
    case 4:{
      /* 削除 */
    }
    case 5:{
      /* 削除 */
    }
    }
    return 0;
  }
  public void run(){
    try{
      out = new PrintWriter(sock.getOutputStream(),true);
      in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
      out.println(robotNames[type]);
      int i;
      String name=in.readLine();
      String s=null;
      int[][] gameRec=new int[2][50];
      for(i=0;i<50;i++){
	int a=robot(i,gameRec);
	if((s=in.readLine())==null) break;
	int b=Integer.parseInt(s);
	gameRec[0][i]=a;
	gameRec[1][i]=b;
	out.println(a);
      }
      if(i==50){
	String str=recToStr(robotNames[type],gameRec[0])+recToStr(name,gameRec[1]);
	int w;
	for(w=0,i=0;i<50;i++)
	  w+=win(gameRec[0][i],gameRec[1][i]);
	str=str+new Date()+"\n";
	if(w>0){
	  str=str+robotNames[type]+" が "+name+"に"+(w+50)+" 点で勝ちました";
	}
	else if(w<-10){
	  str=str+name+" が "+robotNames[type]+"に"+(50-w)+" 点で勝ちました. じゃんけんロボット("+type+")はクリアしました\n";
	}
	else{
	  str=str+name+" が "+robotNames[type]+"に"+(50-w)+" 点で勝ちました. じゃんけんロボット("+type+")をクリアするには60点必要です\n";
	}
	out.println(str);
	js.println(str);
      }
      else{
	out.println("プログラムが途中で中断しました");
	js.println("プログラムが途中で中断しました");
      }
      sock.close();
    }
    catch(IOException e){
      System.err.println(e);
    }
  }
}

// 呼び出し方
// java JankenServer 対戦モード ポート番号
// 対戦モードは
// 0 自由対戦
// 1-5 対応するプログラム

class JankenServer{
    // コンストラクタ
  public JankenServer(int type, int port){
    Socket sock,waitingSock=null;
    try{
        // ServerSocketを作成
      ServerSocket servsock=new ServerSocket(port);
        // 無限ループ,breakが来るまで
      while(true){
          // クライアントからのアクセスをうけつけた.
        sock=servsock.accept();
	if(type==0){
	  if(waitingSock==null){
	    waitingSock=sock;
	  }
	  else{
	    new Thread(new JankenGame(this,waitingSock,sock)).start();
	    waitingSock=null;
	  }
	}
	else if(type<=5){
	  new Thread(new JankenRobot(this,sock,type)).start();
	  waitingSock=null;
	}
      }
    } catch(IOException ioe){
      System.out.println(ioe);
    }
  }
 synchronized void println(String s){
    System.err.println(s);
  }
  public static void main(String args[]){
      // インスタンスを1つだけ作る.
    new JankenServer(Integer.parseInt(args[0]),Integer.parseInt(args[1]));
  }
}

オプション課題
  1. 一つのプログラムで,Kodomo{1,2,3}, Otona{1,2}すべてに勝つプログ ラムを作成する.ただし,プログラムに引数として対戦相手の種類を与えたり, 相手の送ってくるプログラム名を見て戦略を変えるプログラムは認めない.相 手の出す手をもとに,対戦プログラムの種類の見当をつけるのは許される. 書けたら,プログラムをネットワークニュースに投稿する.
  2. サーバプログラムの抜けている部分を推察して,(一部でもいいので)埋める. その部分をプログラムをネットワークニュースに投稿する. 締め切りは12/18(来週の月曜日)21時まで.