猜数字游戏是80后很多人在小时候经常玩的一个逻辑游戏。这个游戏一般需要两个参与者,每个人秘密声明一个各个位值上数字不相同的四位数。互相猜测对方的数字,如果猜测的数字中位置正确且数字正确,对方会给出一个A,如果猜测的数字中位置不正确但数字正确,对方会给出B。两个参与者互相猜测多轮直至一方完全猜测出另一方的数字。
理论上8次就可以猜出对方的数字。
比如两个玩家"赵四"和"刘能"
动作 | 赵四 | 刘能 |
---|---|---|
秘密声明 | 1024 | 9102 |
猜测对方 | 1234(得到刘能答复0A2B) | 1234(得到赵四答复2A1B) |
猜测对方 | 5678(得到刘能答复0A0B) | 5678(得到赵四答复0A0B) |
在这个步骤:双方都能排除对方不包含5678。赵四可以确定刘能的数字一定含有9和0
根据分析再次继续猜测
动作 | 赵四 | 刘能 |
---|---|---|
猜测对方 | 9012(得到刘能答复2A2B) | 1239(得到赵四答复1A1B) |
在这个步骤:赵四可以确定刘能的四个数字,刘能可以确定9不是且4是A
根据分析再次继续猜测
动作 | 赵四 | 刘能 |
---|---|---|
猜测对方 | 9102(得到刘能答复4A0B) | 1204(得到赵四答复2A2B) |
此时赵四猜中刘能的数字,获胜。刘能也几乎快要猜到赵四的数字。
现在通过所学的Java知识完成这样的游戏设计
1. 客户端首先需要使用账号密码登录,服务器连接数据库验证登录
2. 客户端若没有账号需要访问服务器进行注册,服务器在数据库中保存账号信息
3. 客户端开始游戏,服务器生成数字
4. 客户端猜测数字,服务器给出nAnB的回应
5. 客户端投降,服务器给出生成的数字
6. 客户端猜中数字,服务器记录用时并保存最好记录
7. 客户端可以查看当前速度最快的前10名的排名
1. 数据库
players表
2. 服务器(GameServer)
DBConfig.properties
数据库配置文件,请根据实际情况修改。
IPAdress=localhost
port=3306
database=mydb
username=root
password=root
timezone=GMT%2B8
DBUtil.java
数据库连接工具类
public class DBUtil {
private static String url;
private static String username;
private static String password;
private DBUtil() {}
static {
// 1.加载驱动
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//读取配置文件,并完成连接字符串
Properties p = new Properties();
try {
p.load(DBUtil.class.getResourceAsStream("DBConfig.properties"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String ip = p.getProperty("IPAdress");
String port = p.getProperty("port");
String db = p.getProperty("database");
String tz = p.getProperty("timezone");
url = "jdbc:mysql://"+ip+":"+port+"/"+db+"?useUnicode=true&characterEncoding=utf-8&serverTimezone="+tz;
username = p.getProperty("username");
password = p.getProperty("password");
}
public static Connection getConnection() {
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
public static void close(Connection conn, Statement pst, ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(pst != null) {
try {
pst.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void close(Connection conn, Statement pst) {
close(conn, pst, null);
}
public static void close(Connection conn) {
close(conn, null, null);
}
}
Player.java
玩家pojo类,映射Players表
public class Player {
private String account;
private String apass;
private long besttime;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getApass() {
return apass;
}
public void setApass(String apass) {
this.apass = apass;
}
public long getBesttime() {
return besttime;
}
public void setBesttime(long besttime) {
this.besttime = besttime;
}
}
PlayerDAO.java
Player持久化类(含部分业务逻辑)
public class PlayerDAO {
/**
* 登录
* @param p
* @return
*/
public boolean login(Player p) {
Connection conn = DBUtil.getConnection();
String sql = "select * from players where account = ? and apass = ?";
PreparedStatement pst = null;
ResultSet rs = null;
boolean r = false;
try {
pst = conn.prepareStatement(sql);
pst.setString(1, p.getAccount());
pst.setString(2, p.getApass());
rs = pst.executeQuery();
if(rs.next()) {
r = true;
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
DBUtil.close(conn, pst, rs);
}
return r;
}
/**
* 注册
*/
public boolean regist(Player p) {
Connection conn = DBUtil.getConnection();
String sql = "insert into players values(?,?,?)";
PreparedStatement pst = null;
try {
pst = conn.prepareStatement(sql);
pst.setString(1, p.getAccount());
pst.setString(2, p.getApass());
pst.setLong(3, p.getBesttime());
pst.executeUpdate();
} catch (SQLException e) {
return false;
} finally {
DBUtil.close(conn, pst);
}
return true;
}
/**
* 取得当前记录
*/
public long getBestTime(String account) {
Connection conn = DBUtil.getConnection();
String sql = "select besttime from players where account = ?";
PreparedStatement pst = null;
ResultSet rs = null;
long r = 0;
try {
pst = conn.prepareStatement(sql);
pst.setString(1, account);
rs = pst.executeQuery();
while(rs.next()) {
r = rs.getLong("besttime");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
DBUtil.close(conn, pst, rs);
}
return r;
}
/**
* 更新记录
*/
public boolean updateTime(Player p) {
Connection conn = DBUtil.getConnection();
String sql = "update players set besttime = ? where account = ?";
PreparedStatement pst = null;
try {
pst = conn.prepareStatement(sql);
pst.setLong(1, p.getBesttime());
pst.setString(2, p.getAccount());
pst.executeUpdate();
} catch (SQLException e) {
return false;
} finally {
DBUtil.close(conn, pst);
}
return true;
}
/**
* 查询记录
*/
public String getList() {
Connection conn = DBUtil.getConnection();
String sql = "select account, besttime from players "
+ "where besttime <> 0 order by besttime asc limit 0,10";
PreparedStatement pst = null;
ResultSet rs = null;
StringBuffer sb = new StringBuffer();
try {
pst = conn.prepareStatement(sql);
rs = pst.executeQuery();
while(rs.next()) {
sb.append(rs.getString("account"));
sb.append(",");
sb.append(rs.getLong("besttime"));
sb.append(";");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
DBUtil.close(conn, pst, rs);
}
return sb.toString();
}
}
UserThread.java
核心业务处理类,每个访问服务器的对象都利用该类的对象创建线程对应处理
public class UserThread extends Thread{
private Socket s;
public UserThread(Socket s) {
this.s = s;
}
@Override
public void run() {
PlayerDAO dao = new PlayerDAO();
int number = 0;
long l1 = 0;
long l2 = 0;
String ac = null; //记录线程属于哪个用户
while(true) {
//通过输入流获取客户端发来信息
try {
DataInputStream dis = new DataInputStream(s.getInputStream());//处理输入流
DataOutputStream dos = new DataOutputStream(s.getOutputStream());//处理输出流
//操作dis就可获得客户端的消息,这也是一个阻塞事件
String str = dis.readUTF();
String rec = null; //回应的消息
String[] ss = str.split(",");
//解析暗号
int signal = Integer.parseInt(ss[0]); //将字符串转换成数字
if(signal == 100) {
//登录
String account = ss[1];
String apass = ss[2];
Player p = new Player();
p.setAccount(account);
p.setApass(apass);
boolean b = dao.login(p);
if(b) {
rec = "OK";
ac = account; //登录成功的时候设置线程拥有者
}else {
rec = "NG";
}
}else if(signal == 101) {
//注册
String account = ss[1];
String apass = ss[2];
Player p = new Player();
p.setAccount(account);
p.setApass(apass);
p.setBesttime(0);
boolean b = dao.regist(p);
if(b) {
rec = "OK";
}else {
rec = "NG";
}
}else if(signal == 102) {
//登录页面退出
break;
}else if(signal == 103) {
//开始游戏(生成数字)
number = StartServer.getRandomNumber(); //得到随机数字
System.out.println(number);
l1 = System.currentTimeMillis();
rec = "OK";
}else if(signal == 104) {
//猜数字
int guess = Integer.parseInt(ss[1]); //用户猜测的数字
rec = StartServer.compareNumber(number, guess);
//结果是4A0B,结束计时
if(rec.equals("4A0B")) {
l2 = System.currentTimeMillis();
long t = l2 - l1; //耗时
long n = dao.getBestTime(ac);//数据库中的当前记录
//如果当前记录是0 ,t保存
//如果t小于当前的最好记录,t保存
if(n == 0 || t < n) {
Player temp = new Player();
temp.setAccount(ac);
temp.setBesttime(t);
dao.updateTime(temp);
}
l1 = 0;
l2 = 0;
number = 0;
}
}else if(signal == 105) {
//投降
rec = number+"";
l1 = 0;
l2 = 0;
number = 0;
}else if(signal == 106) {
//查看排名
rec = dao.getList();
}else {
}
dos.writeUTF(rec); //回应客户端
dos.flush();
} catch (IOException e) {
break;
}
}
}
}
StartServer.java
服务器启动类,开启通信监听和创建用户处理线程,提供部分工具性质方法。
public class StartServer {
public static void main(String[] args) {
//创建了ServerSocket服务器
ServerSocket ss = null;
try {
ss = new ServerSocket(40000);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
while(true) {
try {
//监听来自客户端的请求,这是一个阻塞事件
Socket s = ss.accept();
UserThread ut = new UserThread(s);
ut.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 随机生成一个各个为上数字不同的四位数
* @return
*/
public static int getRandomNumber() {
Random r = new Random();
int q = r.nextInt(9)+1; //[1,9]
int b,s,g;
while(true) {
int temp = r.nextInt(10); //[0,9]
if(temp != q) {
b = temp;
break;
}
}
while(true) {
int temp = r.nextInt(10); //[0,9]
if(temp != q && temp != b) {
s = temp;
break;
}
}
while(true) {
int temp = r.nextInt(10); //[0,9]
if(temp != q && temp != b && temp != s) {
g = temp;
break;
}
}
return q*1000+b*100+s*10+g;
}
/**
* 比较n1和n2的结果
* @param n1
* @param n2
* @return
*/
public static String compareNumber(int n1, int n2) {
int[] a = new int[4];
int[] b = new int[4];
a[0] = n1/1000;
a[1] = n1/100%10;
a[2] = n1/10%10;
a[3] = n1%10;
b[0] = n2/1000;
b[1] = n2/100%10;
b[2] = n2/10%10;
b[3] = n2%10;
int x = 0; //A
int y = 0; //B
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 4; j++) {
if(a[i] == b[j] && i == j) {
x++;
}else if(a[i] == b[j] && i != j) {
y++;
}
}
}
return x+"A"+y+"B";
}
}
3. 客户端(GameClient)
StartClient.java
通信,数字输入和各项操作。相当于客户端界面
public class StartClient {
public static void main(String[] args) {
//建议IP和端口通过配置文件获取
try {
Socket s = new Socket("localhost", 40000);
DataOutputStream dos = new DataOutputStream(s.getOutputStream());//处理输出流
DataInputStream dis = new DataInputStream(s.getInputStream());//处理输入流
while(true) {
/*
* 数据库表players表, 账号,密码,最好时间
*
* 1. 用户输入账号和密码 登录
* 2. 注册
*
* 3. 开始游戏 - 猜数字
* 4. 查看排名
*
* 5. 退出
*/
System.out.println("1 - 登录");
System.out.println("2 - 注册");
System.out.println("9 - 退出");
System.out.println("请选择:");
Scanner sc1 = new Scanner(System.in);
int s1 = sc1.nextInt();
if(s1 == 1) {
//登录
//1.提示用户输入账号
Scanner sc2 = new Scanner(System.in);
System.out.println("请输入账号:");
String account = sc2.nextLine();
//2.提示用户输入密码
Scanner sc3 = new Scanner(System.in);
System.out.println("请输入密码:");
String apass = sc3.nextLine();
//3.将账号和密码发送给服务器 (服务器验证)
dos.writeUTF(100+","+account+","+apass);
dos.flush();
//4.接收服务器的验证结果 NG-重新执行该界面
String rec = dis.readUTF();
if(rec.equals("OK")) {
//OK- 进入下一个界面
while(true) {
System.out.println("1 - 开始游戏");
System.out.println("2 - 查看排名");
System.out.println("9 - 退出");
System.out.println("请选择:");
Scanner sc4 = new Scanner(System.in);
int s2 = sc4.nextInt();
if(s2 == 1) {
//开始游戏
//1.发送指令,服务器生成随机数字
dos.writeUTF(103+",");
dos.flush();
String rec1 = dis.readUTF();
if(rec1.equals("OK")) {
//2.开始猜
while(true) {
Scanner sc5 = new Scanner(System.in);
System.out.println("请输入猜测的四位数(投降请输入-1):");
int guess = sc5.nextInt();
if(guess == -1) {
//(2)投降 -1
dos.writeUTF(105+",");
dos.flush();
String rec2 = dis.readUTF();
System.out.println("服务器生成的数字是:"+rec2);
break;
}else {
//(1)输入数字 - 猜
dos.writeUTF(104+","+guess);
dos.flush();
String rec2 = dis.readUTF();
System.out.println("比较结果:"+rec2);
if(rec2.equals("4A0B")) {
System.out.println("恭喜您猜对了");
break;
}
}
}
}else {
System.out.println("服务器出现问题");
}
}else if(s2 == 2) {
//查看排名
dos.writeUTF(106+",");
dos.flush();
//接收消息
String rec2 = dis.readUTF();
String[] ps = rec2.split(";");
for(String p : ps) {
String[] ts = p.split(",");
for(String t : ts) {
System.out.print(t);
System.out.print("\t");
}
System.out.println();
}
}else if(s2 == 9) {
//退出
dos.writeUTF(102+",");
dos.flush();
System.exit(0);
}
}
}else {
System.out.println("账号密码错误");
}
}else if(s1 == 2) {
//注册
//1.提示用户输入账号
Scanner sc2 = new Scanner(System.in);
System.out.println("请输入账号:");
String account = sc2.nextLine();
//2.提示用户输入密码
Scanner sc3 = new Scanner(System.in);
System.out.println("请输入密码:");
String apass = sc3.nextLine();
//3.将账号和密码发送给服务器 (服务器保存)
dos.writeUTF(101+","+account+","+apass);
dos.flush();
//4.接收服务器的验证结果
String rec = dis.readUTF();
if(rec.equals("OK")) {
//注册成功
System.out.println("注册成功");
}else {
System.out.println("注册失败:账号被使用");
}
}else if(s1 == 9) {
//退出
dos.writeUTF(102+",");
dos.flush();
break;
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}