n1cef1sh's Blog

概况

题目本身不多赘述,从以下五个方面作简要介绍。

1、运行展示

image.png

首先玩家可以选择难度模式,easy的牌堆是4×4,hard则是6×6。选择后显示初始牌堆,输入两个坐标,格式有提醒,数字之间用空格分隔。

image.png

输入后首先展示该两张牌,若不匹配则重新显示背面,同时记录操作步数。

image.png

若匹配则显示牌面内容。

image.png

全部匹配则游戏胜利。

image.png

针对hard模式提供提示功能,输入t则会随机将两张牌翻过来,降低难度。

image.png

还有更过分的作弊功能,输入cheat直接杀死比赛。

2、设计思路

首先总共设计了四个类

image.png

Board类是牌堆,负责设置牌堆大小和内容、初始化牌堆、洗牌以及拓展的小功能,包括提示功能和作弊功能(在第四部分中介绍)。

Card类是单张牌,包含了牌的各种属性,坐标、内容、正反面以及匹配成功的标志位。之所以单独设置一个isDone,是为了防止再次选择已经匹配成功的牌时,会覆盖之前的匹配结果,导致又显示背面。

int x, y;//横纵坐标
int flag = 0;//0是背面,1是正面
String str = ""; //牌面内容
int isDone = 0; //匹配成功的标志

Judge类是判定类,包含了几个关键的规则判断方法。例如检测输入是否合规,每次翻牌后两张是否匹配,以及游戏是否胜利。

Test1就是游戏的主类,首先有两个不同大小的牌堆数组,用于不同难度的模式。初始化各类对象后,根据选择的难度去设置对象中的length属性,从而完成相应的初始化、洗牌、打印、判定等等。

    if(level == 1){
        length = 4;
        board.setLength(length);
        board.setContents(contents1);
        judge.setLength(length);
    }
    if(level == 2){
        length = 6;
        board.setLength(length);
        board.setContents(contents2);
        judge.setLength(length);
    }  

接收输入的时候使用nextLine接收字符串,一次性输入两个坐标,数字之间用空格间隔,这样做的目的是为了进行功能拓展时,可以输入相应的字符串命令激活功能。而将字符串按格式处理成int类型也比较容易,然后判断是否符合输入规定,合规则先显示两张牌的内容,随后判定是否匹配,进行接下来的显示。

3、实现代码

对部分功能的关键代码进行介绍,注解中有说明

3.1 洗牌功能

先把取牌的顺序用随机序列得出,然后按照这个随机的次序对牌堆赋值。

//生成随机序列以用来洗牌
//集合是没有重复的值,LinkedHashSet是有顺序不重复集合
public static Set<Integer> generateRandomArray(int size) {
    Set<Integer> set = new LinkedHashSet<Integer>();
    Random ran = new Random();
    while(set.size() < size) {
        int t = ran.nextInt(size);
        set.add(t);
    }
    return set;
}

//洗牌
public static void shuffleCards(Card[][] card) {
    int t = 0;
    int tt = 0;
    //用一维数组保存生成的随机序列
    Set<Integer> set = generateRandomArray(length*length);
    int[] randomArray = new int[length*length];
    for(Integer value : set) {
        randomArray[tt] = value;
        tt ++;
    }
    //利用生成的随机序列完成洗牌
    for(int i = 0; i < length; i ++) {
        for(int j = 0; j < length; j ++) {
            card[i][j] = new Card(i, j, contents[randomArray[t]]);
            t ++;
        }
    }
}

3.2 每次翻牌判定

//每次翻牌判定
public static void eachSelect(int x1, int x2, int y1, int y2, Card[][] card) {
    //如果两张牌牌面相同
    if(card[x1][y1].str.equals(card[x2][y2].str)) {
        //匹配成功
        card[x1][y1].isDone = 1;
        card[x2][y2].isDone = 1;
        //显示正面
        card[x1][y1].flag = 1;
        card[x2][y2].flag = 1;
    }
    //牌面不同则仍保持原状
    else {
        //已经匹配成功的牌仍显示正面,另一张仍是背面
        if(card[x1][y1].isDone == 1) {
            card[x1][y1].flag = 1;
            card[x2][y2].flag = 0;
        }
        else if(card[x2][y2].isDone == 1) {
            card[x2][y2].flag = 1;
            card[x1][y1].flag = 0;
        }
        else {
            card[x1][y1].flag = 0;
            card[x2][y2].flag = 0;
        }
}

3.3 是否胜利

遍历牌堆,若没有$则翻牌成功。

//是否胜利
public static boolean isWin(Card[][] card) {
    //遍历整个牌堆
    for(int i = 0; i < length; i ++) {
        for(int j = 0; j < length; j ++) {
            if(card[i][j].cardContent().equals("$")) {
                return false;
            }
        }
    }
    System.out.println("=============================");
    //若没有$则成功
    return true;
} ## 4、拓展优化

在牌堆变大时,游戏难度会大幅提升,因此拓展了两个小功能。

4.1 提示功能

//hard模式的提示功能,随机翻两张牌
public static void hardTips(Card [][]card){
    Random ran = new Random();
    int i = ran.nextInt(length);
    int j = ran.nextInt(length);
    card[i][j].isDone = 1;
    card[i][j].flag = 1;
    int ii = ran.nextInt(length);
    int jj = ran.nextInt(length);
    card[ii][jj].isDone = 1;
    card[ii][jj].flag = 1;
}

4.2 作弊功能

//作弊功能,所有翻面,直接杀死游戏
public static void cheat(Card[][]card,int length){
    for(int i = 0; i < length; i ++) {
        for(int j = 0; j < length; j ++) {
            card[i][j].isDone=1;
            card[i][j].flag=1;
        }
    }
}

4.3 其他

最初所有逻辑都在一个类中进行编写,后期在修改优化的时候非常不方便,因此重构了代码(虽然本身也不复杂……),将对象抽象成类,包含了相应的属性状态以及操作方法,在主类中只需要创建对应的对象,按照要求去初始化,调用相应的方法操作即可。
最典型的就是牌堆尺寸这个属性,调用方法时不用每次都传参,只需要在对象中setLength,里面的方法就可以使用这个属性进行操作了。
如果想要进行功能的拓展,也比较方便。例如现在统计了步数,如果按照每次步数排名(类似按照时间排名),就可以把这个方法封装到Card类里调用即可。或者是进一步增加难度,设置更复杂的规则,就可以把规则方法封装到Judge类里,每次匹配的时候进行判定即可。

5、小结

一言难尽,虽然写的稀烂,随便记一下吧。