14 赞同
2 评论
46 收藏

当初对这个游戏颇有印象还是在最强大脑节目上面,何猷君以几十秒就完成了这个游戏。写2048游戏的时候,又想起了这个游戏,想着来研究一下。

游戏玩法

用尽量少的步数,尽量短的时间,将棋盘上的数字方块,按照从左到右、从上到下的顺序重新排列整齐。

效果图

游戏操作方式

第一种是通过键盘的按键来移动数字,比如按上键,需要做的操作是把13移上去。

第二种是通过鼠标点击13,13移上去。

不论采取哪种操作方式,可移动数字只能往固定的方向移动,做的操作都是把13和0位置互换即可。考虑到操作性,我们选用鼠标点击这种更为简单的操作方式。

自定义棋盘

定义棋盘的大小:4 * 4, 当然我把这个棋盘的大小做成了可自定义的属性,如果你觉得4*4没有挑战性, 还可以5 * 5 、6 * 6等等,下面以4 * 4为例做介绍。

看过我之前2048那篇文章的,一定明白接下来应该怎么做。没错,忽略棋盘背景,把棋盘看作一个4*4的二维矩阵,空白格用数字0填充:

接下来,我们应该考虑的问题是,数字的移动该怎么做?看上图的状态,此时只有0的上,右,下三格可以移动,别的都不能动。这里大家可以停顿一下考虑考虑,看看能不能找到一个移动的算法方案?

数字移动

通过数字在二维矩阵中的下标来表示数字的位置,比如8的位置是第一行、第一列即(0,0)(列表元素的下标从0开始), 第二行、第一列是(1, 0)。

上图中,13的坐标是(3,0)。当鼠标点击13时,13和0交换位置,13的位置变成(2,0)。从(3,0)到(2,0)可以看作加了一个矢量(-1,0)。那如果点击不是13, 而是(1,0)位置的3,发生的变化就是(1,0)变成(2,0),矢量是(1,0),同理(2,1)位置移到(2, 0) 矢量是(0, -1),我们得到了一个上下左右的矢量集合, 上(-1, 0), 下(1, 0), 左(0, -1), 右(0, 1) 。

游戏逻辑

游戏的逻辑就变成了,当我们随便点击一个数字时,判断该数字的位置分别在加上4个矢量后的位置上的数字是否为0,是0,就把两个位置的数字交换,不是0,不做任何变动即可。

为什么会变成这样,因为一个数字可移动的方向只有0个或者1个。

数据矩阵存放

缕清了游戏的逻辑,那在记录数据二维矩阵时,就需要记录每个数字在矩阵中的位置下标。所以我们不打算采用2048中的矩阵的记录方式,而是采用python中的dict, 把位置作为key,把数字作为value。

整个操作的逻辑就是,当我们鼠标点击时,首先获取点击的坐标(x, y),通过坐标(x, y),获取到点击了哪一行,哪一列,得到(row_index, col_index)。然后把(row_index, col_index)分别加四个矢量, 判断加矢量后的位置(row_index+vector, col_index+vector) 在不在上图中的dict中且值是否等于0, 如果等于0, 使(row_index, col_index) 和 (row_index+vector, col_index+vector)的值互换。

如何判断游戏胜利?

判断dict中的最后一位是否为0,且0之前的所有值,是否按照顺序排序, 即为胜利。字典考虑到需要顺序,所以可以采用collections库中的OrderedDict 有序字典的数据格式。

开撸代码

  1. 定义全局变量
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import random
from collections import OrderedDict
import pygame

FPS = 60
SHAPE = 4         # 棋盘shape
CELL_SIZE = 100   # 方格大小
CELL_GAP_SIZE = 10  # 方格间距
MARGIN = 10  # 方格的margin
PADDING = 10  # 方格的padding
SCREEN_WIDTH = (CELL_SIZE + MARGIN) * SHAPE + MARGIN  # 屏幕宽度
SCREEN_HEIGHT = (CELL_SIZE + MARGIN) * SHAPE + MARGIN  # 屏幕高度

BACKGROUND_COLOR = "#92877d"  # 背景颜色
BACKGROUND_EMPTY_CELL_COLOR = "#9e948a"  # 空方格颜色
BACKGROUND_CELL_COLOR = "#edc22e"  # 方格颜色

# 定义两个元组相加
def tuple_add(t1, t2):
    return (t1[0] + t2[0], t1[1] + t2[1])

