猜拳游戏
结果呈现
游戏规则类
public class Rule {
/**输入工具:为什么要把Scanner作为属性存在?
* 因为 输入姓名 和 输入有效的数值 都要运用到输入工具 写在属性就不用在各个方法中重复写 节省内存
*/
private final Scanner INPUT = new Scanner(System.in);
//随机工具
private final Random RAND = new Random();
//正则:两个以上的汉字
private final String NAME_REGEX = "^[\u4e00-\u9fa5]{2,}$";
//正则:数值
private final String DIGIT_REGEX = "^[0-9]{1,}$";
//拳谱
private final String FIST = "布、剪、锤";
//拳值数组
private final String[] FISTS;
//无参构造 给拳值数组赋值
public Rule() {
FISTS = FIST.split("、");
}
/**
* 获取控制台输入一个有效昵称
* @param title 输出提示主体
* @return String
*/
public String getInputName(String title){
String name = null;
System.out.printf("请输入"+title+"昵称:");
do{
name = INPUT.nextLine();
if(name.matches(NAME_REGEX))
break;
System.out.println("昵称必须为两个汉字以上的形式!请重新输入:");
}while(true);
return name;
}
/**
* 从控制台获取一个输入的整数
* @param min 取值下限
* @param max 取值上限
* @return
*/
public int getInputInt(String title,int min,int max){
int digit = 0;
do{
System.out.print("请输入"+title+":");
String num = INPUT.next();
if(num.matches(DIGIT_REGEX)){
digit = Integer.parseInt(num);
if(digit >= min && digit <= max){
break;
}
System.out.println("数值必须位于"+min+"~"+max+"之间,请重新输入:");
}
}while(true);
return digit;
}
/**
* 获取控制台输入的有效拳值对应拳值数组的下标(索引)
* @param nickname 出拳玩家的昵称
* @return int 拳值对应的下标
*/
public int getInputFistIx(String nickname){
int fist = -1;
System.out.print(nickname+"出拳:");
do{
String strFist = INPUT.nextLine();
for (int i = 0; i < FISTS.length; i++) {
if(strFist.equals(FISTS[i])){
fist = i;
break;
}
}
if(fist >= 0)
break;
System.out.println("拳值必须为布、剪、锤,请"+nickname+"重新出拳");
}while(true);
return fist;
}
/**
* 单局根据拳值下标之差判断输赢
* @param humanFist 人类玩家的拳值
* @param machineFist 机器玩家的拳值
* @return int(0:平局,1/2:人类玩家胜利,-1/-2:机器玩家胜利)
*/
public int compare(int humanFist,int machineFist){
if(humanFist*machineFist == 0&&humanFist+machineFist == 2){
if(humanFist == 0){
humanFist = 3;
}
else{
machineFist = 3;
}
}
return humanFist - machineFist;
}
/**
* 获取随机生成的拳值(下标) 并且输出机器的拳
* @param nickname 机器玩家的昵称 作为输出提示
* @return int 0~2
*/
public int getRandFistIX(String nickname){
int rst = RAND.nextInt(FISTS.length);
System.out.println(nickname+"出拳:"+FISTS[rst]);
return rst;
}
}
1.简单模板的提取
观察我们可以得到,我们能够从游戏规则类中提取出一些方法模板:
public xxx get(String title){
String line = null;
do{
System.out.println(...)//一个与title拼接的字符串,说明输入信息
line = INPUT.nextLine();
if(line.matches(...)){
//或者是说处于某个范围 等于某个值(条件判断)
break;
}
System.out.println(...)//另一个与title拼接的字符串,说明错误原因
}while(true);
}
我们能够看出在OOP的学习过程中存在典型的方法模板,需要积累。
2.学会用一些数学思想去优化条件判断
if(humanFist*machineFist == 0&&humanFist+machineFist == 2){
因为拳值只可能为0,1,2,而分别代表着布剪锤,即0<1,1<2,2<0,所以如果非一布一锤的情况,值大即胜利,但是当同时出现2和0的时候,谁是0谁就胜利,通过这个优化的条件判断来确定了这种情况。
3.对于这种比较类,我们一般通过程序逻辑自定义比较规则,并将其详细地写在注释中,便于看代码。
4.关键点:将不可比较的拳类,构造出拳值数组,通过输入的字符串与拳值数组匹配得到下标转化为可比较的拳值。
游戏角色类
public class Role extends Rule{
private String nickname;
public Role(String title) {
this.nickname = getInputName(title);
}/* 先传到同包子类的方法中 获取一个昵称 再对属性赋值
而到ROLE这一层还无法确定是要输入的是人类还是机器人的昵称 所以要给构造方法同样的title参数
以便于让human和machine的类调用
*/
/**
* 获取角色昵称
* @return
*/
public String getNickname() {
return nickname;
}//作为提取角色昵称的出口 打破private获取数据的限制
/**
* 角色的标准方法:出拳
* 由于人类角色和机器角色出拳的方式不同 在此处做方法的定义
* * @return int(拳值对应的下标 是判断输赢的依据)
*/
public int showFist(){
return -1;
}
}
1.showFist()抽象方法的差异化实现
机器角色类
public class Machine extends Role {
public Machine(){
super("机器角色");
}
/**
* 重写实现机器角色出拳:随机
*/
@Override
public int showFist() {
return getRandFistIX(getNickname());
}
}
人类角色类
public class Human extends Role{
//可以或缺 不写也能在子类的构造中自动调用父类的构造
public Human(){
super("人类角色");
}
/**
* 重写实现人类角色出拳
* @return
*/
@Override
public int showFist() {
return getInputFistIx(getNickname());//此处可以不用super 原因在于在human里面并没有getNickname() 会自动追踪到父类
}
}
裁判类
public class Judge extends Rule{
/**
* 裁判掌握的信息 就是 属性
*/
private String name;
/**
* 获取裁判名字
* @return String
*/
public String getName() {
return name;
}
/**
* 游戏总局数
*/
private int totalSets;
/**
* 获取游戏的总局数
* @return int
*/
public int getTotalSets() {
return totalSets;
}
/**
* 游戏数据:为了简洁,设计成整形的数组,也可以设计成三个变量,但不够简洁
*/
private int[] data;
public Judge(){
name = getInputName("裁判");
totalSets = getInputInt("总局数",3,10);
data = new int[3]; // data [人类玩家胜局数,机器玩家胜局数,平局数]
}
/**
*
* 裁判执裁判断单局输赢 完成所有游戏数据的赋值
* @param humanName
* @param humanFistInt
* @param machineName
* @param machineFistInt
* @return
*/
public void judge(String humanName,int humanFistInt,String machineName,int machineFistInt){
String whoWin = "胜";
switch(compare(humanFistInt,machineFistInt)){
case 1:
data[0]++;
whoWin = humanName + whoWin;
break;
case 0:
data[2]++;
whoWin = "平局";
break;
case -1:
data[1]++;
whoWin = machineName + whoWin;
break;
}
System.out.println("本局["+whoWin+"]");
}
public void finalJudge(String humanName,String machineName){
String whoWin = "胜";
final int rst = data[0] - data[1];
if(rst > 0){
whoWin += humanName;
}else if(rst == 0){
whoWin = "平局";
}else{
whoWin += machineName;
}
System.out.println("-------最终结果-------");
System.out.println("本次比赛共:"+totalSets+"局");
System.out.println("人类玩家胜:"+data[0]+"局");
System.out.println("机器玩家胜:"+data[1]+"局");
System.out.println("平:"+data[2]+"局");
System.out.println("最终"+whoWin);
}
}
1.学习这种字符串拼接方式
String whoWin = "胜";
whoWin = humanName + whoWin;
游戏主类
public class FistGame {
//建立游戏角色
/**
* 人类角色对象(引用)
*/
private Human human;
/**
* 机器角色对象(引用)
*/
private Machine machine;
/**
* 裁判角色对象(引用)
*/
private Judge judge;
/**
* 游戏业务
*/
public FistGame(){
human = new Human();
machine = new Machine();
judge = new Judge();
}
public void start(){
//提取角色昵称
final String humanName = human.getNickname();
final String machineName = machine.getNickname();
final String judgeName = judge.getName();
//输出控制
System.out.println("------- 双人猜拳游戏 -------");
System.out.println("------- "+humanName+" VS "+machineName+" -------");
System.out.println("------- 本场裁判 [ "+judgeName+" ]");
//游戏进程
final int totalSets = judge.getTotalSets();
for (int i = 1; i <= totalSets; i++) {
System.out.println("第"+i+"局:");
//双方分别出拳
final int humanFistInt = human.showFist();
final int machineFistInt = machine.showFist();
//裁判针对当局执行裁决
judge.judge(humanName,humanFistInt,machineName,machineFistInt);
//输出空行,控制输出格式
System.out.println();
}
//最终执裁
judge.finalJudge(humanName,machineName);
}
}
1.执裁过程是先单局再多局,只有先通过单局给到data数组数据,多局执裁才能利用data数组进行结果输出。
笔者认为,这道题是一道非常好的练习OOP的题目(当然也可以适用于其他语言),首先需要有游戏主类、规则类,再考虑游戏角色,既有Role类与具体角色的继承关系,差异化行为实现,又有考虑到人类和机器角色之间一些共性行为和差异化行为(比如说人类是通过输入的拳类来获取拳值,而机器是通过随机生成拳值。)
2.将角色对象作为类属性,并通过构造方法新创建对象,调用各自的方法。(详见面向对象知识总结之组合关系),因为游戏主类包含了人类,机器,裁判三者,并且游戏主类由多个其他类的对象组成,组成了更复杂的对象结构,所以此处选择该方式。
(请记住一个原则:多用组合 少用继承)