2048游戏的正确方块移动

huangapple go评论75阅读模式
英文:

Correct tile movement for a 2048 game

问题

我决定制作一个2048命令行版,但是我在获取正确的方块移动方式方面遇到了问题...

我的当前结构是一个二维整数数组(4x4)的棋盘。当接收到输入时,它将尝试将该方向上的每个方块向前推动(忽略值为0的方块),如果注意到有变化,就会重新开始(因为底行的方块需要一直向上移动,而不仅仅是向上移动一步)。然而,这样做的一个副作用是以下问题:
[2][2][4] 使用命令 -> 应该得到 [0][4][4],但由于重新开始,程序将能够合并4和4,并得到一个 [0][0][8]。
另一个困难的问题是 [4][4][8][8] 应该得到 [0][0][8][16],所以我不能在合并后停止。

下面的代码是我的 processCommand 函数。它接受一个棋盘和一个输入(可以是 "d"、"u"、"l" 或 "r"。如果游戏注意到游戏结束,它将将 "gameover" 作为输入)。这段代码不太漂亮,我试图使用一个 for 循环来移动方块(比如,如果你输入 "l",那么 horiz 的值将为 -1,如果你输入 "r",那么 horiz 的值将为 1,然后我将方块水平移动 horiz 个位置,但我无法使其工作)。

对于如何解决这个问题(以及对我的编程的批评),你有什么想法吗?将不胜感激!

func processCommand(board [][]int, input string) {

	board_new := board

	switch input {
		case "d":
			for i := 0; i < height - 1; i++ {
				for j := 0; j < width; j++ {

					if board[i][j] == 0 {
						continue
					}

					if board[i + 1][j] == 0 || board[i + 1][j] == board[i][j] {
						board_new[i + 1][j] = board[i + 1][j] + board[i][j]
						board_new[i][j] = 0
						i = 0
						j = 0
						change = true
					}
				}
			}

		case "u":
			for i := 1; i < height; i++ {
				for j := 0; j < width; j++ {

					if board[i][j] == 0 {
						continue
					}

					if board[i - 1][j] == 0 || board[i - 1][j] == board[i][j] {
						board_new[i - 1][j] = board[i - 1][j] + board[i][j]
						board_new[i][j] = 0
						i = 1
						j = 0
						change = true
					}
				}
			} 

		case "l":
			for i := 0; i < height; i++ {
				for j := 1; j < width; j++ {

					if board[i][j] == 0 {
						continue
					}

					if board[i][j - 1] == 0 || board[i][j - 1] == board[i][j] {
						board_new[i][j - 1] = board[i][j - 1] + board[i][j]
						board_new[i][j] = 0
						i = 0
						j = 1
						change = true
					}
				}
			}

		case "r":
			for i := 0; i < height; i++ {
				for j := 0; j < width - 1; j++ {

					if board[i][j] == 0 {
						continue
					}

					if board[i][j + 1] == 0 || board[i][j + 1] == board[i][j] {
						board_new[i][j + 1] = board[i][j + 1] + board[i][j]
						board_new[i][j] = 0
						i = 0
						j = 0
						change = true
					}
				}
			}

		case "gameover":
			gameOver = true

		default:
			processCommand(board, input)
	}

	board = board_new
}
英文:

I decided to make a 2048 Command Line Edition but I'm having trouble getting the right tile movement...

My current structure is that the board is a 2D array (4x4) of ints. When an input is received, it will try to push every tile in that direction (ignoring tiles with value 0), if it notices a change it will start over (Because a tile on the bottom row will have to go all the way up, not just one step up). However, a side effect of this is the following problem:
[2][2][4] with the command -> should give [0][4][4] but since it starts over, the program will be able to merge 4 and 4 and get a [0][0][8] intead...
Another difficult problem is the [4][4][8][8] which should give [0][0][8][16] so I can't just stop after a merge.