2. 算法逻辑,初始化棋盘数据,先生成正确的序列,然后随机移动一千次。如果采用随机序列,有可能游戏无解。

class Logic:
    def __init__(self, shape=4):
        self.shape = int(shape) if shape > 2 else 4  # 初始化形状
        self.tiles = OrderedDict()  # 初始化数据
        self.neighbors = [  # 定义方向矢量
            [1, 0],  # 下
            [-1, 0],  # 上
            [0, 1],  # 右
            [0, -1],  # 左
        ]
        self.click_dict = {'x': {}, 'y': {}}  # 定义鼠标点击坐标转换下标的数据
        self.init_load()  # 初始化加载

    def init_load(self):
        count = 1
        # 生成正确的序列
        for x in range(self.shape):
            for y in range(self.shape):
                mark = tuple([x, y])
                self.tiles[mark] = count
                count += 1
        self.tiles[mark] = 0

        for count in range(1000):  # 随机移动一千次
            neighbor = random.choice(self.neighbors)
            spot = tuple_add(mark, neighbor)

            if spot in self.tiles:
                number = self.tiles[spot]
                self.tiles[spot] = 0
                self.tiles[mark] = number
                mark = spot

        self.init_click_dict()

    def init_click_dict(self):
        # 初始化点击坐标转换下标的数据
        for r in range(self.shape):
            for c in range(self.shape):
                x = MARGIN * (c + 1) + c * CELL_SIZE
                x1 = x + CELL_SIZE
                click_x = tuple(range(x, x1))

                self.click_dict['x'][click_x] = c
                y = MARGIN * (r + 1) + r * CELL_SIZE
                y1 = y + CELL_SIZE
                click_y = tuple(range(y, y1))
                self.click_dict['y'][click_y] = r

    def move(self, mark):
        # 移动数据
        for neighbor in self.neighbors:
            spot = tuple_add(mark, neighbor)

            if spot in self.tiles and self.tiles[spot] is 0:
                self.tiles[spot], self.tiles[mark] = self.tiles[
                    mark], self.tiles[spot]
                break

    def click_to_move(self, x, y):
        # 点击移动
        x1 = None
        for k, v in self.click_dict['x'].items():
            if x in k:
                x1 = v

        if x1 is None:
            return
        y1 = None
        for k, v in self.click_dict['y'].items():
            if y in k:
                y1 = v

        if y1 is None:
            return
        self.move((y1, x1))

    def is_win(self):
        # 游戏结束判定
        if self.tiles[(self.shape - 1, self.shape - 1)] is not 0:
            return False
        values = list(self.tiles.values())
        for index in range(values.__len__() - 1):
            if index + 1 != values[index]:
                return False
        return True

3. 初始化游戏和画数字的函数

def init_game():
    # 初始化游戏
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption('数字华容道 -- 0')
    return screen


def draw_num(logic, screen):
    for r in range(logic.shape):
        for c in range(logic.shape):
            num = logic.tiles[(r, c)]
            if num is not 0:
                color = pygame.Color(BACKGROUND_CELL_COLOR)
            else:
                color = pygame.Color(BACKGROUND_EMPTY_CELL_COLOR)

            x = MARGIN * (c + 1) + c * CELL_SIZE
            y = MARGIN * (r + 1) + r * CELL_SIZE
            pygame.draw.rect(screen, color, (x, y, CELL_SIZE, CELL_SIZE))
            if num is not 0:
                font_size = int((CELL_SIZE - PADDING) / 1.3)
                font = pygame.font.SysFont('arialBlod', font_size)
                font_width, font_height = font.size(str(num))
                screen.blit(font.render(str(num), True, (255, 255, 255)),
                            (x + (CELL_SIZE - font_width) / 2, y +
                             (CELL_SIZE - font_height) / 2 + 5))

4. 监控事件和游戏胜利事件

def press(is_game_over, logic, COUNT, counts):
    for event in pygame.event.get():
        if event.type == COUNT and not is_game_over:  # 设置定时器,记录时间
            counts += 1
            pygame.display.set_caption('数字华容道 -- {}'.format(counts))
        if event.type == pygame.QUIT:  # 点击关闭按钮退出
            pygame.quit()
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONUP:  # 鼠标点击
            if event.button == 1 and not is_game_over:
                x, y = event.pos
                logic.click_to_move(int(x), int(y))  # 点击移动
        elif event.type == pygame.KEYDOWN and event.key == 13:  # 游戏结束,回车重开
            return True
    if COUNT:
        return counts


