cs61b sp21 project 0 学习心得

代码解读部分

本项目是实现一个基本的2048游戏,本项目提供了大量的初始代码以供参考,设计者本身使用的是MVC模式构建基本代码,可以让我们专注于逻辑设计,忽略大量的高级Java知识和技术

Tile类

tile 类主要在项目中使用到的是tile类下的value方法,提供标准坐标系下(即以北边为最远的边的坐标系)(这种说法还会在下面的side类中提到)的某个点的值

Side 类

side 类主要在项目中起到重定向坐标系的作用,这里主要有四个属性,分别是col0, row0, dcol, drow,其中col0和row0分别用于重定向坐标原点,比如当在以北边为最远的坐标系下,最左下角的点的坐标就是(0, 0), 当以最东边为最远时,最左下角的坐标就是(size, 0), 上述的坐标都是相对于标准坐标系而言的(即以北边为最远的坐标系);而dcol, drow 的作用就是在每个重定向的坐标系下,如果还想像标准坐标系那样向北边移动一个单位,需要对行和列分别加上(dcol, drow)


public enum Side {
NORTH(0, 0, 0, 1), EAST(0, 1, 1, 0), SOUTH(1, 1, 0, -1),
WEST(1, 0, -1, 0);
}

 

接着对重定向的坐标采用坐标变换就可以得到标准坐标系下的坐标

/** Return the standard column number for square (C, R) on a board
     *  of size SIZE oriented with this Side on top. */
    public int col(int c, int r, int size) {
        return col0 * (size - 1) + c * drow + r * dcol;
    }

    /** Return the standard row number for square (C, R) on a board
     *  of size SIZE oriented with this Side on top. */
    public int row(int c, int r, int size) {
        return row0 * (size - 1) - c * dcol + r * drow;
    }

至于这样的坐标变换到底怎么得来,可能需要极好的数学基础,不过我们可以感性理解下,如果当前重定向的坐标系是以最东边为最远的,并且板子长度为5,重定向的坐标为(2, 1),根据上述公式可以算出标准坐标系下的点是(1, 2),画图验证发现成立。

Board 类

board类主要是用来对tile进行管理的,可以让我们从更高的层次改变board的状态,并且可以用于封装,我们所需要用到的方法如下:

move方法可以将tile移动到重定向后的坐标(col, row),注意这里的(col, row)是重定向的坐标

/** Places the Tile TILE at column COL, row ROW where COL and ROW are
     * treated as coordinates with respect to the current viewPerspective.
     *
     * Returns whether or not this move is a merge.
     * */
    public boolean move(int col, int row, Tile tile) {
        int pcol = viewPerspective.col(col, row, size()),
                prow = viewPerspective.row(col, row, size());
        if (tile.col() == pcol && tile.row() == prow) {
            return false;
        }
        Tile tile1 = vtile(col, row, viewPerspective);
        values[tile.col()][tile.row()] = null;

        if (tile1 == null) {
            values[pcol][prow] = tile.move(pcol, prow);
            return false;
        } else {
            values[pcol][prow] = tile.merge(pcol, prow, tile1);
            return true;
        }
    }

tile方法, 可以返回在某个固定的视角(也就是重定向坐标系下)的(col, row)的地方的tile对象

/** Return the current Tile at (COL, ROW), where 0 <= ROW < size(),
     *  0 <= COL < size(). Returns null if there is no tile there. */
    public Tile tile(int col, int row) {
        return vtile(col, row, viewPerspective);
    }

toString方法,可以返回board的状态,便于我们向GUI发送是否改变board状态,以及是否要生成新的随机tile.

@Override
    /** Returns the board as a string, used for debugging. */
    public String toString() {
        Formatter out = new Formatter();
        out.format("%n[%n");
        for (int row = size() - 1; row >= 0; row -= 1) {
            for (int col = 0; col < size(); col += 1) {
                if (tile(col, row) == null) {
                    out.format("|    ");
                } else {
                    out.format("|%4d", tile(col, row).value());
                }
            }
            out.format("|%n");
        }
        return out.toString();
    }

任务部分

我们需要完成的任务都是在Model类中,

任务一:

public static boolean emptySpaceExists(Board b)

这个task是要求我们判断是否有空白的tile在一块板子上, 我们只需要遍历板子即可

/** Returns true if at least one space on the Board is empty.
     *  Empty spaces are stored as null.
     * */
    public static boolean emptySpaceExists(Board b) {
        int size = b.size();
        for (int i = 0; i < size; i++){
            for (int j = 0; j < size; j++){
                if (b.tile(i, j) == null){
                    return true;
                }
            }
        }
        return false;
    }

