连 连 看

2023-11-15

1. 案例介绍

连连看是一款曾经非常流行的小游戏。

游戏规则:

  1. 点击选中两个相同的方块。

  2. 两个选中的方块之间连接线的折点不超过两个(接线由X轴和Y轴的平行线组成)。

  3. 每找出一对,它们就会自动消失。

  4. 连线不能从尚未消失的图案上经过。

  5. 把所有的图案全部消除即可获得胜利。

2. 设计思路

  1. 生成成对的图片元素。

  2. 将图片元素打乱排布。

  3. 定义什么才算 相连(两张图片的连线不多于3跟直线,或者说转角不超过2个)。

  4. 实现 相连 判断算法。

  5. 消除图片元素并判断是否消除完毕。

3. 示例效果

图片

4. 示例源码

 

 

from tkinter import *
from tkinter.messagebox import *
from threading import Timer
import time
import random


class Point:
    # 点类
    def __init__(self, x, y):
        self.x = x
        self.y = y


# --------------------------------------


'''
判断选中的两个方块是否可以消除
'''


def IsLink(p1, p2):
    if lineCheck(p1, p2):
        return True
    if OneCornerLink(p1, p2):  # 一个转弯(折点)的联通方式
        return True
    if TwoCornerLink(p1, p2):  # 两个转弯(折点)的联通方式
        return True
    return False


# ---------------------------
def IsSame(p1, p2):
    if map[p1.x][p1.y] == map[p2.x][p2.y]:
        print("clicked at IsSame")
        return True
    return False


def callback(event):  # 鼠标左键事件代码
    global Select_first, p1, p2
    global firstSelectRectId, SecondSelectRectId

    # print ("clicked at", event.x, event.y,turn)
    x = (event.x) // 40  # 换算棋盘坐标
    y = (event.y) // 40
    print("clicked at", x, y)

    if map[x][y] == " ":
        showinfo(title="提示", message="此处无方块")
    else:

        if Select_first == False:
            p1 = Point(x, y)
            # 画选定(x1,y1)处的框线
            firstSelectRectId = cv.create_rectangle(x * 40, y * 40, x * 40 + 40, y * 40 + 40, width=2, outline="blue")
            Select_first = True
        else:
            p2 = Point(x, y)
            # 判断第二次点击的方块是否已被第一次点击选取,如果是则返回。
            if (p1.x == p2.x) and (p1.y == p2.y):
                return
            # 画选定(x2,y2)处的框线
            print('第二次点击的方块', x, y)
            # SecondSelectRectId=cv.create_rectangle(100,20,x*40+40,y*40+40,width=2,outline="yellow")
            SecondSelectRectId = cv.create_rectangle(x * 40, y * 40, x * 40 + 40, y * 40 + 40, width=2,
                                                     outline="yellow")
            print('第二次点击的方块', SecondSelectRectId)
            cv.pack()

            # 判断是否连通
            if IsSame(p1, p2) and IsLink(p1, p2):
                print('连通', x, y)
                Select_first = False
                # 画选中方块之间连接线
                drawLinkLine(p1, p2)
                # clearTwoBlock()
                # time.sleep(0.6)
                # clearFlag=True
                t = Timer(timer_interval, delayrun)  # 定时函数
                t.start()


            else:  # 重新选定第一个方块
                # 清除第一个选定框线
                cv.delete(firstSelectRectId)
                cv.delete(SecondSelectRectId)
                # print('清除第一个选定框线')
                # firstSelectRectId=SecondSelectRectId
                # p1=Point(x,y)           #设置重新选定第一个方块的坐标
                Select_first = False


timer_interval = 0.3  # 0.3秒


# --------------------------------------
def delayrun():
    clearTwoBlock()  # 清除连线及方块


def clearTwoBlock():  # 清除连线及方块
    # 延时0.1秒
    # time.sleep(0.1)
    # 清除第一个选定框线
    cv.delete(firstSelectRectId)
    # 清除第2个选定框线
    cv.delete(SecondSelectRectId)
    # 清空记录方块的值
    map[p1.x][p1.y] = " "
    cv.delete(image_map[p1.x][p1.y])
    map[p2.x][p2.y] = " "
    cv.delete(image_map[p2.x][p2.y])
    Select_first = False
    undrawConnectLine()  # 清除选中方块之间连接线