def game_win(screen, logic, clock, text='You Win!'):
    font = pygame.font.SysFont('Blod', int(SCREEN_WIDTH / 4))
    font_width, font_height = font.size(str(text))
    while True:
        if press(True, logic, None, None):
            break
        screen.fill(pygame.Color(BACKGROUND_COLOR))
        draw_num(logic, screen)
        screen.blit(font.render(str(text), True, (0, 0, 0)),
                    ((SCREEN_WIDTH - font_width) / 2,
                     (SCREEN_HEIGHT - font_height) / 2))
        pygame.display.update()
        clock.tick(FPS)

5. 主函数

def main():
    screen = init_game()
    clock = pygame.time.Clock()
    logic = Logic(SHAPE)
    COUNT = pygame.USEREVENT + 1
    pygame.time.set_timer(COUNT, 1000)
    seconds = 0  # 记录时间
    while True:
        if logic.is_win():  # 判断游戏是否胜利
            break
        seconds = press(False, logic, COUNT, seconds)  # 监控按键
        screen.fill(pygame.Color(BACKGROUND_COLOR))  # 填充背景
        draw_num(logic, screen)  # 画数字
        pygame.display.update()
        clock.tick(FPS)
    game_win(screen, logic, clock, text='Time:' + str(seconds))


if __name__ == "__main__":
    while True:
        main()

200行左右完成。

游戏运行

左上角有记录游戏时间。

打代码的shy:用Python 写游戏之 2048 打代码的shy--用python写游戏系列

初来乍到,请多关照。

编辑于 2020-03-30 · 著作权归作者所有

哆哆女性网世字起名大全网站建设存在的问题模板小学语文读后感八字算命配对seo网站优化方案男士养生保健培训金线莲怎么种植的程字取名起名大全女孩名字寄小读者的读后感50字网络游戏起名瓷都免费算命生辰八字碟调网最新电视剧姓王的男的起什么名字霸气周易公司取姓名周娜艺术签名义乌企业网站建设视频网站seo周公解梦大全查询自己光身土石方公司起名参考毒岛伢子夏邑住房商标起什么姓名周公解梦大全查询下李和赵组合起名字女孩材料建筑公司起名十三笔画的字有哪个适合起名字起名健康管理有限公司水电工程公司起名姓余的起什么名子女孩算死命完整免费阅读淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻不负春光新的一天从800个哈欠开始有个姐真把千机伞做出来了国产伟哥去年销售近13亿充个话费竟沦为间接洗钱工具重庆警方辟谣“男子杀人焚尸”男子给前妻转账 现任妻子起诉要回春分繁花正当时呼北高速交通事故已致14人死亡杨洋拄拐现身医院月嫂回应掌掴婴儿是在赶虫子男孩疑遭霸凌 家长讨说法被踢出群因自嘲式简历走红的教授更新简介网友建议重庆地铁不准乘客携带菜筐清明节放假3天调休1天郑州一火锅店爆改成麻辣烫店19岁小伙救下5人后溺亡 多方发声两大学生合买彩票中奖一人不认账张家界的山上“长”满了韩国人?单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#青海通报栏杆断裂小学生跌落住进ICU代拍被何赛飞拿着魔杖追着打315晚会后胖东来又人满为患了当地回应沈阳致3死车祸车主疑毒驾武汉大学樱花即将进入盛花期张立群任西安交通大学校长为江西彩礼“减负”的“试婚人”网友洛杉矶偶遇贾玲倪萍分享减重40斤方法男孩8年未见母亲被告知被遗忘小米汽车超级工厂正式揭幕周杰伦一审败诉网易特朗普谈“凯特王妃P图照”考生莫言也上北大硕士复试名单了妈妈回应孩子在校撞护栏坠楼恒大被罚41.75亿到底怎么缴男子持台球杆殴打2名女店员被抓校方回应护栏损坏小学生课间坠楼外国人感慨凌晨的中国很安全火箭最近9战8胜1负王树国3次鞠躬告别西交大师生房客欠租失踪 房东直发愁萧美琴窜访捷克 外交部回应山西省委原副书记商黎光被逮捕阿根廷将发行1万与2万面值的纸币英国王室又一合照被质疑P图男子被猫抓伤后确诊“猫抓病”

哆哆女性网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化