Game 2048
Environment PreparationMain task
`emptySpaceExists(Board b)`
Related classSolutionReview `maxTileExists(Board b)`
Related classSolutionReview `atLeastoneMoveExists(Board b)`
Related classSolutionReview `tilt(Side side)`
Related classSolutionReview Java Basic knowledge
static variable`assert`finalFor-each LOOPIterable<>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2GqVbXqa-1648373110374)(game2048.assets/image-20220327165731872.png)]
参见项目指导书game2048
Main taskemptySpaceExists(Board b)
maxTileExists(Board b)
atLeastoneMoveExists(Board b)
tilt(Side side)
Board
tile method
public Tile tile(int col, int row) {
return vtile(col, row, viewPerspective);
}
TestEmptySpace
共有8个测试案例,其中前6个是棋盘上有空(return true),后两个全满(return false)的
| test | return |
|---|---|
| 1 | true |
| 2 | true |
| 3 | true |
| 4 | true |
| 5 | true |
| 6 | true |
| 7 满 | false |
| 8 满 | false |
Tile(棋子类)
这个类定义了每一个棋子的状态,包括:数值、列、行、和下一个棋子。可以看作是一个有三个值一个指针的链表。
public class Tile {
private Tile(int value, int col, int row) {
this.value = value;
this.row = row;
this.col = col;
this.next = null;
}
createmovemergenext
Model
SolutionSolution 1:
public static boolean emptySpaceExists(Board b) {
// TODO: Fill in this function.
int count = 0; // count the number of empty tiles
int board_size = b.size();
for(int i = 0; i < board_size; ++i){
for(int j = 0; j < board_size; ++j){
if( b.tile(i,j) == null){
count++;
}
}
}
return count != 0;
}
Solution 2: Modified on the solution 1
public static boolean emptySpaceExists(Board b) {
// TODO: Fill in this function.
int board_size = b.size();
for(int i = 0; i < board_size; ++i){
for(int j = 0; j < board_size; ++j){
if( b.tile(i,j) == null){
return true;
}
}
}
return false;
}
Review
第一个task的难度不在于思路而在于:
1. 阅读理解`game2048`文档,配置换进并了解测试程序的使用。 1. 看懂各个类之间的调用关系,每个类所定义的变量、提供的接口。 1. 对于某些看不懂的`Java`前置引用概念的不理解就使用。
思路就是一个对于一个二维数组的遍历,去看每个元素的值是否为null,但是就如指导书里说的那样,封装的Board类中定义了private Tiel[][] values;所以无法直接调用,只能通过Board中的这两个函数进行调用
private Tile vtile(int col, int row, Side side) {
return values[side.col(col, row, size())][side.row(col, row, size())];
}
public Tile tile(int col, int row) {
return vtile(col, row, viewPerspective);
}
于是我们就很清楚,具体该怎么做了,正如项目指导书中写的我们只会用到.size()和.tile()两个方法就可以完成,不过通过在逐个类中分析出来是一种有点累但却很快乐的方式。
maxTileExists(Board b) Related class和emptySpaceExists(Board b)相同
Solution
public static boolean maxTileExists(Board b) {
// TODO: Fill in this function.
int board_size = b.size();
for (int i = 0; i < board_size; ++i)
for (int j = 0; j < board_size; ++j) {
if (b.tile(i, j) == null) {
} else {
if (b.tile(i, j).value() == MAX_PIECE)
return true;
}
}
return false;
}
Review
一开始直接写,没有考虑完整,碰到了这样的错,也就是提示我们要注意是空的位置的棋子的特殊情况。考虑到这点就没问题啦。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tt0n66Oe-1648373110376)(game2048.assets/image-20220326103538932.png)]
atLeastoneMoveExists(Board b) Related class同前
Solution
public static boolean hasSameAdjacentTiles(Board b){
int board_size = b.size();
// test (n-1)*(n-1) size board
for (int i = 0; i < board_size -1; ++i){
for (int j = 0; j < board_size -1 ; ++j){
if ((b.tile(i, j).value() == b.tile(i + 1, j).value()) || (b.tile(i, j).value() == b.tile(i, j + 1).value())){
return true;
}
}
}
// test n_th col
int col = board_size -1;
for (int i = 0; i < board_size - 1; ++i){
if (b.tile(col, i).value() == b.tile(col, i+1).value())
return true;
}
// test n_th row
int row = board_size -1;
for (int j = 0; j < board_size - 1; ++j){
if (b.tile(j, row).value() == b.tile(j+1, row).value())
return true;
}
return false;
}
public static boolean atLeastOneMoveExists(Board b) {
// TODO: Fill in this function.
// condition 1
boolean b1 = true;
if (emptySpaceExists(b))
return b1;
else{ // condition 2
if(hasSameAdjacentTiles(b))
return b1;
else
return false;
}
}
Review
通过一个辅助函数hasSameAdjacentTiles(Board b)来遍历board查看是否有可移动的位置,只需做基础判断就可,比较容易。
tilt(Side side) Related classTileBoardModleMain Solution
// public void moveToghterInline ()
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.
// set the side to be NORTH, if it is not north, therefor easy to manipulate
if (side != Side.NORTH) {
board.setViewingPerspective(side);
}
// press up, and turn all the tiles be adjacent
int board_size = board.size();
for (int colIndex = 0; colIndex < board_size; ++colIndex){ // move all tiles in a line adjacent, col by col
int nullCount = 0; // count how many null tile is before current tile in a line
for (int rowIndex = board_size -1; rowIndex >= 0; --rowIndex){
if(board.tile(colIndex, rowIndex) == null) {
nullCount++;
continue;
}
Tile t = board.tile(colIndex, rowIndex);
if(board.tile(colIndex, rowIndex+nullCount) == null) {
board.move(colIndex, rowIndex + nullCount, t);
changed = true;
}
}
}
// merge if possible
for (int colIndex = 0; colIndex < board_size; ++colIndex){
for (int rowIndex = board_size -1; rowIndex > 0; --rowIndex){
if((board.tile(colIndex, rowIndex) != null) && (board.tile(colIndex, rowIndex -1) != null) && (board.tile(colIndex, rowIndex).value() == board.tile(colIndex, rowIndex - 1).value())){
//Tile currentTile = board.tile(colIndex, rowIndex);
Tile nextTile = board.tile(colIndex, rowIndex-1);
board.move(colIndex, rowIndex, nextTile);
score += nextTile.value()*2;
changed = true;
}
}
}
// turn all tiles together again after merge
for (int colIndex = 0; colIndex < board_size; ++colIndex){ // move all tiles in a line adjacent, col by col
int nullCount = 0; // count how many null tile is before current tile in a line
for (int rowIndex = board_size -1; rowIndex >= 0; --rowIndex){
if(board.tile(colIndex, rowIndex) == null) {
nullCount++;
continue;
}
Tile t = board.tile(colIndex, rowIndex);
if(board.tile(colIndex, rowIndex+nullCount) == null) {
board.move(colIndex, rowIndex+nullCount, t);
}
}
}
// turn the side back, if the origin side is not north
if(side != Side.NORTH) {
board.setViewingPerspective(Side.NORTH);
}
checkGameOver();
if (changed) {
setChanged();
}
return changed;
}
private void checkGameOver() {
gameOver = checkGameOver(board);
}
private static boolean checkGameOver(Board b) {
return maxTileExists(b) || !atLeastOneMoveExists(b);
}
public static boolean emptySpaceExists(Board b) {
// TODO: Fill in this function.
int board_size = b.size();
for(int i = 0; i < board_size; ++i){
for(int j = 0; j < board_size; ++j){
if( b.tile(i,j) == null){
return true;
}
}
}
return false;
}
Review
这确实是整个proj0中最难的一个,难点在于:
- 理解合并的过程,和游戏的规则,把文档提供的Google quiz轻易完成很有必要。
- 虽然不应该提醒,更应该是自己发现,这点搞错的话会做不了的,但是还是想说一下,这里的棋盘是以左下角为原点的,其他就不多说了,自己去javadoc里会慢慢发现的
我自己还有一些没做好的地方是这一部分的代码写得过于冗杂,不够简单,实现的方式也不够巧妙,下一周复习的时候再来思考一下应该怎么优化。
Java Basic knowledge static variable
static variable is a variable that all classes could use.
We generally use static methods to perform an operation that is not dependent upon instance creation.
In order to share a code across all instances of that class, we write that code in a static method:
public static void setNumberOfCars(int numberOfCars) {
Car.numberOfCars = numberOfCars;
}
We also commonly use static methods to create utility or helper classes so that we can get them without creating a new object of these classes.
Just take a look at Collections or Math utility classes from JDK, StringUtils from Apache or CollectionUtils from Spring framework and notice that all methods are static.
assert粗浅理解一下是像写操作系统项目会遇到的assert(expression)函数,类似if-else的简单版,只有确保了expression为真才可以运行之后的语句。
final For-each LOOPIterating over a collection is uglier than it needs to be. Consider the following method, which takes a collection of timer tasks and cancels them:
void cancelAll(Collectionc) { for (Iterator i = c.iterator(); i.hasNext(); ) i.next().cancel(); }
The iterator is just clutter. Furthermore, it is an opportunity for error. The iterator variable occurs three times in each loop: that is two chances to get it wrong. The for-each construct gets rid of the clutter and the opportunity for error. Here is how the example looks with the for-each construct:
void cancelAll(Collectionc) { for (TimerTask t : c) t.cancel(); }
When you see the colon (:) read it as “in.” The loop above reads as “for each TimerTask t in c.” As you can see, the for-each construct combines beautifully with generics. It preserves all of the type safety, while removing the remaining clutter. Because you don’t have to declare the iterator, you don’t have to provide a generic declaration for it. (The compiler does this for you behind your back, but you need not concern yourself with it.)
The for-each construct is also applicable to arrays, where it hides the index variable rather than the iterator. The following method returns the sum of the values in an int array:
>// Returns the sum of the elements of a>
int sum(int[] a) {
int result = 0;
for (int i : a)
result += i;
return result;
}
Iterable<>
public interface Iterable
Implementing this interface allows an object to be the target of the “for-each loop” statement. See For-each Loop
In general, an object Implementing Iterable allows it to be iterated. An iterable interface allows an object to be the target of enhanced for loop(for-each loop).



