用JAVA实现"猜数字"游戏过程中的一点心得——编程时如何思考

(2014年6月9日:高考结束了,尽了人事,剩下的基本上只能听天命了。)
 温习了一下许久没有碰过的JAVA,说实话还真的有些生疏了。于是做了个挺简单的“猜数字”的小游戏,复习了下基本的语法。虽然这个“游戏”简单到基本学过编程就能做,但是自己亲自从思考程序逻辑,设计算法,编码调试,修改,然后再调试,最后差不多没有什么明显的问题,整个过程下来还是很有收获的。一些心得,分享一下, 如果有什么错误也希望大家能够不吝指正。
 首先还是介绍一下这个游戏的玩法,因为很大程度上一个游戏的玩法(规则)就是我们设计程序总的算法,我们的编码总归是要围绕实现“游戏规则”这个目标的。这里的“猜数字”,并不是指的是“生成一个随机数,然后让游戏者猜,然后程序会告诉你高了还是低了,如此循环往复,直到猜中”,它的规则要复杂一些,同时游戏性也更强,它还要求游戏者有一定的推理判断力,当然还有运气。好了,规则如下:
 
 1.程序随机生成一个四位的整数,要求是这个整数每一位都不相同,比如“1234”是符合要求的,而“3453”则不行
 2.用户输入猜测的数字,当然这个数字也必须是四位的,而且每一位都不相同
 3.程序会根据用户输入的数字,给出反馈,反馈的形式是nBmA,含义为用户输入的数字中有(m+n)个数字与生成的随机数(称为目标数)是一样的,其中有n个数字与目标数一样但所处的位置不同,有m个数与目标数一样而且所处的位置也相同。比如:目标数是:2456,输入:5236,那么程序反馈就是:2B1A。因为输入数与目标数中都有2和5,但位置不同,所以n=2;而输入数与目标数中都有6,且都处在相同的位置(都在个位),所以m=1。
 4.五次还未猜出正确数字,那么游戏就失败。

 看到上面的规则,是不是觉得很简单,急于编码。我认为急于下手是一种很不好的习惯,有时候头脑中的快速构想并不一定就是解决问题的理想模型,很可能实现到一半的时候才会发现有致命的缺陷,然后再来重构就会浪费更多的精力,这是经验之谈。就拿这个程序的实现来说,第一次我急于求成,写到一半才发现某些部分虽然可以实现但是非常繁琐,而且一定可以写得更精简一些,但是由于我的代码太过混乱,结构很不好,所以没办法实现。因此,我完全放弃了第一次的代码,先是仔细考虑了整个程序的目标是什么,大致的流程有哪些,有哪些关键的算法,能不能分成不同的模块来减少各部分间的相互影响(耦合)......然后想了一个非常粗略的框架,由宏观再到微观的实现,大方向有了,写起代码来基本上不会“东拉西扯”,写代码的感觉也比较流畅,除了写一些具体的算法会停下来思考,基本上都是一路没有“瓶颈”写下来的。
 好了,上面是一些感受,讲点具体的东西。首先是对规则进行分析,搞清楚它要我们做什么,我们可以做什么,不可以做什么:通览所有规则,发现这个程序涉及到人机交互,也就是输入输出(有些废话了,这是所有游戏最基本的特征之一),所以我们在编码中应该考虑到采用哪种形式的交互,是字符界面还是GUI界面?如果你跟我想的一样,想要实现两种方式,那么势必要考虑任何让界面与业务逻辑尽量相互独立,毕竟无论采取何种表现形式,业务逻辑还是一样的,并不会随之转移。最好,业务逻辑根本不知道有界面的存在,这样界面的改动对业务逻辑来讲是没有任何影响的。所以我就想把业务逻辑抽取出来,并且封装起来,对外只提供输入输出的接口,于是我就创建了一个Logic类(在com.zyzz.Logic包下),来处理游戏的业务逻辑,所有的核心算法和游戏流程的实现都位于此,只提供了Logic.input(int[] in)来接受外部输入。它的构造器是:Logic(OutputHandler handler),而OutputHandler是一个接口(也位于com.zyzz.Logic下),里面有一个onOutput(Object out)的接口方法,每当游戏业务产生了输出都会回调这个方法,达到向外界传递输出信息的目的,总的来说,它相当于一个事件监听器,在发生“输出事件”时被触发。这样Logic的输入输出接口都有了,它与外界(特别是界面)的唯一交流就只能通过这些有限的接口,而内部的实现对外界来说是透明的(即:不可见的),它既不知道Logic内部发生了什么,也无法改变什么,它唯一能做的就是在适当的时候向Logic对象输入一些数据(通过调用Logic.input()),或者接受Logic对象的反馈输出并用适当的发生显示它们(通过实现OutputHandler,并重写onOutput()方法)。这样就达到了两相隔离,互不影响的目的。这样设计是基于“责任”的考虑,责任不同,分工不同,只有各行其道,方能条理清晰,层次分明。下面分别是字符界面的实现和GUI界面的实现,基本上没有什么差异:
 

 //字符界面的游戏循环 
 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.stringToBitsArray(sc.next());
    logic.input(input);//输入数据
   }catch(Exception e){
    System.out.println("DEADLY ERROR");
    break;
   }
      
  }while(logic.getState()==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) {
   if(logic.getState()==Logic.STATE_DEAD) {
    logic.reset();
    textArea.setText("");
   }
   logic.input(RandomTool.stringToBitsArray(textField.getText()));//输入数据
   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}这个序列中的每一个数字随机的交换位置就行了。这个比较另类的算法保证了在确定的步骤内一定能够产生结果,即所谓“确定性”。下面贴出具体的实现代码:

//-------------------------------------------------------------------------------------------------------------------------//
 /**
  * 
  * @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 = rnd.nextInt(10);//随机产生将与第i位交换的位
   int temp = srcNum[i];
   srcNum[i] = srcNum[exchange_bit];
   srcNum[exchange_bit] = temp;
  }
  if (srcNum[0] == 0) {
   int exchange_bit = rnd.nextInt(9) + 1;
   srcNum[0] = srcNum[exchange_bit];
   srcNum[exchange_bit] = 0;
  }
  return Arrays.copyOf(srcNum, bits);
 }
//-------------------------------------------------------------------------------------------------------------------------//



 关于规则2,3,4的实现都比较简单这里不再赘述。

完整的源代码:http://download.csdn.net/detail/zyzzate/7472635
(PS:本人菜鸟,上面如有有偏颇之处望各位大侠指正,谢谢!)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值