任务二:

public static boolean maxTileExists(Board b)

这个是让我们判断是否存在游戏结束的另一个条件,即存在2048块,在这里我们不应该直接使用2048这个魔法数字,而是应该使用常量MAX_PIECE

/**
     * Returns true if any tile is equal to the maximum valid value.
     * Maximum valid value is given by MAX_PIECE. Note that
     * given a Tile object t, we get its value with t.value().
     */
    public static boolean maxTileExists(Board b) {
        int size = b.size();
        for (int i = 0; i < size; i++){
            for (int j = 0; j < size; j++){
                if (b.tile(i, j) != null && b.tile(i, j).value() == MAX_PIECE){
                    return true;
                }
            }
        }
        return false;
    }

任务三:

public static boolean atLeastOneMoveExists(Board b)

是否存在至少一个可以移动的,至少一个可以移动就是指有空值,或者上下左右四个方位可以移动,有空值的功能我们在任务一中已经实现,我们只需判断是否有邻居可以合并

 public static boolean atLeastOneMoveExists(Board b) {
        boolean flag = false;
        // 是否存在空白块
        if (emptySpaceExists(b)) return true;
        // 是否存在相邻的相同块
        for (int i = 0; i < b.size(); i++){
 for (int j = 0; j < b.size(); j++){
                if (i < b.size() - 1 && b.tile(i, j).value() == b.tile(i + 1, j).value()){
                    flag = true;
                }
                if (j < b.size() - 1 && b.tile(i, j).value() == b.tile(i, j + 1).value()){
                    flag = true;
                }
            }
        }
        return flag;
    }

任务四:

public boolean tile(Side side)

我们需要对游戏的一次操作进行移动,也就是当用户按下上下左右四个键的时候,我们需要对游戏板进行操作,这里我们需要一次确定每个板上的tile的最终移动位置,这是它所要求的,并且根据规则,我们不可以将合并过的板子再次合并,因此,我们应该以从上往下行的顺序进行合并,而不能从下往上,从上往下的好处是我们可以快速确定每个板子的最终位置,对于列的遍历顺序我们没有要求,从左往右,从右往左都可以,但是我们可以设计一个helper函数帮助我们处理每个列中的合并移动逻辑,这样可以使我们专注于更小的设计逻辑。

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);
        int c = board.size();
        String oldBoard = this.toString();
        for (int i = 0; i <= c - 1; i++) {
            columnHelper(i);
        }
        String newBoard = this.toString();
        if (!oldBoard.equals(newBoard)) {
            changed = true;
        }
        checkGameOver();

        if (changed) {
            setChanged();
        }
        board.setViewingPerspective(Side.NORTH);
        return changed;
    }
    public  void columnHelper(int column) {
        boolean[] st = new boolean[board.size()];
        for (int i = board.size() - 2; i >= 0; i--) {
            // 向上合并
            if (board.tile(column, i) != null) {

                // 向上合并
                moveHelper(column, i, st);
            }
        }
    }
    public  void moveHelper(int column, int row, boolean[] st) {
        int i = row + 1;
        while (i < board.size() - 1) {
            if (board.tile(column, i) == null) {
                i++;
            } else break;
        }

        if (board.tile(column, i) == null) {
            board.move(column, i, board.tile(column, row));
        } else if (!st[i] && board.tile(column, i).value() == board.tile(column, row).value()) {
            st[i] = true;
            board.move(column, i, board.tile(column, row));
            score += board.tile(column, i).value();
        } else {
            board.move(column, i - 1, board.tile(column, row));
        }
    }

测试部分

项目采用junit测试

我们一般采用junit的测试形式是

import org.junit.Test;

import static org.junit.Assert.*;

并且我们需要吧每个测试方法都设计为实例方法,并且在明明函数的时候采取驼峰命名法

下面是一个测试的简单形式

@Test
    /** Checks right two pieces merge when 3 adjacent pieces have same value. */
    public void testTripleMerge1() {
        int[][] before = new int[][]{
                {2, 0, 0, 0},
                {2, 0, 0, 0},
                {2, 0, 0, 0},
                {0, 0, 0, 0},
        };
        int[][] after = new int[][]{
                {4, 0, 0, 0},
                {2, 0, 0, 0},
                {0, 0, 0, 0},
                {0, 0, 0, 0},
        };

        updateModel(before, 0, 0, false);
        String prevBoard = model.toString();
        boolean changed = model.tilt(Side.NORTH);
        checkChanged(Side.NORTH, true, changed);
        checkModel(after, 4, 0, prevBoard, Side.NORTH);
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值