CS61B Proj0 2048

376 阅读9分钟

CS61B Proj0 2048

极度建议:

第四个问题tilt 逻辑很复杂, 如果不会测试, 最好先看完3.1, 了解测试的知识后再继续写. 不然计算使用FP的编程方式, 方便测试, 但是语句执行的过程中出现问题的地方, 依然发现不了, 而且对这个问题, 需要很多耐心, 本人从早上6:00 一直到下午14:00 间断性的完成本题, 最后再学习完3.1, 知道测试的方法后, 重新排查, 最后才发现了问题所在.

游戏

游戏背景:

在一个正方形的方格网络中, 每个方格可能是空的, 也可以是一个带有2 或者 2的次方整数.

游戏阶段:

在游戏的起始阶段, 会有随机2(75%可能性) 或者 4(25%可能性)的方格出现在背景板中.

玩家行为:

可以以东南西北四个方向移动方格网络. 图块也会随着这个方向移动.

规则

移动的规则:

  1. 每一次移动, 方格网络中都会有随机数量的图块添加到空方块

合并的规则:

  1. 两个图块如果数值相同, 则移动后会形成一个拥有两个图块数值之和的新图块出现
  2. 合并后出现的新方块不会和其他方块合并
  3. 当两个图块合并时, 玩家会获得合并后的积分作为自身的分数

分数规则:

  1. 每次移动, 都会更新
  2. 更新增加的分数为和新出现的方块的数值之和

输赢规则:

输: 倾斜改变不了棋盘 赢: 有数值为2048的方块出现

可能会感觉到挫折的地方:

其中会有初学者不熟悉的语法, 甚至未来的课程也不会出现.

游戏者中的类:

1. Tile类 -- 方格:

Tile 实例 == null, 表示为空的方块

我们只需要使用Tile的实例t的value方法, t.value()会返回方块的数值

2. Side类 -- 倾斜的方向:

Side类是一种特殊的类: Enum 我们不需要使用任何该类的任何方法, 也不需要操作它的实例变量

我们只需要使用该类的一种方法: 类属性的赋值.(Enum不能使用new关键字构造一个新的实例, 而是使用类属性传递数值)

Side s == Side.NORTH 
复制代码

3. Model类 -- 模型:

该类代表整个有戏的状态. 我们只需要编辑该文件即可.

4. Board类 -- 背景:

这个代表游戏的方格网络背景.

image.png可以假想为一个二元数组: 以列为索引, 按照从左往右的顺序存储数组.

每个数组再以行为索引, 从下往上存储元素.

我们只需要使用该类的以下三个实例方法:

  1. board.setViewingPerspective(Side) : 将board的Side方向设置为NORTH方向. (即方向标准以Side为NORTH方向为标准, 旋转board)
  2. board.tile(i, j) : 返回i列j行的tile的数值
  3. board.move(i, j, tile) : 移动board中的tile到board的(i列j行)中

任务

完成四个函数的逻辑

  1. emptySpaceExists
  2. maxTileExists
  3. atLeastMoveExists (相对困难)
  4. tilt (预计需要3 - 10h才能完成)

1. public static boolean emptySpaceExists(Board b)

要求:

  1. 如果棋盘中的任何一个图块为空, 这个方法返回True, 否则返回为False. 提示:
  2. 我们只需要使用Board的实例方法 tile 和 size
  3. Board使用对其数据使用private关键字, 我们不能直接使用这些数据. 只能使用操作这些数据的方法. (数据抽象)

使用的方法介绍:

  1. tile方法 接收两个参数, 列 和 行, 返回Board实例中对应行和类的方块的返回值.
  2. size | 不接受参数, 返回Board实例中的行和列的长度.

解决方法: 遍历二维数组, 判断其中的元素是否有null

可能出现的问题: board实例中的存储的不一定是tile, 还有可能是null.如果是null,不可访问属性值

public static boolean emptySpaceExists(Board b) {
    // TODO: Fill in this function. check Board have empty tile.
    for (int i = 0; i < b.size(); i += 1) {
        for (int j = 0; j < b.size(); j += 1) {
            if (b.tile(i, j) == null) {
                return true;
            }
        }
    }
    return false;
}
复制代码

2. public static boolean maxTileExists(Board b)