The code below is my processCommand function. It takes a board and an input (which is "d", "u", "l", or "r". If the game notices gameover it will put "gameover" as input). It's not very pretty and I've tried to make a single for loop for moving tiles (Like if you write "l" a value horiz will be -1 and if you write "r" it will be 1 and then I move the tile horiz spots horizontal but I couldn't make that work).

Any ideas on how to do this (and critique on my programming) would be greatly appreciated!

func processCommand(board [][]int, input string) {
board_new := board
switch input {
case &quot;d&quot;:
for i := 0; i &lt; height - 1; i++ {
for j := 0; j &lt; width; j++ {
if board[i][j] == 0 {
continue
}
if board[i + 1][j] == 0 || board[i + 1][j] == board[i][j] {
board_new[i + 1][j] = board[i + 1][j] + board[i][j]
board_new[i][j] = 0
i = 0
j = 0
change = true
}
}
}
case &quot;u&quot;:
for i := 1; i &lt; height; i++ {
for j := 0; j &lt; width; j++ {
if board[i][j] == 0 {
continue
}
if board[i - 1][j] == 0 || board[i - 1][j] == board[i][j] {
board_new[i - 1][j] = board[i - 1][j] + board[i][j]
board_new[i][j] = 0
i = 1
j = 0
change = true
}
}
} 
case &quot;l&quot;:
for i := 0; i &lt; height; i++ {
for j := 1; j &lt; width; j++ {
if board[i][j] == 0 {
continue
}
if board[i][j - 1] == 0 || board[i][j - 1] == board[i][j] {
board_new[i][j - 1] = board[i][j - 1] + board[i][j]
board_new[i][j] = 0
i = 0
j = 1
change = true
}
}
}
case &quot;r&quot;:
for i := 0; i &lt; height; i++ {
for j := 0; j &lt; width - 1; j++ {
if board[i][j] == 0 {
continue
}
if board[i][j + 1] == 0 || board[i][j + 1] == board[i][j] {
board_new[i][j + 1] = board[i][j + 1] + board[i][j]
board_new[i][j] = 0
i = 0
j = 0
change = true
}
}
}
case &quot;gameover&quot;:
gameOver = true
default:
processCommand(board, input)
}
board = board_new

}

答案1

得分: 6

首先,你的合并问题。

目前,你总是从上到下、从左到右扫描方块,与玩家的移动方向无关。然而,对于2048游戏来说,最好根据玩家的移动方向来扫描,因为只有在那个方向上才会发生方块合并。例如,让我们看下面的情况:

0	0	2	0	|
0	0	2	2	| 玩家移动
0	2	4	8	v
2	32	4	2

假设玩家的移动方向是向下,所以我们从底部向上开始扫描。在第三列中,我们需要先合并4+4,然后是2+2,即从底部到顶部。按照这个方向,你可以合并4+4,然后将该列的最底部方块标记为已合并,从而不允许进一步合并(用括号表示):

0	0	0	0	|
0	0	2	2	| 玩家移动
0	2	2	8	v
2	32	(8)	2

合并完最底部的方块(如果可能的话),我们继续处理上面的方块,依此类推...

0	0	0	0	|
0	0	0	2	| 玩家移动
0	2	(4)	8	v
2	32	(8)	2
[...]
0	0	(0)	0	|
0	0	(0)	2	| 玩家移动
0	2	(4)	8	v
2	32	(8)	2

当不再有可合并的方块时,移动结束,所有的“已合并”标记都被移除,然后等待下一轮。这种方法可以解决你的多次合并问题。

另一个关于扫描方向的例子(数字现在表示循环遍历方块的顺序):

玩家移动
---->
4	3	2	1
8	7	6	5
12	11	10	9
16	15	14	13

关于你的代码。

看着代码,人们会注意到在循环中有很多重复的代码。对于每个case,你都有一个单独的嵌套for循环,这并不是最优的做法。相反,你可以尝试这样做:

for i := 1; i < height; i++ {
for j := 0; j < width; j++ {
if board[i][j] == 0 {
continue
}
switch input {
case "d":
updateBoardDown(board, i, j)
case "u":
updateBoardUp(board, i, j)
[...]
}
}
}
英文:

First your merging problem

Currently you're always scanning your tiles top-to-bottom and left-to-right, independently of the player's move. For 2048, however, it is preferable to scan in the opposite direction of the player's move, since tiles will only be merged in that direction. For example, let's take the following scenario:

0	0	2	0	|
0	0	2	2	| Player move
0	2	4	8	v
2	32	4	2

Let's assume the player's move direction is towards the bottom, so we start scanning from the bottom towards the top. In the third column, we'd need to first merge 4+4 and then 2+2, i.e. bottom-to-top. Going in that direction allows you to merge 4+4 and then mark the bottom-most field of the column as merged, thus not allowing further merges (indicated by the parentheses around the number):

0	0	0	0	|
0	0	2	2	| Player move
0	2	2	8	v
2	32	(8)	2

After merging the bottom-most cells (if possible), we proceed with the cells above and so on...

0	0	0	0	|
0	0	0	2	| Player move
0	2	(4)	8	v
2	32	(8)	2
[...]
0	0	(0)	0	|
0	0	(0)	2	| Player move
0	2	(4)	8	v
2	32	(8)	2

When no more merges are possible the move ends and all "merged" markers are removed and we wait for the next turn. This approach fixes your multiple-merge problem.

Another example for the scan direction (numbers now represent how a loop would run through the fields):

Player move
----&gt;
4	3	2	1
8	7	6	5
12	11	10	9
16	15	14	13

About your code

Looking at the code, one notices that there is lots of code duplication regarding your loops. For each case you do a separate nested for-loop, which is not really optimal. Instead, you might be able to do something like this:

for i := 1; i &lt; height; i++ {
for j := 0; j &lt; width; j++ {
if board[i][j] == 0 {
continue
}
switch input {
case &quot;d&quot;:
updateBoardDown(board, i, j)
case &quot;u&quot;:
updateBoardUp(board, i, j)
[...]
}
}
}

huangapple
  • 本文由 发表于 2014年4月8日 19:23:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/22935492.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定