def drawQiPan():  # 画棋盘
    for i in range(0, 15):
        cv.create_line(20, 20 + 40 * i, 580, 20 + 40 * i, width=2)
    for i in range(0, 15):
        cv.create_line(20 + 40 * i, 20, 20 + 40 * i, 580, width=2)
    cv.pack()


def print_map():  # 输出map地图
    global image_map
    for x in range(0, Width):  # 0--14
        for y in range(0, Height):  # 0--14
            if (map[x][y] != ' '):
                img1 = imgs[int(map[x][y])]
                id = cv.create_image((x * 40 + 20, y * 40 + 20), image=img1)
                image_map[x][y] = id
    cv.pack()
    for y in range(0, Height):  # 0--14
        for x in range(0, Width):  # 0--14
            print(map[x][y], end=' ')
        print(",", y)


'''
* 同行同列情况消除方法 原理:如果两个相同的被消除元素之间的 空格数
spaceCount等于他们的(行/列差-1)则 两者可以联通消除
* x代表列,y代表行
* param p1 第一个保存上次选中点坐标的点对象
* param p2 第二个保存上次选中点坐标的点对象
'''


# 直接连通
def lineCheck(p1, p2):
    absDistance = 0
    spaceCount = 0
    if (p1.x == p2.x or p1.y == p2.y):  # 同行同列的情况吗?
        print("同行同列的情况------")
        # 同列的情况
        if (p1.x == p2.x and p1.y != p2.y):
            print("同列的情况")
            # 绝对距离(中间隔着的空格数)
            absDistance = abs(p1.y - p2.y) - 1
            # 正负值
            if p1.y - p2.y > 0:
                zf = -1
            else:
                zf = 1
            for i in range(1, absDistance + 1):
                if (map[p1.x][p1.y + i * zf] == " "):
                    # 空格数加1
                    spaceCount += 1
                else:
                    break;  # 遇到阻碍就不用再探测了

        # 同行的情况
        elif (p1.y == p2.y and p1.x != p2.x):
            print(" 同行的情况")
            absDistance = abs(p1.x - p2.x) - 1
            # 正负值
            if p1.x - p2.x > 0:
                zf = -1
            else:
                zf = 1
            for i in range(1, absDistance + 1):
                if (map[p1.x + i * zf][p1.y] == " "):
                    # 空格数加1
                    spaceCount += 1
                else:
                    break;  # 遇到阻碍就不用再探测了
        if (spaceCount == absDistance):
            # 可联通
            print(absDistance, spaceCount)
            print("行/列可直接联通")
            return True
        else:
            print("行/列不能消除!")
            return False
    else:
        # 不是同行同列的情况所以直接返回false
        return False;

    # --------------------------------------


# 第二种,直角连通
'''
直角连接,即X,Y坐标都不同的,可以用这个方法尝试连接
 param first:选中的第一个点
 param second:选中的第二个点
'''


def OneCornerLink(p1, p2):
    # 第一个直角检查点,如果这里为空则赋予相同值供检查
    checkP = Point(p1.x, p2.y)
    # 第二个直角检查点,如果这里为空则赋予相同值供检查
    checkP2 = Point(p2.x, p1.y);
    # 第一个直角点检测
    if (map[checkP.x][checkP.y] == " "):
        if (lineCheck(p1, checkP) and lineCheck(checkP, p2)):
            linePointStack.append(checkP)
            print("直角消除ok", checkP.x, checkP.y)
            return True
    # 第二个直角点检测
    if (map[checkP2.x][checkP2.y] == " "):
        if (lineCheck(p1, checkP2) and lineCheck(checkP2, p2)):
            linePointStack.append(checkP2)
            print("直角消除ok", checkP2.x, checkP2.y)
            return True
    print("不能直角消除")
    return False;