要求: 如果Board实例中有一个方格的数值为2048. 返回True. 但是我们不应该使用2048这个数值, 而是使用MAX_PIECE这个变量代替2048.

解决思路: 遍历二维数组, 判断其中的元素是否有2048.

public static boolean maxTileExists(Board b) {
    // TODO: Fill in this function.

    /*
    * this is problem is using null compare with int, but they can't compare
    * so that it need to add condition that t is null type.
    * */

    /*
    * null can only compare with object rather than other primitive type
    * */
    for (int i = 0; i < b.size(); i += 1) {
        for (int j = 0; j < b.size(); j += 1) {
            if (b.tile(i, j) != null && b.tile(i, j).value() == MAX_PIECE) {
                return true;
            }
        }
    }
    return false;
}
复制代码

3. public static boolean atLeastOneMoveExists(Board b)

要求: 如果使用了倾斜了, 方格网络中的方格还可以移动, 返回True, 否则返回False

有效移动的条件:

  1. 有一个方格为null
  2. 有一个方格为2048
  3. 两个相邻的图块的数值为相同的值

解题方法:

  1. 对于条件1和2:

直接使用之前的两个方法即可.

  1. 对于条件3:

遍历i-1列 和 j-1行的二维数组. 对其中的每个元素进行一次左右和上下比较.

因为不能覆盖最后一列的上下比较 和 最后一行的左右比较. 所以添加一下特殊条件

当为i - 1 列的时候进行一次 (i + 1, j) 和 (i + 1, j + 1) 的比较.

当为j - 1 行的时候进行一次 (i, j + 1) 和 (i + 1, j + 1) 的比较.

但是本人因为懒, 直接将逻辑整合到了一起.

public static boolean atLeastOneMoveExists(Board b) {
    // TODO: Fill in this function.
    if (emptySpaceExists(b) || maxTileExists(b)) {
        return true;
    }
    for (int i = 0; i < b.size() - 1; i += 1) {
        for (int j = 0; j < b.size() - 1; j += 1) {
            if (b.tile(i, j) != null && b.tile(i + 1, j) != null) {
                int left = b.tile(i, j).value();
                int right = b.tile(i + 1, j).value();
                if (left == right) {
                    return true;
                }
            }
            if (b.tile(i, j) != null && b.tile(i, j + 1) != null) {
                int up =  b.tile(i, j).value();
                int down = b.tile(i, j + 1).value();
                if (up == down) {
                    return true;
                }
            }
            if (b.tile(i + 1, j + 1) != null && b.tile(i + 1, j) != null)  {
                if (b.tile(i + 1, j + 1).value() == b.tile(i + 1, j).value()) {
                    return true;
                }
            }
            if (b.tile(i + 1, j + 1) != null && b.tile(i, j + 1) != null) {
                if (b.tile(i + 1, j + 1).value() == b.tile(i, j + 1).value()) {
                    return true;
                }
            }

        }
    }
    return false;
}
复制代码

4. public boolean tilt(Side side)

这是游戏的主要逻辑 -- 控制方格的移动, 但是很困难. 我们从这个问题中, 可以初步意识到到计算机的本质 就是 管理复杂性.

要求:

  1. 按照side方向, 移动棋盘. 如果棋盘发生了变化. 将提供的骨骼中的changed变量设置为true.
  2. 记录因为合并产生的图块的数据总值. 使用score(score为Model类中的私有变量)
score += board.tile(i, j).value()
复制代码
  1. 所有的移动由Board实例的move方法实现. 每次移动对于给定图块调用一次move.
  2. 每一次访问方格使用Board类的tile方法进行访问.

方法:

  1. 有一个TestUpOnly类, 提供了NORTH方向的测试.
  2. 该测试使用的背景假想为行从下往上(0-3). 列从左往右(0-3).
  3. 在每列(以0为准)中, 顶行(0,3)元素不变, 所有的操作从次顶行(0,2)元素 至 最后一行(0,0)元素.
  4. 如果上行为空, 下行的元素可以往上移动.
  5. 如果上行元素的数值 和 下行元素的数值相同. 也可以移动到改行, move方法自动在该位置自动建立一个新数值(为原先两倍)的方块.
  6. move的使用方法, 不是一格一格的移动(诸如: 1->2, 2->3), 而是整体移动. 诸如 TIle t = board.tile(0, 0) 它可以到达(0, 2). 那么直接使用board.move(0, 2, t) 7.Board实例有一个setViewPerspective(SIde s)方法, 可以改变背景的方向.
    输入一个方向, 背景板则以该方向为NORTH方向.
    诸如输入SOUTH, 则原本的SOUTH为NORTH方向. 如果想要将背景恢复原来的方向, 只需要进行调用board.setViewPerspective(Side.North)即可.

