11/16 課題講評
講評
- 41名が提出
- サーバ上のプログラムの戦略は以下の通り
- Kodomo1
- 最初の1回は乱数で決めて,あとの49回は同じものを出し続ける.-> 2回目以降は相手の手に勝てる手を出し続ければ 98点以上取れる.
- Kodomo2
- 最初の1回は乱数で決めて,あとの49回はその手か,それに負ける手から乱数で出す.-> 最初が最初に出した手を出し続けると,平均75点ほどで勝てる.
- Kodomo3
- 相手が直前に出したのに勝てるのを出す -> 自分が直前に出した手に負ける手を出すと勝てる.
解答例とコメント
import java.io.*;
import java.net.*;
public class Janken{
static void janken(BufferedReader reader,PrintWriter writer,int port)throws IOException{
String enemy=reader.readLine();
System.out.println("The opponent is "+enemy+".");
int myhand=(int)(Math.random()*3);
int enemyhand=0;
int[] myhandB=new int[50];
int[] enemyhandB=new int[50];
int strategy=-1;
for(int i=0;i< 50;i++){
writer.println(myhand);
String ehand=reader.readLine();
if(ehand==null){System.exit(1);}
enemyhand=Integer.parseInt(ehand);
myhandB[i]=myhand;
enemyhandB[i]=enemyhand;
if(port==3331 || port==3334){
myhand=enemyhand-1;
if(myhand==-1){myhand=2;}
}else if(port==3332){
myhand=enemyhandB[0];
}else if(port==3333){
myhand=myhand+1;
if(myhand==3){myhand=0;}
}else{
myhand=(int)(Math.random()*3);
}
}
String s;
while((s=reader.readLine())!=null){System.out.println(s);}
}
public static void main(String[] args)throws IOException{
Socket sock=new Socket(args[0],Integer.parseInt(args[1]));
BufferedReader reader=new BufferedReader(new InputStreamReader(sock.getInputStream(),"UTF-8"));
PrintWriter writer=new PrintWriter(sock.getOutputStream(),true);
writer.println("gXXXXXX");
janken(reader,writer,Integer.parseInt(args[1]));
}
}
それぞれの相手に対して最適と思われる戦略がスッキリと書けています。これがオプション課題だったらポートで相手を判定するのは反則なのですが、基本課題なので問題ありません。(成毛)
オプション課題
この課題ではもの足りない人は,以下のような課題に挑戦してみると良いだろう
- 一つのプログラムで,Kodomo{1,2,3}, Otona{1,2}すべてに勝つプログ
ラムを作成する.ただし,プログラムに引数として対戦相手の種類を与えたり,
相手の送ってくるプログラム名を見て戦略を変えるプログラムは認めない.相
手の出す手をもとに,対戦プログラムの種類の見当をつけるのは許される.
- サーバプログラムの抜けている部分を推察して,(一部でもいいので)埋める.
1の解答例(1)
/*
修正箇所:
・初めの6回を乱数にするのをやめて、012012に固定しました。
これでKodomo2とOtona2の区別を間違えることが無くなりました。
・6回目の後にtypeを判断した後、7回目に出す手が6回目と同じになってしまっていたのを修正しました。
*/
import java.util.*;
// 入出力関係のクラスを利用するので java.util.*をインポート
import java.io.*;
// ネットワーク関係のクラスを利用するので java.net.*をインポート
import java.net.*;
class JankenMaster2{
static void janken(BufferedReader in,PrintWriter out) throws IOException{
Random r=new Random();
String s=in.readLine();
System.out.println("対戦相手は"+s);
// 自分の手は最初は乱数で
int myHand=0;
int logmy[]=new int[50];
int logop[]=new int[50];
int type=2;
for(int i=0;i< 50;i++){
out.println(myHand);
if((s=in.readLine())==null){
System.exit(1);
}
int opHand=Integer.parseInt(s);
logmy[i]=myHand;
logop[i]=opHand;
if(i>=0 && i<=4){
myHand=(i+1)%3;
}
if(i==5){
int p[]=new int[4];
for(int k=1;k< 5;k++){
int his[]=new int[3];
for(int j=1;j< k;j++){
if(logmy[j-1]==logmy[k-1] && logop[j-1]==logop[k-1])
his[logmy[j]]++;
}
if(his[0]>=his[1] && his[0]>=his[2]) p[k-1]=2;
else if(his[1]>=his[2]) p[k-1]=0;
else p[k-1]=1;
}
int w1=0;
for(int t=0;t< p.length;t++){
if(p[t]==logop[t+1]) w1++;
}
int w2=0;
for(int t=0;t< 5;t++){
if(logop[t+1]==(logmy[t]+2)%3) w2++;
}
int w4=0;
for(int t=0;t< 4;t=t+3){
if(logop[t]==logop[t+1] && logop[t+1]==logop[t+2]) w4++;
}
if(w1==4) type=0;
else if(w2==5) type=1;
else if(w4==2) type=2;
else type=3;
}
if(i>=5){
switch(type){
case 0:
int hist[]=new int[3];
for(int j=1;j< i+1;j++){
if(logmy[j-1]==logmy[i] && logop[j-1]==logop[i])
hist[logmy[j]]++;
}
if(hist[0]>=hist[1] && hist[0]>=hist[2]) myHand=1;
else if(hist[1]>=hist[2]) myHand=2;
else myHand=0;
if(i==9){
int w3=0;
for(int t=6;t< 9;t++){
if(logop[t+1]==(logmy[t]+2)%3) w3++;
}
if(w3==3) type=1;
}
break;
case 1:
switch(logmy[i]){
case 0:
myHand=1;
break;
case 1:
myHand=2;
break;
case 2:
myHand=0;
break;
}
break;
case 2:
switch(logop[i]){
case 0:
myHand=2;
break;
case 1:
myHand=0;
break;
case 2:
myHand=1;
break;
}
break;
case 3:
myHand=logop[0];
break;
}
}
}
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]));
// サーバから送られてくるメッセージの文字コードはUTF-8なので,
// その変換をおこなうInputStreamReaderを作る
BufferedReader in=new BufferedReader(new InputStreamReader(sock.getInputStream(),"UTF-8"));
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);
}
}
確実に敵を見極めることができるようになりましたね
1の解答例(2)
/*
XXXX
OOOO
過去の((過去に(一つ前の(相手の手と自分の手)に対してもっとも多く出された相手の手)よりも強い手と実際の相手の手との勝敗)の数を考え,修正した手を出すプログラム
kodomo2 以外には確実に勝ってくれそう
*/
// Randomクラスを利用するので java.util.*をインポート
import java.util.*;
// 入出力関係のクラスを利用するので java.io.*をインポート
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 myHand=r.nextInt(3);
// 自分の手と相手の手を記録するための配列.
int myHands[]=new int[50];
int opHands[]=new int[50];
//戦略用配列
int senryaku[][][] =new int[3][3][3];
int daisenryaku[] =new int[3];
int n=0;
// 50回の繰り返し
for(int i=0;i<50;i++){
// 自分の手を送る
out.println(myHand);
// サーバからの入力をもらう
if((s=in.readLine())==null){
System.exit(1);
}
// 相手の手を受け取る
int opHand=Integer.parseInt(s);
// 記録する
myHands[i]=myHand;
opHands[i]=opHand;
if(i>0){senryaku[opHands[i-1]][myHands[i-1]][opHands[i]]++;
if(i>2)daisenryaku[(n-opHands[i]+3)%3]++;
for(n=0;n<3;n++){if(Math.max(Math.max(senryaku[opHands[i]][myHands[i]][0],senryaku[opHands[i]][myHands[i]][1]),senryaku[opHands[i]][myHands[i]][2])==senryaku[opHands[i]][myHands[i]][n])break;}
/* for(n=0;n<3;n++){if(Math.max(Math.max(senryaku[opHands[i]][myHands[i]][0]*2+senryaku[opHands[i]][myHands[i]][2],senryaku[opHands[i]][myHands[i]][1]*2+senryaku[opHands[i]][myHands[i]][0]),senryaku[opHands[i]][myHands[i]][2]*2+senryaku[opHands[i]][myHands[i]][1])==senryaku[opHands[i]][myHands[i]][n]*2+senryaku[opHands[i]][myHands[i]][(n+2)%3])break;}*/
/* for(n=0;n<3;n++){if(Math.min(Math.min(senryaku[opHands[i]][myHands[i]][0],senryaku[opHands[i]][myHands[i]][1]),senryaku[opHands[i]][myHands[i]][2])==senryaku[opHands[i]][myHands[i]][(n+1)%3])break;}*/
/* for(n=0;n<3;n++){if(Math.min(Math.min(senryaku[opHands[i]][myHands[i]][0],senryaku[opHands[i]][myHands[i]][1]),senryaku[opHands[i]][myHands[i]][2])==senryaku[opHands[i]][myHands[i]][(n+1)%3])break;}*/
for(int l=0;l<3;l++){
if(Math.max(Math.max(daisenryaku[0],daisenryaku[1]),daisenryaku[2])==daisenryaku[l]){
myHand=(n-l+2)%3;
//myHand=(n+2)%3;
break;
}}
}
else{myHand=r.nextInt(3);}
}
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]));
// サーバから送られてくるメッセージの文字コードはUTF-8なので,
// その変換をおこなうInputStreamReaderを作る
BufferedReader in=new BufferedReader(new InputStreamReader(sock.getInputStream(),"UTF-8"));
PrintWriter out=new PrintWriter(sock.getOutputStream(),true);
out.println(System.getProperty("user.name"));
// out.println("XXXXX");
janken(in,out);
}
static void usage(){
System.err.println("使い方: java Janken ホスト名 ポート番号");
System.exit(1);
}
}
相手の戦略を推察しないプログラムの中ではよくやっています.字下げを入れたり
途中の結果を変数に取ったり,部分を切り出すなどしてプログラムを見やすくした方が良いと思います.
2の解答例
// 入出力ストリームを使うので,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=r.nextInt(3);
return ir;
}
switch(type){
case 1: /* Kodomo1: いつも同じものを出す */
return ir;
case 2: // Kodomo2
ir=r.nextInt(2);
switch(rec[0][0]){
case 0:
return ir;
case 1:
if(ir==0) ir=2;
return ir;
case 2:
if(ir==1) ir=2;
return ir;
}
case 3: // Kodomo3
return (rec[1][i-1]+2)%3;
case 4:{ // Otona1
if(i%3!=0) return rec[0][i-1];
else return r.nextInt(3);
}
case 5:{ /* Otona2: 直前の自分の手,相手の手を元に次の手を推察する */
int j;
int hist[]=new int[3];
if(i==1) return (rec[1][0]+2)%3;
for(j=1;j< i;j++){
if(rec[0][j-1]==rec[0][i-1] && rec[1][j-1]==rec[1][i-1])
hist[rec[1][j]]++;
}
if(hist[0]>=hist[1] && hist[0]>=hist[2]) return 2;
else if(hist[1]>=hist[2]) return 0;
else return 1;
}
}
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]));
}
}
正解です.すっきり書けていますね.