# -----------------------------------------
'''
#第三种,双直角连通
双直角联通判定可分两步走:
1. 在p1点周围4个方向寻找空格checkP
2. 调用OneCornerLink(checkP, p2)
3. 即遍历 p1 4 个方向的空格,使之成为 checkP,然后调用 OneCornerLink(checkP, 
p2)判定是否为真,如果为真则可以双直角连同,否则当所有的空格都遍历完而没有找
到一个checkP使OneCornerLink(checkP, p2)为真,则两点不能连同
具体代码:

双直角连接方法
@param p1 第一个点
@param p2 第二个点
'''


def TwoCornerLink(p1, p2):
    checkP = Point(p1.x, p1.y)
    # 四向探测开始
    for i in range(0, 4):
        checkP.x = p1.x
        checkP.y = p1.y
        # 向下
        if (i == 3):
            checkP.y += 1
            while ((checkP.y < Height) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("下探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.y += 1
            print("ssss", checkP.y, Height - 1)
            # 补充两个折点都在游戏区域底侧外部
            if checkP.y == Height:  # 出了底部,则仅需判断p2能否也达到底部边界
                z = Point(p2.x, Height - 1)  # 底部边界点
                if lineCheck(z, p2):  # 两个折点在区域外部的底侧
                    linePointStack.append(Point(p1.x, Height))
                    linePointStack.append(Point(p2.x, Height))
                    print("下探测到游戏区域外部OK")
                    return True
        # 向右
        elif (i == 2):
            checkP.x += 1
            while ((checkP.x < Width) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("右探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.x += 1
            # 补充两个折点都在游戏区域右侧外部
            if checkP.x == Width:  # 出了右侧,则仅需判断p2能否也达到右部边界
                z = Point(Width - 1, p2.y)  # 右部边界点
                if lineCheck(z, p2):  # 两个折点在区域外部的底侧
                    linePointStack.append(Point(Width, p1.y))
                    linePointStack.append(Point(Width, p2.y))
                    print("右探测到游戏区域外部OK")
                    return True
        # 向左
        elif (i == 1):
            checkP.x -= 1
            while ((checkP.x >= 0) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("左探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.x -= 1
        # 向上
        elif (i == 0):
            checkP.y -= 1
            while ((checkP.y >= 0) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("上探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.y -= 1

    # 四个方向都寻完都没找到适合的checkP点
    print("两直角连接没找到适合的checkP点")
    return False;


# ---------------------------
# 画连接线
def drawLinkLine(p1, p2):
    if (len(linePointStack) == 0):
        Line_id.append(drawLine(p1, p2))
    else:
        print(linePointStack, len(linePointStack))
    if (len(linePointStack) == 1):
        z = linePointStack.pop()
        print("一折连通点z", z.x, z.y)
        Line_id.append(drawLine(p1, z))
        Line_id.append(drawLine(p2, z))
    if (len(linePointStack) == 2):
        z1 = linePointStack.pop()
        print("2折连通点z1", z1.x, z1.y)
        Line_id.append(drawLine(p2, z1))
        z2 = linePointStack.pop()
        print("2折连通点z2", z2.x, z2.y)
        Line_id.append(drawLine(z1, z2))
        Line_id.append(drawLine(p1, z2))


# 删除连接线
def undrawConnectLine():
    while len(Line_id) > 0:
        idpop = Line_id.pop()
        cv.delete(idpop)


def drawLine(p1, p2):
    print("drawLine p1,p2", p1.x, p1.y, p2.x, p2.y)
    # cv.create_line( 40+20, 40+20,200,200,width=5,fill='red')
    id = cv.create_line(p1.x * 40 + 20, p1.y * 40 + 20, p2.x * 40 + 20, p2.y * 40 + 20, width=5, fill='red')
    # cv.pack()
    return id


# --------------------------------------
def create_map():  # 产生map地图
    global map
    # 生成随机地图
    # 将所有匹配成对的动物物种放进一个临时的地图中
    tmpMap = []
    m = (Width) * (Height) // 10
    print('m=', m)
    for x in range(0, m):
        for i in range(0, 10):  # 每种方块有10个
            tmpMap.append(x)
    random.shuffle(tmpMap)
    for x in range(0, Width):  # 0--14
        for y in range(0, Height):  # 0--14
            map[x][y] = tmpMap[x * Height + y]


# --------------------------------------
def find2Block(event):  # 自动查找
    global firstSelectRectId, SecondSelectRectId
    m_nRoW = Height
    m_nCol = Width
    bFound = False;
    # 第一个方块从地图的0位置开始
    for i in range(0, m_nRoW * m_nCol):
        # 找到则跳出循环
        if (bFound):
            break

        # 算出对应的虚拟行列位置
        x1 = i % m_nCol
        y1 = i // m_nCol
        p1 = Point(x1, y1)
        # 无图案的方块跳过
        if (map[x1][y1] == ' '):
            continue
        # 第二个方块从前一个方块的后面开始
        for j in range(i + 1, m_nRoW * m_nCol):
            # 算出对应的虚拟行列位置
            x2 = j % m_nCol
            y2 = j // m_nCol
            p2 = Point(x2, y2)
            # 第二个方块不为空 且与第一个方块的动物相同
            if (map[x2][y2] != ' ' and IsSame(p1, p2)):
                # 判断是否可以连通
                if (IsLink(p1, p2)):
                    bFound = True
                    break
    # 找到后自动消除
    if (bFound):  # p1(x1,y1)与p2(x2,y2)连通
        print('找到后', p1.x, p1.y, p2.x, p2.y)
        # 画选定(x1,y1)处的框线
        firstSelectRectId = cv.create_rectangle(x1 * 40, y1 * 40, x1 * 40 + 40, y1 * 40 + 40, width=2, outline="red")
        # 画选定(x2,y2)处的框线
        secondSelectRectId = cv.create_rectangle(x2 * 40, y2 * 40, x2 * 40 + 40, y2 * 40 + 40, width=2, outline="red")
        # t=Timer(timer_interval,delayrun)#定时函数
        # t.start()

    return bFound


# 游戏主逻辑
root = Tk()
root.title("Python连连看 ")
imgs = [PhotoImage(file='images\\bar_0' + str(i) + '.gif') for i in range(0, 10)]  # 所有图标图案
Select_first = False  # 是否已经选中第一块
firstSelectRectId = -1  # 被选中第一块地图对象
SecondSelectRectId = -1  # 被选中第二块地图对象
clearFlag = False
linePointStack = []
Line_id = []
Height = 10
Width = 10
map = [[" " for y in range(Height)] for x in range(Width)]
image_map = [[" " for y in range(Height)] for x in range(Width)]
cv = Canvas(root, bg='green', width=440, height=440)
# drawQiPan( )
cv.bind("<Button-1>", callback)  # 鼠标左键事件
cv.bind("<Button-3>", find2Block)  # 鼠标右键事件
cv.pack()
create_map()  # 产生map地图
print_map()  # 打印map地图
root.mainloop()

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

连 连 看 的相关文章

随机推荐

  • 7年经验之谈 —— 如何高效的开展app的性能测试?

    APP性能测试是什么 从网上查了一下 貌似也没什么特别的定义 我这边根据自己的经验给出一个自己的定义 如有巧合纯属雷同 客户端性能测试就是 从业务和用户的角度出发 设计合理且有效的性能测试场景 制定各性能场景下的客户端性能指标 内存 CPU
  • 微信小程序服务器域名怎么填,微信小程序合法域名配置方法

    在微信小程序的开发过程中 当需要请求第三方网站数据时 各种教程就直接说调用wx request接口即可 但是当初学者自己用的时候就会出现问题 比如我们这里请求聚合数据的API 里边有不少免费的数据申请就可以使用 调用邮编查询的接口 getP
  • Mybatis嵌套查询与嵌套结果

    一对多关系 一是用户 多是订单 实体类User public class User private Integer id private String name private Integer age private List
  • 后台获取前端提交数据的GET、POST方法遇到的问题

    在写代码的时候 总发现前端数据获取不到 最后发现了问题是因为get和post要一起出现 缺一不可 protected void doGet HttpServletRequest request HttpServletResponse res
  • JavaWeb之添加数据,显示到页面

    需求 从jsp页面添加一条记录到数据库 且显示到界面 分析 创建jsp页面 创建EmailServlet gt addEmail方法 设置请求编码 获取所有parameter的值 封装对象 调用addEmail方法 重定向到email sh
  • 游戏开发unity杂项知识系列:SetActive使用注意

    static public void SetActive GameObject go bool state if go null return if go activeSelf state go SetActive state 项目中类似上
  • JSP语法:setProperty

    JSP语法 13 setProperty 时间 2009 03 21 20 37 来源 作者 CSDN IE QQ 百度 Google POCO 新浪 365Key 天极 和讯 博拉 Live 奇客 收客 饭否 叽歪
  • 互联网未来发展方向

    都知道马云带来了互联网以及互联网的高潮 随着国家推动一带一路经济带 以及国内互联网大局的发展 很明显未来是互联网的天下 而互联网将来会怎样哪 第一 网购或者终端购物成为主流 随着经济发展 社会文明进步 智能制造 智能社会越来越凸显 智能手机
  • Python和C语言哪个难?零基础学哪个好?

    Python和C语言哪个难 零基础学哪个好 Python上手简单有交互性强的开发环境 还有众多的第三方库 学习起来会比C C 容易的多 C过于底层强在内存操作 功能实现起来却十分复杂并不适合新手作为上手语言 Python和C语言各有各的优势
  • Elastic Search 学习笔记

    来自尚硅谷 ES 教程 背景知识 从MySQL 到 ES 这一小节是我的一点点理解 如果有不对的话 欢迎指正 ES 是一个开源的高扩展的分布式全文搜索引擎 这样讲似乎还是有点抽象 那我们用一个更加熟悉的东西 MySQL来辅助理解 既然是搜索
  • 程序员技术面常用知识点

    转自 http blog csdn net qq 15437629 article details 52388685 在这里只做备份 计算机网络 TCP IP 模型 TCP IP协议集的分层实施 为什么要给网络划分层次 1 各层之间相对独立
  • 接口(interface)的实现

    接口 interface 的实现 usb插槽就相当于现实中的接口 其实现实生活和编程相对应的 即程序就是事件 1 java中的接口是怎么实现的呢 接口就是给出一些没有实现的方法 到了某个类要使用的时候就去实现他 语法 interface 接
  • Python多层字典取值

    usr bin python coding utf 8 author Bingo he file get target value py time 2017 12 22 def get target value key dic tmp li
  • 对于vue项目整理增删改查

    模板是来源于官方文档 清除tabledata里的模拟数据先
  • Pytorch相关操作(2)

    PyTroch相关操作 1 21 torch cuda Event 记录GPU的运行时间 start torch cuda Event enable timing True end torch cuda Event enable timin
  • Android Handler 的基本使用

    1 前言 https developer android google cn reference android os Handler html Handler 是 Android 中线程通信的常用方式 文档如是说 A Handler al
  • 【从零开始学c++】——string

    学好STL 一 STL简介 了解 1 什么是STL 2 STL的六大组件 3 STL的缺陷 2 string 1 string的简单了解 如何对stl的查阅 2 string常用接口说明 1 string类 对象常见的构造 2 string
  • Kotlin入门学习(非常详细),从零基础入门到精通,看完这一篇就够了

    文章目录 kotlin的历史 Kotlin的工作原理 语言类型 编译型 解释型 Java的语言类型 Kotlin的运行原理 创建Kotlin项目 语法 变量 变量的声明 基本类型 var和val的本质区别 函数 函数的声明 声明技巧 函数的
  • 找准边界,吃定安全

    创新的资源管理算法 基于会话的全分布式处理流程 山石网科全分布式架构 打破了传统架构的限制 找准边界 吃定安全 往期文章 从访问控制谈起 再看零信任模型 威胁情报加持 泛边界下的全局主动防御体系如何着手 随着 2019 年我国以信息网络等新
  • 连 连 看

    1 案例介绍 连连看是一款曾经非常流行的小游戏 游戏规则 点击选中两个相同的方块 两个选中的方块之间连接线的折点不超过两个 接线由X轴和Y轴的平行线组成 每找出一对 它们就会自动消失 连线不能从尚未消失的图案上经过 把所有的图案全部消除即可