解题思路:

  1. 将元素的移动以列为单位分为4组.

  2. 建立一个val数组收集元素, 便于接下来的比较.

  3. 从次高元素开始到最底层元素每个元素寻找上方是否有空格, 是否可以合并.

    1. 往上方寻找是否有可以移动的元素

      1. 上方有空的Tile
      2. 上方有数值和需要移动的Tile数值一样的方格

    board实例中需要移动的tile的前后位置. 所以我们想要完成这些操作, 主要确定的是j的位置.
    而后通过j的位置进行更改.

    Tile t = board.tile(i,j).value();
    board.move(i, new_j, t) 
    val[j] = 0;
    val[new_j] 
    复制代码
    
    1. score 和 val数组: 因为逻辑不同, 不能将两者的逻辑整合到一起.

      1. 如果移动到达的位置空格, score不变. val初始位置置0, 新位置置原来位置的数值
      2. 如果移动到达的位置不是空格, score改变. val初始位置置0, 新位置置-1(因为不能继续合并了)
public boolean tilt(Side side) {
    boolean changed;
    changed = false;

    // TODO: Modify this.board (and perhaps this.score) to account
    // for the tilt to the Side SIDE. If the board changed, set the
    // changed local variable to true.
    board.setViewingPerspective(side);

    for(int i = 0; i < size(); i += 1) {
        int[] val = col_tile(board, i);
        // 出现问题的地方, 将val[j] != 0 渐入suite中, 不然达不到条件直接直接返回StopIteration.
        for (int j = size() - 2; j >= 0; j -= 1) {
            if (val[j] == 0) {
                continue;
            }
            Tile t = board.tile(i, j);
            // 选择是有空格
            int zero = zero_row(val, j);
            // 1. 有空格
            if (zero != j) {
                board.move(i, zero, t);
                val[j] = 0;
                val[zero] = board.tile(i, zero).value();
                changed = true;
                // 移动到空格处了, 看是否可以合并.   如果可以合并. 如果不可以合并, 不需要操作
                if (up_equal(val, zero, zero)) {
                    val[zero] = 0;
                    val[zero + 1] = -1;
                    t = board.tile(i, zero);
                    board.move(i, zero + 1, t);
                    score += board.tile(i, zero + 1).value();
                    changed = true;
                }
                // 2. 没有空格
            } else {
                if (up_equal(val, j, j)) {
                    val[j] = 0;
                    val[zero + 1] = -1;
                    t = board.tile(i, j);
                    board.move(i, j + 1, t);
                    score += board.tile(i, j + 1).value();
                    changed = true;
                }
            }

        }
    }

    board.setViewingPerspective(Side.NORTH);
    checkGameOver();
    if (changed) {
        setChanged();
    }

    return changed;
}

/**  将Board实例中的第i列元素收集到一个列表中 */
public static int[] col_tile(Board b, int i) {
    int[] test = new int[b.size()];
    for (int j = 0; j < b.size(); j += 1) {
        if (b.tile(i,j) == null) {
            test[j] = 0;
        } else {
            test[j] = b.tile(i,j).value();
        }
    }
    return test;
}

/** 返回数组lst第i个元素之后最后一个0的位置 */
public static int zero_row(int[] lst, int i) {
    int res = i + 1;
    for (res = i + 1; res < lst.length; res += 1) {
        if (lst[res] != 0) {
            return res - 1;
        }
    }
    return  res - 1;
}

// 这个出现了问题, 实际想要解决的问题  和  这个函数不一致
/** 判断数组当前值和zero + 1 值是否相等 */
public static boolean up_equal(int[] lst, int zero, int j) {
    if (zero + 1 >= lst.length) {
        return false;
    }
    int test1 = lst[j];
    int test2 = lst[zero + 1];
    return test1 == test2;
}