⽤JAVA实现猜数字游戏过程中的⼀点⼼得——编程时如何思
温习了⼀下许久没有碰过的JAVA,说实话还真的有些⽣疏了。于是做了个挺简单的“猜数字”的⼩游戏,复习了下基本的语法。虽然这个“游戏”简单到基本学过编程就能做,但是⾃⼰亲⾃从思考程序逻辑,设计算法,编码调试,修改,然后再调试,最后差不多没有什么明显的问题,整个过程下来还是很有收获的。⼀些⼼得,分享⼀下, 如果有什么错误也希望⼤家能够不吝指正。
⾸先还是介绍⼀下这个游戏的玩法,因为很⼤程度上⼀个游戏的玩法(规则)就是我们设计程序总的算法,我们的编码总归是要围绕实
现“游戏规则”这个⽬标的。这⾥的“猜数字”,并不是指的是“⽣成⼀个随机数,然后让游戏者猜,然后程序会告诉你⾼了还是低了,如此循环往复,直到猜中”,它的规则要复杂⼀些,同时游戏性也更强,它还要求游戏者有⼀定的推理判断⼒,当然还有运⽓。好了,规则如下:
1.程序随机⽣成⼀个四位的整数,要求是这个整数每⼀位都不相同,⽐如“1234”是符合要求的,⽽“3453”则不⾏
2.⽤户输⼊猜测的数字,当然这个数字也必须是四位的,⽽且每⼀位都不相同
3.程序会根据⽤户输⼊的数字,给出反馈,反馈的形式是nBmA,含义为⽤户输⼊的数字中有(m+n)个数字与⽣成的随机数    (称为⽬标数)是⼀样的,其中有n个数字与⽬标数⼀样但所处的位置不同,有m个数与⽬标数⼀样⽽且所处的位置也相同。⽐如:⽬标数是:2456,输⼊:5236,那么程序反馈就是:2B1A。因为输⼊数与⽬标数中都有2和5,但位置不同,所以n=2;⽽输⼊数与⽬标数中都有6,且都处在相同的位置(都在个位),所以m=1。
4.五次还未猜出正确数字,那么游戏就失败。
看到上⾯的规则,是不是觉得很简单,急于编码。我认为急于下⼿是⼀种很不好的习惯,有时候头脑中的快速构想并不⼀定就是解决问题的理想模型,很可能实现到⼀半的时候才会发现有致命的缺陷,然后再来重构就会浪费更多的精⼒,这是经验之谈。就拿这个程序的实现来说,第⼀次我急于求成,写到⼀半才发现某些部分虽然可以实现但是⾮常繁琐,⽽且⼀定可以写得更精简⼀些,但是由于我的代码太过混乱,结构很不好,所以没办法实现。因此,我完全放弃了第⼀次的代码,先是仔细考虑了整个程序的⽬标是什么,⼤致的流程有哪些,有哪些关键的算法,能不能分成不同的模块来减少各部分间的相互影响(耦合)......然后想了⼀个⾮常粗略的框架,由宏观再到微观的实现,⼤⽅向有了,写起代码来基本上不会“东拉西扯”,写代码的感觉也⽐较流畅,除了写⼀些具体的算法会停下来思考,基本上都是⼀路没有“瓶颈”写下来的。
好了,上⾯是⼀些感受,讲点具体的东西。⾸先是对规则进⾏分析,搞清楚它要我们做什么,我们可以做什么,不可以做什么:通览所有规则,发现这个程序涉及到⼈机交互,也就是输⼊输出(有些废话了,这是所有游戏最基本的特征之⼀),所以我们在编码中应该考虑到采⽤哪种形式的交互,是字符界⾯还是GUI界⾯?如果你跟我想的⼀样,想要实现两种⽅式,那么势必要考虑任何让界⾯与业务逻辑尽量相互独⽴,毕竟⽆论采取何种表现形式,业务逻辑还是⼀样的,并不会随之转移。最好,业务逻辑根本不知道有界⾯的存在,这样界⾯的改动对业务逻辑来讲是没有任何影响的。所以我就想把业务逻辑抽取出来,并且封装起来,对外只提供输⼊输出的接⼝,于是我就创建了⼀个Logic 类(在Logic包下),来处理游戏的业务逻辑,所有的核⼼算法和游戏流程的实现都位于此,只提供了Logic.input(int[] in)来接受外部输⼊。它的构造器是:Logic(OutputHandler handler),⽽OutputHandler是⼀个接⼝(也位于Logic下),⾥⾯有⼀个onOutput(Object out)的接⼝⽅法,每当游戏业务产⽣了输出都会回调这个⽅法,达到向外界传递输出信息的⽬的,总的来说,它相当于⼀个事件,在发⽣“输出事件”时被触发。这样Logic的输⼊输出接⼝都有了,它与外界(特别是界⾯)的唯⼀交流就只能通过这些有限的接⼝,⽽内部的实现对外界来说是透明的(即:不可见的),它既不知道Logic内部发⽣了什么,也⽆法改变什么,它唯⼀能做的就是在适当的时候向Logic对象输⼊⼀些数据(通过调⽤Logic.input()),或者接受Logic对象的反馈输出并⽤适当的发⽣显⽰它们(通过实现OutputHandler,并重写onOutput()⽅法)。这样就达到了两相隔离,互不影响的⽬的。这样设计是基于“责任”的考虑,责任不同,分⼯不同,只有各⾏其道,⽅能条理清晰,层次分明。下⾯分别是字符界⾯的实现和GUI界⾯的实现,基本上没有什么差异:
[jav a]
1.  //字符界⾯的游戏循环
2.  Logic logic=new Logic(new OutputHandler(){
3.    @Override
4.    public void onOutput(Object val) {
5.    System.out.println(val);//处理输出反馈
6.    }
7.
8.  });
9.  Scanner sc=new Scanner(System.in);
10.
11.  do{
12.    System.out.print("Your Number:");
13.    try{
14.    final int[] input = RandomTool.());
15.    logic.input(input);//输⼊数据
16.    }catch(Exception e){
17.    System.out.println("DEADLY ERROR");
18.    break;
19.    }
20.
21.  }State()==STATE_ACTIVE);
22.  System.out.println("Game Over!");
23.  //-------------------------------//
24.  //GUI界⾯的游戏循环
25.
26.  private final Logic logic=new Logic(new OutputHandler(){
java生成随机数的方法
27.
28.  @Override
29.  public void onOutput(Object val) {
30.    textArea.setText((String)val);//处理输出反馈
31.  }
32.
33.  });
34.  private final Action action = new SwingAction();
35.  ......此处省略界⾯初始化代码
36.  private class SwingAction extends AbstractAction {
37.  /**
38.    *
39.    */
40.  public SwingAction() {
41.    putValue(NAME, "Guess");
42.    putValue(SHORT_DESCRIPTION, "cilck to guess a number");
43.  }
44.  public void actionPerformed(ActionEvent e) {
45.    State()==Logic.STATE_DEAD) {
46.    set();
47.    textArea.setText("");
48.    }
49.    logic.input(RandomTool.Text()));//输⼊数据
50.    textField.setText("");
51.  }
52.  }
53.
54. //---------------------------------------//
54. //---------------------------------------//
//字符界⾯的游戏循环
Logic logic=new Logic(new OutputHandler(){
@Override
public void onOutput(Object val) {
System.out.println(val);//处理输出反馈
}
});
Scanner sc=new Scanner(System.in);
do{
System.out.print("Your Number:");
try{
final int[] input = RandomTool.());
logic.input(input);//输⼊数据
}catch(Exception e){
System.out.println("DEADLY ERROR");
break;
}
}State()==STATE_ACTIVE);
System.out.println("Game Over!");
//-------------------------------//
//GUI界⾯的游戏循环
private final Logic logic=new Logic(new OutputHandler(){
@Override
public void onOutput(Object val) {
textArea.setText((String)val);//处理输出反馈
}
});
private final Action action = new SwingAction();
......此处省略界⾯初始化代码
private class SwingAction extends AbstractAction {
/**
*
*/
public SwingAction() {
putValue(NAME, "Guess");
putValue(SHORT_DESCRIPTION, "cilck to guess a number");
}
public void actionPerformed(ActionEvent e) {
State()==Logic.STATE_DEAD) {
textArea.setText("");
}
logic.input(RandomTool.Text()));//输⼊数据  textField.setText("");
}
}
//---------------------------------------//
可以看到上⾯代码基本是神似的,唯⼀的区别就在于字符界⾯游戏循环要靠⼀个while循环来⽀撑,⽽因为GUI程序窗体有⾃⼰的⽣命周期,所以游戏循环是靠它本⾝的⼀些事件⽀持起来的。
我们只看到程序⾥出现⼀个logic对象,却不知道它到底做了什么,当然,对于上⾯的的代码来说更是不需要知道logic⾥发⽣了什么,但⼀些具体的业务逻辑,我觉得还是很有必要讲⼀下的。
分析规则1,我们会得出这样⼀个结论:我需要⼀个可以产⽣不重复的4位数字的算法,这是⼀个核⼼算法。⽹上流传了许多这⽅⾯的算法,其中有⼀些有缺陷(⽐如理论上会陷⼊死循环,不符合算法的确定性,有穷性),有⼀些实在没看懂,这⾥提供⼀个我⾃⼰想出来的,路⼦⽐较“野”的算法,讲⼀下,当初思考的过程:
当时考虑到这个算法的要求有两个:⼀是随机性,⼆是不重复。第⼀个要求⽐较简单,使⽤java提供的Random⼯具就⾏了,第⼆个要求实现起来要考虑的细节⽐较多,⽐如说如果采⽤“Step1.先随机产⽣⼀个数字——>Step2.判断有没有出现过,若出现过则——
>Step1”这种思路严格说起来是不符合算法设计要求的,因为存在这样⼀种可能:每次随机产⽣的数字都是已经出现过的——虽然这是⼩概率事件,基本上不可能出现,但它仍然是不确定的。既然如此,不如换种思路:先保证数字是不重复的再保证数字序列是随机的。虽然这种思路只是把实现的顺序换了⼀下,但实现起来却要简单的多。想⼀想,怎样才能保证产⽣的数字序列⼀定是不重复的?换个问法,我们最多能够保证多少位的数字序列是不重复的?后⼀个问题,在⼗进制的条件下,我们能保证最多⼗位数字序列是不重复的,因为⼗进制中只有{0,1,2,3,4,5,6,7,8,9}这⼗个基数,所以超过⼗位的⼀个数字序列必定有重复的数字。弄清楚这个,要确保⼀个四位的数字序列不重复,只要保证这个四位的数字序列是{0,1,2,3,4,5,6,7,8,9}的⼀个⼦序列就⾏了。⾄于随机性,只需要让{0,1,2,3,4,5,6,7,8,9}这个序列中的每⼀个数字随机的交换位置就⾏了。这个⽐较另类的算法保证了在确定的步骤内⼀定能够产⽣结果,即所谓“确定性”。下⾯贴出具体的实现代码:
[jav a]
1. //-------------------------------------------------------------------------------------------------------------------------//
2.  /**
3.  *
4.  * @param bits
5.  *            要产⽣的不重复的随机数的位数,因为⼗进制只有{0,1,2,3,4,5,6,7,8,9}共⼗个数字,输⼊位数不能够超过10位
6.  * @return 总共bits位,且各个位互不相同的随机数
7.  */
8.  public static int[] randNonRepeated(int bits) {
9.  if (bits > 10 || bits <= 0)
10.    throw new RuntimeException(
11.      "illegal arg:bits must range from 0 to 10!");
12.  final int[] srcNum = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
13.  for (int i = 0; i < 10; i++) {
14.    int exchange_bit = Int(10);//随机产⽣将与第i位交换的位
15.    int temp = srcNum[i];
16.    srcNum[i] = srcNum[exchange_bit];
17.    srcNum[exchange_bit] = temp;
18.  }
19.  if (srcNum[0] == 0) {
20.    int exchange_bit = Int(9) + 1;
21.    srcNum[0] = srcNum[exchange_bit];
22.    srcNum[exchange_bit] = 0;
23.  }
24.  pyOf(srcNum, bits);
25.  }
26. //-------------------------------------------------------------------------------------------------------------------------//
/
/-------------------------------------------------------------------------------------------------------------------------//
/**
*
* @param bits
*            要产⽣的不重复的随机数的位数,因为⼗进制只有{0,1,2,3,4,5,6,7,8,9}共⼗个数字,输⼊位数不能够超过10位  * @return 总共bits位,且各个位互不相同的随机数
*/
public static int[] randNonRepeated(int bits) {
if (bits > 10 || bits <= 0)
throw new RuntimeException(
"illegal arg:bits must range from 0 to 10!");
final int[] srcNum = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (int i = 0; i < 10; i++) {
int exchange_bit = Int(10);//随机产⽣将与第i位交换的位
int temp = srcNum[i];
srcNum[i] = srcNum[exchange_bit];
srcNum[exchange_bit] = temp;
}
if (srcNum[0] == 0) {
int exchange_bit = Int(9) + 1;
srcNum[0] = srcNum[exchange_bit];
srcNum[exchange_bit] = 0;
}
pyOf(srcNum, bits);
}
//-------------------------------------------------------------------------------------------------------------------------//
关于规则2,3,4的实现都⽐较简单这⾥不再赘述。
(PPS:⽂章本来是我写在CSDN上的,发现⾃⼰还有个OSChina账号,故随⼿挪过来)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。