This time, in a university class, I had the task of creating something in Java via communication between a server and a client, so when I was a high school student, I decided to make a Numer0n game that I used to play during class. When I was a high school student, I was so addicted to the Numer0n game that I enjoyed manually calculating the probability of becoming 〇EAT〇BITE in the first move. .. ..
If you are reading this article, I think you know the basic rules of Numer0n, so I will omit that part. Numeron Wikipedia
Java:version 7 Eclipse: Juno 4.2 OS: windows10
What I want to implement in this system is
If you put all the code, it will be long, so this time I will only post the Channel class that is in charge of sending and receiving on the server side and the Numer0nAI class that implements the computer algorithm. All classes and presentation materials (PowerPoint) are posted on GitHub, so please take a look if you like! Numeron AI is also implemented in python, so if you use python often, please do that too! Numer0n (GitHub) made with Java Python's Numer0nAI (GitHub)
Upon confirming the input from the client, the server creates a Channel, and the Channel and the client interact with each other. Thanks to that, even if an unintended exception occurs to the client, the original server will not go down and the connection of other clients will be maintained.
Channel.java
package server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Channel extends Thread {
JUDGE judge = new JUDGE();
Server server;
Socket socket = null;
BufferedReader input;
OutputStreamWriter output;
String handle;
String playertype;
String roomnumber;
String mynumber;
String tekinumber;
boolean turn;
String ex = "926";//First prediction of AI
String ca ="0123456789";//candidate number: 0-9 at first
String ex_number;
List<String> old_list = new ArrayList<>();
final char controlChar = (char)05;
final char separateChar = (char)06;
Channel(Socket s,Server cs){
this.server = cs;
this.socket = s;
this.start();
}
synchronized void write(String s){
try{
output.write(s + "\r\n");
output.flush();
}catch(IOException e){
System.out.println("Write Err");
close();
}
}
public void run(){
List<String> s_list = new ArrayList<>();//A list that receives and stores input from clients
String s;
String opponent = null;
try{
input = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
output = new OutputStreamWriter(socket.getOutputStream());
write("# Welcome to Numr0n Game");
write("# Please input your name");
handle = input.readLine();
System.out.println("new user: "+ handle);
while(true){//HOST or GUEST Waiting for input
write("INPUT YOUR TYPE(HOST or GUEST or AI)");
playertype = input.readLine();
if(playertype.equals("HOST")){
Random rnd = new Random();
s = String.valueOf(rnd.nextInt(100)+100);
write("[HOST]Room number: "+s);
break;
}else if(playertype.equals("GUEST")){
write("[GUEST]Please enter the room number");
s = input.readLine();
write("[GUEST]Room number: "+s);
break;
}else if(playertype.equals("AI")){
write("[vs AI mode]");
Random rnd = new Random();
s = String.valueOf(rnd.nextInt(100)+100);
write("[HOST]Room number: "+s);
break;
}else{
write("Error entering room number");
}
}
roomnumber = s; //Determining room number
System.out.println(roomnumber);
write("Waiting for an opponent");
if(playertype.equals("AI")){
//Play against AI
write("Decide on your own number (*3-digit number*From 0 to 9*No number cover)");
boolean firstnum = false;
while(firstnum == false){//Does your first number meet the above criteria?
mynumber = input.readLine();
firstnum = judge.isNumber(mynumber);
}
write("My numbers: "+ mynumber);
write(handle + "Start from");
tekinumber="864";
NumeronAI numeron = new NumeronAI();
while(true){
//Game Start
boolean finish = false;
s = input.readLine();
if(s == null){
close();
}else{
System.out.println(s);
boolean numsuccess = judge.isNumber(s);//Numbers in the definition
if (numsuccess) {
JUDGE eatbite = judge.EatBite(s, tekinumber);
finish = judge.Finish(eatbite.eat);//Whether it became 3eat
write("["+ s +"] eat: " +String.valueOf(eatbite.eat) +" bite: "+ String.valueOf(eatbite.bite));
//AI turn from here
JUDGE AIeatbite = judge.EatBite(ex, mynumber);
NumeronAI squeeze = numeron.Squeeze(AIeatbite.eat,AIeatbite.bite,ex,ca,old_list);
if(squeeze.new_can_list.size()<300){
//System.out.println(Arrays.toString(squeeze.new_can_list.toArray()));
//System.out.println(squeeze.can_num);
ex_number = numeron.choice(squeeze.new_can_list,squeeze.can_num);
}else{
Random rnd = new Random();
int index = rnd.nextInt(100);
ex_number = squeeze.new_can_list.get(index);
}
old_list = new ArrayList<>(squeeze.new_can_list);
//System.out.println("Number of remaining candidates: " + String.valueOf(old_list.size()));
write("AI prediction value:" + ex + " [Number of remaining candidates: " + String.valueOf(old_list.size())+"Pieces]");
//System.out.println("AI prediction value: "+ ex);
if(mynumber.equals(ex)){
write("#################you lose#################");
}
ex = ex_number;
//This is the AI turn
} else {
write(" did not send such a number");
}
}
if(finish){
write("#################you win#################");
}
}
}else{//vs human
while(opponent == null){//Waiting for an opponent
opponent = server.findopponent(handle,roomnumber);
}
//write("The opponent has been decided");
write("Decide on your own number (*3-digit number*From 0 to 9*No number cover)");
boolean firstnum = false;
while(firstnum == false){//Does your first number meet the above criteria?
mynumber = input.readLine();
firstnum = judge.isNumber(mynumber);
}
write("My numbers: "+ mynumber);
while(tekinumber == null){//Wait until you get the enemy numbers
tekinumber = server.findopponentnumber(handle, roomnumber);
}
if(playertype.equals("HOST")){
turn = true;
}else{
turn =false;
}
write("Start from the HOST player");
while(true){
//Game Start
boolean finish = false;
while(true){
//Confirmation of turn
s_list.add(input.readLine());//Enter the input
turn = server.isTurn(handle);//Confirmation of turn
if(turn == true){
break;
}
}
s = s_list.get(s_list.size()-1);
s_list.clear();
if(s == null){
close();
}else{
System.out.println(s);
boolean numsuccess = judge.isNumber(s);//Numbers in the definition
if (numsuccess) {
//write("judge ok");
boolean connectsuccess = server.singleSend(opponent,"[Prediction of the other party] "+s);//There is a partner
if(connectsuccess){
//write("There is a partner");
JUDGE eatbite = judge.EatBite(s, tekinumber);
finish = judge.Finish(eatbite.eat);//Whether it became 3eat
write(" [My prediction]"+ s +" eat: " +String.valueOf(eatbite.eat) +" bite: "+ String.valueOf(eatbite.bite));
server.ChangeTurn(handle, opponent);//Switch turn
}else{
write("did not find opponent");
}
} else {
write(" did not send such a number");
}
}
if(finish){
write("#################you win#################");
server.singleSend(opponent, "#################you lose#################");
}
}
}
}catch(IOException e){
System.out.println("Exception occurs in Channel: "+handle);
}
}
public void close(){
try{
input.close();
output.close();
socket.close();
socket = null;
//server.broadcast("Line disconnection: " + handle);
}catch(IOException e){
System.out.println("Close Err");
}
}
}
The Numeron AI class is a class that implements the prediction part of a computer. The prediction consists of the Squeeze method that narrows down the possible candidates from the obtained EAT-BITE information, the Choice method that selects a good move from the candidates, and the count_cand method that calculates a good move. A hand that looks good is the one with the smallest number of expected correct answer candidates from all the combinations of EAT-BITE that are returned when a certain hand is selected. For details, see Thinking about how to win numer0n. I'm implementing something close to this theory! By the way, I think the average number of calls is about 5 or 6 (experience).
NumeronAI.java
package server;
import java.util.ArrayList;
import java.util.List;
public class NumeronAI {
List<String> new_can_list = new ArrayList<>();//candidate list
String can_num;//candidate number
//Narrow down to the number of possible candidates
public NumeronAI Squeeze(int eat,int bite,String pred_num,String ca_num,List<String> old_list){
NumeronAI squeeze = new NumeronAI();
List<String> can_list = new ArrayList<>();
List<String> li = new ArrayList<>();
if(eat == 0 && bite == 0){
//System.out.println("--------" + String.valueOf(ca_num.length()));
for(int i = 0; i<ca_num.length();i++){
if(ca_num.charAt(i) != pred_num.charAt(0) && ca_num.charAt(i) != pred_num.charAt(1) && ca_num.charAt(i) != pred_num.charAt(2)){
li.add(String.valueOf(ca_num.charAt(i)));
}
}
ca_num ="";
StringBuilder builder = new StringBuilder();
for(String num : li){
builder.append(num);
}
ca_num = builder.substring(0,builder.length());
for(int i = 0;i<ca_num.length();i++){
for(int j = 0;j<ca_num.length();j++){
for(int k = 0;k<ca_num.length();k++){
if(ca_num.charAt(i)!=ca_num.charAt(j) && ca_num.charAt(i)!=ca_num.charAt(k) && ca_num.charAt(j)!=ca_num.charAt(k)){
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(ca_num.charAt(j))+String.valueOf(ca_num.charAt(k)));
}
}
}
}
}else if(eat ==0 && bite ==1){
for(int i = 0;i<ca_num.length();i++){
for(int j = 0;j<ca_num.length();j++){
if(ca_num.charAt(i) != ca_num.charAt(j) && ca_num.charAt(i)!=pred_num.charAt(0) && ca_num.charAt(i)!=pred_num.charAt(1) && ca_num.charAt(i)!=pred_num.charAt(2)){
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(0))+String.valueOf(ca_num.charAt(j)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(ca_num.charAt(j))+String.valueOf(pred_num.charAt(0)));
can_list.add(String.valueOf(pred_num.charAt(1))+String.valueOf(ca_num.charAt(i))+String.valueOf(ca_num.charAt(j)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(ca_num.charAt(j))+String.valueOf(pred_num.charAt(1)));
can_list.add(String.valueOf(pred_num.charAt(2))+String.valueOf(ca_num.charAt(i))+String.valueOf(ca_num.charAt(j)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(2))+String.valueOf(ca_num.charAt(j)));
}
}
}
}else if(eat ==0 && bite ==2){
for(int i = 0;i<ca_num.length();i++){
if(ca_num.charAt(i)!=pred_num.charAt(0) && ca_num.charAt(i)!=pred_num.charAt(1) && ca_num.charAt(i)!=pred_num.charAt(2)){
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(0))+String.valueOf(pred_num.charAt(1)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(0)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(1)));
can_list.add(String.valueOf(pred_num.charAt(1))+String.valueOf(pred_num.charAt(0))+String.valueOf(ca_num.charAt(i)));
can_list.add(String.valueOf(pred_num.charAt(1))+String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(0)));
can_list.add(String.valueOf(pred_num.charAt(1))+String.valueOf(pred_num.charAt(2))+String.valueOf(ca_num.charAt(i)));
can_list.add(String.valueOf(pred_num.charAt(2))+String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(0)));
can_list.add(String.valueOf(pred_num.charAt(2))+String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(1)));
can_list.add(String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(0))+String.valueOf(ca_num.charAt(i)));
}
}
}else if(eat == 0 && bite ==3){
can_list.add(String.valueOf(pred_num.charAt(1))+String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(0)));
can_list.add(String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(0))+String.valueOf(pred_num.charAt(1)));
}else if(eat == 1 && bite ==0){
for(int i = 0;i<ca_num.length();i++){
for(int j = 0;j<ca_num.length();j++){
if(ca_num.charAt(i)!=ca_num.charAt(j) && ca_num.charAt(i)!=pred_num.charAt(0) && ca_num.charAt(i)!=pred_num.charAt(1) &&
ca_num.charAt(i)!=pred_num.charAt(2) && ca_num.charAt(j)!=pred_num.charAt(0) &&
ca_num.charAt(j)!=pred_num.charAt(1) && ca_num.charAt(j)!=pred_num.charAt(2)){
can_list.add(String.valueOf(pred_num.charAt(0))+String.valueOf(ca_num.charAt(i))+String.valueOf(ca_num.charAt(j)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(1))+String.valueOf(ca_num.charAt(j)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(ca_num.charAt(j))+String.valueOf(pred_num.charAt(2)));
}
}
}
}else if(eat ==1 && bite ==1){
for(int i = 0;i<ca_num.length();i++){
if(ca_num.charAt(i)!=pred_num.charAt(0) && ca_num.charAt(i)!=pred_num.charAt(1) && ca_num.charAt(i)!=pred_num.charAt(2)){
can_list.add(String.valueOf(pred_num.charAt(0))+String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(1)));
can_list.add(String.valueOf(pred_num.charAt(0))+String.valueOf(pred_num.charAt(2))+String.valueOf(ca_num.charAt(i)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(1))+String.valueOf(pred_num.charAt(0)));
can_list.add(String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(1))+String.valueOf(ca_num.charAt(i)));
can_list.add(String.valueOf(pred_num.charAt(1))+String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(2)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(0))+String.valueOf(pred_num.charAt(2)));
}
}
}else if(eat ==1 && bite ==2){
for(int i = 0;i<ca_num.length();i++){
can_list.add(String.valueOf(pred_num.charAt(0))+String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(1)));
can_list.add(String.valueOf(pred_num.charAt(2))+String.valueOf(pred_num.charAt(1))+String.valueOf(pred_num.charAt(0)));
can_list.add(String.valueOf(pred_num.charAt(1))+String.valueOf(pred_num.charAt(0))+String.valueOf(pred_num.charAt(2)));
}
}else if(eat ==2 && bite ==0){
for(int i = 0;i<ca_num.length();i++){
if(ca_num.charAt(i)!=pred_num.charAt(0) && ca_num.charAt(i)!=pred_num.charAt(1) && ca_num.charAt(i)!=pred_num.charAt(2)){
can_list.add(String.valueOf(pred_num.charAt(0))+String.valueOf(pred_num.charAt(1))+String.valueOf(ca_num.charAt(i)));
can_list.add(String.valueOf(pred_num.charAt(0))+String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(2)));
can_list.add(String.valueOf(ca_num.charAt(i))+String.valueOf(pred_num.charAt(1))+String.valueOf(pred_num.charAt(2)));
}
}
}else if(eat ==3 && bite ==0){
can_list.add(pred_num);
}
if(old_list.size()!=0){
for(String num : can_list){
if(old_list.contains(num)){
squeeze.new_can_list.add(num);
squeeze.can_num =ca_num;
}
}
}else{
squeeze.new_can_list = can_list;
squeeze.can_num = ca_num;
}
//System.out.println(can_num);
return squeeze;
}
//Calculate the expected number of candidates
public double count_cand(String pred_num,String ca_num,List<String> ca_list){
double ave_ca = 0;
int[][] info_list = {{0,0},{0,1},{0,2},{0,3},{1,0},{1,1},{1,2},{2,1},{3,0}};
int sum_ex = 0;
int sum_ex2 = 0;
List<String> old_count_list = new ArrayList<>(ca_list);
String ca_count_num = ca_num;
NumeronAI squeeze2 = new NumeronAI();
for(int[] info :info_list){
squeeze2 = Squeeze(info[0],info[1],pred_num,ca_count_num,old_count_list);
sum_ex=sum_ex+squeeze2.new_can_list.size();
sum_ex2=sum_ex2+squeeze2.new_can_list.size()^2;
}
if(sum_ex!=0){
ave_ca=sum_ex2/sum_ex;
}
return ave_ca;
}
//Select the number with the smallest expected number of candidates
public String choice(List<String> ca_list,String ca_num){
List<Double> ave_list = new ArrayList<>();
int min_index =0;
try{
for(String num :ca_list){
double ave_ca = count_cand(num,ca_num,ca_list);
ave_list.add(ave_ca);
}
double min =ave_list.get(0);
for(int i =0;i<ave_list.size();i++){
double val = ave_list.get(i);
if(min > val){
min = val;
min_index = i;
}
}
return ca_list.get(min_index);
}catch(Exception e){
System.out.println("Choice Miss:" + e);
return "111";
}
}
}
This time, I was hit in 6 turns.
I haven't written much about Java, but I think I've done what I wanted to make. However, I haven't reached the level of practicing knowledge of design patterns yet, so I realized that I need to study. (Design pattern is difficult ...) Writing Numer0n's AI (I don't want to call it AI) by myself is recommended for those who know Numer0n and want to make something because the rules are easy to understand!
Also, I created only the Numeron AI part in python, so I have not written Java, but if you can understand it with python, please refer to it.
This is my first post on Qiita, so if you have any improvements to this article, please leave a comment <(_ _)>
I will do my best to output little by little from now on!
Recommended Posts