用python编写1024游戏 (2)

续上篇文章,我们来一步步实现1024游戏。

总体设计

考虑到实现的难度和效率,改进了一下整体思路:

游戏状态=未结束 
总分=0
棋盘上随机赋值一格

while 游戏状态=未结束:
    if 棋盘上存在格子的值为1024:
        游戏状态=胜利
    elif 棋盘上存在剩余空格 或者 棋盘上依然存在可以合并的元素:
        游戏状态=未结束
    else:
        游戏状态=失败

    打印棋盘状态
    接收到滑动
    for 序列 in 棋盘作平行于该方向的切分:
        for 元素 in 反序序列:
            if 元素不为空:
                if 左边存在相隔空元素的相同元素:
                    自身数值翻倍
                    总分=总分+合并后该元素的值
                    被合并的格子置空
                while 右边元素为空:
                    自身右移一格

    if 棋盘有变化 而且 存在空格子:
        在空位中随机出现数字2或4

首先直接把它翻译成python代码:

def play(n, target, swap_func, debug=False):
    board, state, score = init_game(n)
    set_random_number(board)
    if debug:
        print_game(board)
    while state == GameState.NORMAL:
        if board.max_value == target:
            state = GameState.WIN
            break
        elif board.num_of_empty == 0 and not combinable(board):
            state = GameState.LOSE
            break

        swap_dir = swap_func(board)
        cur_score, moved = apply_swap(board, swap_dir)
        if moved:
            if debug:
                print "score:", score
                print_game(board)
            set_random_number(board)
        score += cur_score

    return state


if __name__ == '__main__':
    state = play(4, 1024, get_swap_dir, debug=True)
    if state == GameState.WIN:
        print "You Win!"
    else:
        print "You Lose!"

设计数据结构

然后设计一下用到的数据结构:

class GameState:
    NORMAL = 1
    WIN    = 2
    LOSE   = 3


class SwapDir:
    LEFT  = 1
    UP    = 2
    RIGHT = 3
    DOWN  = 4


class GameBoard:
    def __init__(self, n):
        self.length = n
        self.board = [0 for i in range(n*n)]
        self.num_of_empty = n*n
        self.max_value = 0

    def get(self, x, y):
        if x < 0 or y < 0 or x >= self.length or y >= self.length:
            return -1
        return self.board[x*self.length + y]

    def set(self, x, y, value):
        if x < 0 or y < 0 or x >= self.length or y >= self.length:
            return False
        self.board[x*self.length + y] = value

    def __len__(self):
        return self.length

由于我用的python版本没有enum类型,所以用class代替。

这里的棋盘类我使用一维数组来作为内部存储实现。要注意get和set方法中坐标到下标的换算方式。

其中:

if x < 0 or y < 0 or x >= self.length or y >= self.length:
    return -1

是用来判断棋盘边界的。

实现

接下来是几个关键函数的实现:

随机放置2或4

def set_random_number(board):
    # 在空格处放置随机值
    n = len(board)
    while not board.num_of_empty == 0:
        x = random.randint(0, n - 1)
        y = random.randint(0, n - 1)
        if board.get(x, y) == 0:
            value = random.uniform(0, 1)
            if value < 0.1:
                value = 4
            else:
                value = 2
            board.set(x, y, value)
            board.num_of_empty -= 1
            return True
    return False

这个函数会随着空值元素减少效率变低,应该有优化的算法。其中的0.1控制着2和4出现的比例。

检测当前棋盘的状态是否可变

def combinable(board):
    # 从左上角开始,检查左边和下边是否存在可合并的单元格
    for i in range(len(board)):
        for j in range(len(board)):
            value = board.get(i, j)
            if value == 0:
                return True
            if value == board.get(i, j + 1):
                return True
            if value == board.get(i + 1, j):
                return True
    return False

改变棋盘状态

def apply_swap(board, swap_dir):
    # 统一成swap left
    def xset_cur(i, j, value):
        board.set(i, j, value)
    def yset_cur(i, j, value):
        board.set(j, i, value)
    def xget_cur(i, j):
        return board.get(i, j)
    def yget_cur(i, j):
        return board.get(j, i)

    col_indexs = range(len(board))

    if swap_dir == SwapDir.LEFT:
        get_cur = xget_cur
        set_cur = xset_cur
        delta = 1
    elif swap_dir == SwapDir.RIGHT:
        get_cur = xget_cur
        set_cur = xset_cur
        delta = -1
        col_indexs = col_indexs[::-1]
    elif swap_dir == SwapDir.UP:
        get_cur = yget_cur
        set_cur = yset_cur
        delta = 1
    elif swap_dir == SwapDir.DOWN:
        get_cur = yget_cur
        set_cur = yset_cur
        delta = -1
        col_indexs = col_indexs[::-1]
    else:
        return 0, False

    score = 0
    moved = False
    for i in range(len(board)):
        for j in col_indexs:
            if get_cur(i, j) == 0:
                continue
            k = j + delta
            while get_cur(i, k) == 0:
                k += delta
            if get_cur(i, j) == get_cur(i, k):
                set_cur(i, j, get_cur(i, j)*2) 
                set_cur(i, k, 0)
                score += get_cur(i, j)
                board.num_of_empty += 1
                moved = True
                print "get score:", get_cur(i, j)

            k = j
            while get_cur(i, k - delta) == 0:
                k -= delta
            if not k == j:
                set_cur(i, k, get_cur(i, j)) 
                set_cur(i, j, 0)
                moved = True
                # print "move", i, j, "to", i, k
    if score > board.max_value:
        board.max_value = score
    return score, moved

由于滑动的方向有四个,各个方向的处理办法都不一样。留意到上下、左右可以成组,组内操作的方向是反的。但组间的操作却是转置的。

考虑到棋盘本身可以看做一个矩阵,可以用一个线性变换将棋盘统一到某一方向,然后应用同一种操作,然后再变换回来。这样子虽然避免了重复的分支操作,但是程序的复杂度上升,效率也受到影响。

换一个思路,我们尝试提取不同方向下的不同点:定位和遍历。例如向左滑动和向右滑动,它们的定位方法是一样的,但是遍历方向相反。向下和向上滑动的遍历方向也是相反的,定位方法一样。向左滑动和向上滑动的遍历方向一样,但是定位方法确实相反的(一个i行j列,一个j行i列。于是我们对上述两种操作再针对不同滑动方向赋予不同的实现,从而使操作得到统一。

完整代码

代码放在了Github Gist上,如下所示:

“用python编写1024游戏 (2)”的一个回复

  1. 换一个思路,我们尝试提取不同方向下的不同点:定位和遍历。例如向左滑动和向右滑动,它们的定位方法是一样的,但是遍历方向相反。向下和向上滑动的遍历方向也是相反的,定位方法一样。向左滑动和向上滑动的遍历方向一样,但是定位方法确实相反的(一个i行j列,一个j行i列。于是我们可以抽象出上述两种操作,使不同方向上的操作得到统一。——不明觉厉wow~ ⊙o⊙

发表评论

您的电子邮箱地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据