今天分享7个学妹看见都惊呆的 Python 小项目!【附源码】

建议收藏

界面应用

1、计算器

1. 案例介绍

本例利用 Python 开发一个可以进行简单的四则运算的图形化计算器,会用到 Tkinter 图形组件进行开发。主要知识点:Python Tkinter 界面编程;计算器逻辑运算实现。本例难度为初级,适合具有 Python 基础和 Tkinter 组件编程知识的用户学习。

2. 设计原理

要制作一个计算器,首先需要知道它由哪些部分组成。示意如下图所示。

从结构上来说,一个简单的图形界面,需要由界面组件、组件的事件监听器(响应各类事件的逻辑)和具体的事件处理逻辑组成。界面实现的主要工作是创建各个界面组件对象,对其进行初始化,以及控制各组件之间的层次关系和布局。

3. 示例效果

4. 示例源码

`def zf(self):`
import tkinter  
import math  
import tkinter.messagebox  
  
  
class Calculator(object):  
    # 界面布局方法  
    def __init__(self):  
        # 创建主界面,并且保存到成员属性中  
        self.root = tkinter.Tk()  
        self.root.minsize(280, 450)  
        self.root.maxsize(280, 470)  
        self.root.title('计算器')  
        # 设置显式面板的变量  
        self.result = tkinter.StringVar()  
        self.result.set(0)  
        # 设置一个全局变量  运算数字和f符号的列表  
        self.lists = []  
        # 添加一个用于判断是否按下运算符号的标志  
        self.ispresssign = False  
        # 界面布局  
        self.menus()  
        self.layout()  
        self.root.mainloop()  
  
    # 计算器菜单界面摆放  
  
    def menus(self):  
        # 添加菜单  
        # 创建总菜单  
        allmenu = tkinter.Menu(self.root)  
        # 添加子菜单  
        filemenu = tkinter.Menu(allmenu, tearoff=0)  
        # 添加选项卡  
        filemenu.add_command(  
            label='标准型(T)            Alt+1', command=self.myfunc)  
        filemenu.add_command(  
            label='科学型(S)            Alt+2', command=self.myfunc)  
        filemenu.add_command(  
            label='程序员(P)            Alt+3', command=self.myfunc)  
        filemenu.add_command(label='统计信息(A)        Alt+4', command=self.myfunc)  
        # 添加分割线  
        filemenu.add_separator()  
        # 添加选项卡  
        filemenu.add_command(label='历史记录(Y)      Ctrl+H', command=self.myfunc)  
        filemenu.add_command(label='数字分组(I)', command=self.myfunc)  
        # 添加分割线  
        filemenu.add_separator()  
        # 添加选项卡  
        filemenu.add_command(  
            label='基本(B)             Ctrl+F4', command=self.myfunc)  
        filemenu.add_command(label='单位转换(U)      Ctrl+U', command=self.myfunc)  
        filemenu.add_command(label='日期计算(D)      Ctrl+E', command=self.myfunc)  
        menu1 = tkinter.Menu(filemenu, tearoff=0)  
        menu1.add_command(label='抵押(M)', command=self.myfunc)  
        menu1.add_command(label='汽车租赁(V)', command=self.myfunc)  
        menu1.add_command(label='油耗(mpg)(F)', command=self.myfunc)  
        menu1.add_command(label='油耗(l/100km)(U)', command=self.myfunc)  
        filemenu.add_cascade(label='工作表(W)', menu=menu1)  
        allmenu.add_cascade(label='查看(V)', menu=filemenu)  
  
        # 添加子菜单2  
        editmenu = tkinter.Menu(allmenu, tearoff=0)  
        # 添加选项卡  
        editmenu.add_command(label='复制(C)         Ctrl+C', command=self.myfunc)  
        editmenu.add_command(label='粘贴(V)         Ctrl+V', command=self.myfunc)  
        # 添加分割线  
        editmenu.add_separator()  
        # 添加选项卡  
        menu2 = tkinter.Menu(filemenu, tearoff=0)  
        menu2.add_command(label='复制历史记录(I)', command=self.myfunc)  
        menu2.add_command(  
            label='编辑(E)                      F2', command=self.myfunc)  
        menu2.add_command(label='取消编辑(N)            Esc', command=self.myfunc)  
        menu2.add_command(label='清除(L)    Ctrl+Shift+D', command=self.myfunc)  
        editmenu.add_cascade(label='历史记录(H)', menu=menu2)  
        allmenu.add_cascade(label='编辑(E)', menu=editmenu)  
  
        # 添加子菜单3  
        helpmenu = tkinter.Menu(allmenu, tearoff=0)  
        # 添加选项卡  
        helpmenu.add_command(label='查看帮助(V)       F1', command=self.myfunc)  
        # 添加分割线  
        helpmenu.add_separator()  
        # 添加选项卡  
        helpmenu.add_command(label='关于计算器(A)', command=self.myfunc)  
        allmenu.add_cascade(label='帮助(H)', menu=helpmenu)  
  
        self.root.config(menu=allmenu)  
  
    # 计算器主界面摆放  
  
    def layout(self):  
        # 显示屏  
        result = tkinter.StringVar()  
        result.set(0)  
        show_label = tkinter.Label(self.root, bd=3, bg='white', font=(  
            '宋体', 30), anchor='e', textvariable=self.result)  
        show_label.place(x=5, y=20, width=270, height=70)  
        # 功能按钮MC  
        button_mc = tkinter.Button(self.root, text='MC', command=self.wait)  
        button_mc.place(x=5, y=95, width=50, height=50)  
        # 功能按钮MR  
        button_mr = tkinter.Button(self.root, text='MR', command=self.wait)  
        button_mr.place(x=60, y=95, width=50, height=50)  
        # 功能按钮MS  
        button_ms = tkinter.Button(self.root, text='MS', command=self.wait)  
        button_ms.place(x=115, y=95, width=50, height=50)  
        # 功能按钮M+  
        button_mjia = tkinter.Button(self.root, text='M+', command=self.wait)  
        button_mjia.place(x=170, y=95, width=50, height=50)  
        # 功能按钮M-  
        button_mjian = tkinter.Button(self.root, text='M-', command=self.wait)  
        button_mjian.place(x=225, y=95, width=50, height=50)  
        # 功能按钮←  
        button_zuo = tkinter.Button(self.root, text='←', command=self.dele_one)  
        button_zuo.place(x=5, y=150, width=50, height=50)  
        # 功能按钮CE  
        button_ce = tkinter.Button(  
            self.root, text='CE', command=lambda: self.result.set(0))  
        button_ce.place(x=60, y=150, width=50, height=50)  
        # 功能按钮C  
        button_c = tkinter.Button(self.root, text='C', command=self.sweeppress)  
        button_c.place(x=115, y=150, width=50, height=50)  
        # 功能按钮±  
        button_zf = tkinter.Button(self.root, text='±', command=self.zf)  
        button_zf.place(x=170, y=150, width=50, height=50)  
        # 功能按钮√  
        button_kpf = tkinter.Button(self.root, text='√', command=self.kpf)  
        button_kpf.place(x=225, y=150, width=50, height=50)  
        # 数字按钮7  
        button_7 = tkinter.Button(  
            self.root, text='7', command=lambda: self.pressnum('7'))  
        button_7.place(x=5, y=205, width=50, height=50)  
        # 数字按钮8  
        button_8 = tkinter.Button(  
            self.root, text='8', command=lambda: self.pressnum('8'))  
        button_8.place(x=60, y=205, width=50, height=50)  
        # 数字按钮9  
        button_9 = tkinter.Button(  
            self.root, text='9', command=lambda: self.pressnum('9'))  
        button_9.place(x=115, y=205, width=50, height=50)  
        # 功能按钮/  
        button_division = tkinter.Button(  
            self.root, text='/', command=lambda: self.presscalculate('/'))  
        button_division.place(x=170, y=205, width=50, height=50)  
        # 功能按钮%  
        button_remainder = tkinter.Button(  
            self.root, text='//', command=lambda: self.presscalculate('//'))  
        button_remainder.place(x=225, y=205, width=50, height=50)  
        # 数字按钮4  
        button_4 = tkinter.Button(  
            self.root, text='4', command=lambda: self.pressnum('4'))  
        button_4.place(x=5, y=260, width=50, height=50)  
        # 数字按钮5  
        button_5 = tkinter.Button(  
            self.root, text='5', command=lambda: self.pressnum('5'))  
        button_5.place(x=60, y=260, width=50, height=50)  
        # 数字按钮6  
        button_6 = tkinter.Button(  
            self.root, text='6', command=lambda: self.pressnum('6'))  
        button_6.place(x=115, y=260, width=50, height=50)  
        # 功能按钮*  
        button_multiplication = tkinter.Button(  
            self.root, text='*', command=lambda: self.presscalculate('*'))  
        button_multiplication.place(x=170, y=260, width=50, height=50)  
        # 功能按钮1/x  
        button_reciprocal = tkinter.Button(  
            self.root, text='1/x', command=self.ds)  
        button_reciprocal.place(x=225, y=260, width=50, height=50)  
        # 数字按钮1  
        button_1 = tkinter.Button(  
            self.root, text='1', command=lambda: self.pressnum('1'))  
        button_1.place(x=5, y=315, width=50, height=50)  
        # 数字按钮2  
        button_2 = tkinter.Button(  
            self.root, text='2', command=lambda: self.pressnum('2'))  
        button_2.place(x=60, y=315, width=50, height=50)  
        # 数字按钮3  
        button_3 = tkinter.Button(  
            self.root, text='3', command=lambda: self.pressnum('3'))  
        button_3.place(x=115, y=315, width=50, height=50)  
        # 功能按钮-  
        button_subtraction = tkinter.Button(  
            self.root, text='-', command=lambda: self.presscalculate('-'))  
        button_subtraction.place(x=170, y=315, width=50, height=50)  
        # 功能按钮=  
        button_equal = tkinter.Button(  
            self.root, text='=', command=lambda: self.pressequal())  
        button_equal.place(x=225, y=315, width=50, height=105)  
        # 数字按钮0  
        button_0 = tkinter.Button(  
            self.root, text='0', command=lambda: self.pressnum('0'))  
        button_0.place(x=5, y=370, width=105, height=50)  
        # 功能按钮.  
        button_point = tkinter.Button(  
            self.root, text='.', command=lambda: self.pressnum('.'))  
        button_point.place(x=115, y=370, width=50, height=50)  
        # 功能按钮+  
        button_plus = tkinter.Button(  
            self.root, text='+', command=lambda: self.presscalculate('+'))  
        button_plus.place(x=170, y=370, width=50, height=50)  
  
    # 计算器菜单功能  
  
    def myfunc(self):  
        tkinter.messagebox.showinfo('', '预留接口,学成之后,你是不是有冲动添加该功能.')  
  
    # 数字方法  
    def pressnum(self, num):  
        # 全局化变量  
        # 判断是否按下了运算符号  
        if self.ispresssign == False:  
            pass  
        else:  
            self.result.set(0)  
            # 重置运算符号的状态  
            self.ispresssign = False  
        if num == '.':  
            num = '0.'  
        # 获取面板中的原有数字  
        oldnum = self.result.get()  
        # 判断界面数字是否为0  
        if oldnum == '0':  
            self.result.set(num)  
        else:  
            # 连接上新按下的数字  
            newnum = oldnum + num  
  
            # 将按下的数字写到面板中  
            self.result.set(newnum)  
  
    # 运算函数  
    def presscalculate(self, sign):  
        # 保存已经按下的数字和运算符号  
        # 获取界面数字  
        num = self.result.get()  
        self.lists.append(num)  
        # 保存按下的操作符号  
        self.lists.append(sign)  
        # 设置运算符号为按下状态  
        self.ispresssign = True  
  
    # 获取运算结果  
  
    def pressequal(self):  
        # 获取所有的列表中的内容(之前的数字和操作)  
        # 获取当前界面上的数字  
        curnum = self.result.get()  
        # 将当前界面的数字存入列表  
        self.lists.append(curnum)  
        # 将列表转化为字符串  
        calculatestr = ''.join(self.lists)  
        # 使用eval执行字符串中的运算即可  
        endnum = eval(calculatestr)  
        # 将运算结果显示在界面中  
        self.result.set(str(endnum)[:10])  
        if self.lists != 0:  
            self.ispresssign = True  
        # 清空运算列表  
        self.lists.clear()  
  
    # 暂未开发说明  
  
    def wait(self):  
        tkinter.messagebox.showinfo('', '更新中......')  
  
    # ←按键功能  
  
    def dele_one(self):  
        if self.result.get() == '' or self.result.get() == '0':  
            self.result.set('0')  
            return  
        else:  
            num = len(self.result.get())  
            if num > 1:  
                strnum = self.result.get()  
                strnum = strnum[0:num - 1]  
                self.result.set(strnum)  
            else:  
                self.result.set('0')  
  
    # ±按键功能  
  
    def zf(self):  
        strnum = self.result.get()  
        if strnum[0] == '-':  
            self.result.set(strnum[1:])  
        elif strnum[0] != '-' and strnum != '0':  
            self.result.set('-' + strnum)  
  
    # 1/x按键功能  
  
    def ds(self):  
        dsnum = 1 / int(self.result.get())  
        self.result.set(str(dsnum)[:10])  
        if self.lists != 0:  
            self.ispresssign = True  
        # 清空运算列表  
        self.lists.clear()  
  
    # C按键功能  
  
    def sweeppress(self):  
        self.lists.clear()  
        self.result.set(0)  
  
    # √按键功能  
  
    def kpf(self):  
        strnum = float(self.result.get())  
        endnum = math.sqrt(strnum)  
        if str(endnum)[-1] == '0':  
            self.result.set(str(endnum)[:-2])  
        else:  
            self.result.set(str(endnum)[:10])  
        if self.lists != 0:  
            self.ispresssign = True  
        # 清空运算列表  
        self.lists.clear()  
  
  
# 实例化对象  
my_calculator = Calculator()  
  

2、记事本

1. 案例介绍

tkinter 是 Python下面向 tk 的图形界面接口库,可以方便地进行图形界面设计和交互操作编程。tkinter 的优点是简单易用、与 Python 的结合度好。tkinter 在 Python 3.x 下默认集成,不需要额外的安装操作;不足之处为缺少合适的可视化界面设计工具,需要通过代码来完成窗口设计和元素布局。本例采用的 Python 版本为 3.8,如果想在 python 2.x下使用 tkinter,请先进行安装。需要注意的是,不同 Python 版本下的 tkinter 使用方式可能略有不同,建议采用 Python3.x 版本。本例难度为中级,适合具有 Python 基础和 Tkinter 组件编程知识的用户学习。

2. 示例效果

3. 示例源码

from tkinter import *  
from tkinter.filedialog import *  
from tkinter.messagebox import *  
import os  
  
filename = ""  
  
  
def author():  
    showinfo(title="作者", message="Python")  
  
  
def power():  
    showinfo(title="版权信息", message="课堂练习")  
  
  
def mynew():  
    global top, filename, textPad  
    top.title("未命名文件")  
    filename = None  
    textPad.delete(1.0, END)  
  
  
def myopen():  
    global filename  
    filename = askopenfilename(defaultextension=".txt")  
    if filename == "":  
        filename = None  
    else:  
        top.title("记事本" + os.path.basename(filename))  
        textPad.delete(1.0, END)  
        f = open(filename, 'r')  
        textPad.insert(1.0, f.read())  
        f.close()  
  
  
def mysave():  
    global filename  
    try:  
        f = open(filename, 'w')  
        msg = textPad.get(1.0, 'end')  
        f.write(msg)  
        f.close()  
    except:  
        mysaveas()  
  
  
def mysaveas():  
    global filename  
    f = asksaveasfilename(initialfile="未命名.txt", defaultextension=".txt")  
    filename = f  
    fh = open(f, 'w')  
    msg = textPad.get(1.0, END)  
    fh.write(msg)  
    fh.close()  
    top.title("记事本 " + os.path.basename(f))  
  
  
def cut():  
    global textPad  
    textPad.event_generate("<<Cut>>")  
  
  
def copy():  
    global textPad  
    textPad.event_generate("<<Copy>>")  
  
  
def paste():  
    global textPad  
    textPad.event_generate("<<Paste>>")  
  
  
def undo():  
    global textPad  
    textPad.event_generate("<<Undo>>")  
  
  
def redo():  
    global textPad  
    textPad.event_generate("<<Redo>>")  
  
  
def select_all():  
    global textPad  
    # textPad.event_generate("<<Cut>>")  
    textPad.tag_add("sel", "1.0", "end")  
  
  
def find():  
    t = Toplevel(top)  
    t.title("查找")  
    t.geometry("260x60+200+250")  
    t.transient(top)  
    Label(t, text="查找:").grid(row=0, column=0, sticky="e")  
    v = StringVar()  
    e = Entry(t, width=20, textvariable=v)  
    e.grid(row=0, column=1, padx=2, pady=2, sticky="we")  
    e.focus_set()  
    c = IntVar()  
    Checkbutton(t, text="不区分大小写", variable=c).grid(row=1, column=1, sticky='e')  
    Button(t, text="查找所有", command=lambda: search(v.get(), c.get(),  
                                                  textPad, t, e)).grid(row=0, column=2, sticky="e" + "w", padx=2,  
                                                                       pady=2)  
  
    def close_search():  
        textPad.tag_remove("match", "1.0", END)  
        t.destroy()  
  
    t.protocol("WM_DELETE_WINDOW", close_search)  
  
  
def mypopup(event):  
    # global editmenu  
    editmenu.tk_popup(event.x_root, event.y_root)  
  
  
def search(needle, cssnstv, textPad, t, e):  
    textPad.tag_remove("match", "1.0", END)  
    count = 0  
    if needle:  
        pos = "1.0"  
        while True:  
            pos = textPad.search(needle, pos, nocase=cssnstv, stopindex=END)  
            if not pos:  
                break  
            lastpos = pos + str(len(needle))  
            textPad.tag_add("match", pos, lastpos)  
            count += 1  
            pos = lastpos  
        textPad.tag_config('match', fg='yellow', bg="green")  
        e.focus_set()  
        t.title(str(count) + "个被匹配")  
  
  
top = Tk()  
top.title("记事本")  
top.geometry("600x400+100+50")  
  
menubar = Menu(top)  
  
# 文件功能  
filemenu = Menu(top)  
filemenu.add_command(label="新建", accelerator="Ctrl+N", command=mynew)  
filemenu.add_command(label="打开", accelerator="Ctrl+O", command=myopen)  
filemenu.add_command(label="保存", accelerator="Ctrl+S", command=mysave)  
filemenu.add_command(label="另存为", accelerator="Ctrl+shift+s", command=mysaveas)  
menubar.add_cascade(label="文件", menu=filemenu)  
  
# 编辑功能  
editmenu = Menu(top)  
editmenu.add_command(label="撤销", accelerator="Ctrl+Z", command=undo)  
editmenu.add_command(label="重做", accelerator="Ctrl+Y", command=redo)  
editmenu.add_separator()  
editmenu.add_command(label="剪切", accelerator="Ctrl+X", command=cut)  
editmenu.add_command(label="复制", accelerator="Ctrl+C", command=copy)  
editmenu.add_command(label="粘贴", accelerator="Ctrl+V", command=paste)  
editmenu.add_separator()  
editmenu.add_command(label="查找", accelerator="Ctrl+F", command=find)  
editmenu.add_command(label="全选", accelerator="Ctrl+A", command=select_all)  
menubar.add_cascade(label="编辑", menu=editmenu)  
  
# 关于 功能  
aboutmenu = Menu(top)  
aboutmenu.add_command(label="作者", command=author)  
aboutmenu.add_command(label="版权", command=power)  
menubar.add_cascade(label="关于", menu=aboutmenu)  
  
top['menu'] = menubar  
  
# shortcutbar = Frame(top, height=25, bg='light sea green')  
# shortcutbar.pack(expand=NO, fill=X)  
# Inlabe = Label(top, width=2, bg='antique white')  
# Inlabe.pack(side=LEFT, anchor='nw', fill=Y)  
  
textPad = Text(top, undo=True)  
textPad.pack(expand=YES, fill=BOTH)  
scroll = Scrollbar(textPad)  
textPad.config(yscrollcommand=scroll.set)  
scroll.config(command=textPad.yview)  
scroll.pack(side=RIGHT, fill=Y)  
  
# 热键绑定  
textPad.bind("<Control-N>", mynew)  
textPad.bind("<Control-n>", mynew)  
textPad.bind("<Control-O>", myopen)  
textPad.bind("<Control-o>", myopen)  
textPad.bind("<Control-S>", mysave)  
textPad.bind("<Control-s>", mysave)  
textPad.bind("<Control-A>", select_all)  
textPad.bind("<Control-a>", select_all)  
textPad.bind("<Control-F>", find)  
textPad.bind("<Control-f>", find)  
  
textPad.bind("<Button-3>", mypopup)  
top.mainloop()  

3、登录和注册

1. 案例介绍

本例设计一个用户登录和注册模块,使用 Tkinter 框架构建界面,主要用到画布、文本框、按钮等组件。涉及知识点:Python Tkinter 界面编程、pickle 数据存储。本例实现了基本的用户登录和注册互动界面,并提供用户信息存储和验证。pickle 是 python 语言的一个标准模块,安装 python 后已包含 pickle 库,不需要单独再安装。pickle 模块实现了基本的数据序列化和反序列化。通过 pickle 模块的序列化操作能够将程序中运行的对象信息保存到文件中去,永久存储;通过 pickle 模块的反序列化操作,能够从文件中创建上一次程序保存的对象。本例难度为中级,适合具有 Python 基础和 Tkinter 组件编程知识的用户学习。

2、记事本

1. 案例介绍

tkinter 是 Python下面向 tk 的图形界面接口库,可以方便地进行图形界面设计和交互操作编程。tkinter 的优点是简单易用、与 Python 的结合度好。tkinter 在 Python 3.x 下默认集成,不需要额外的安装操作;不足之处为缺少合适的可视化界面设计工具,需要通过代码来完成窗口设计和元素布局。本例采用的 Python 版本为 3.8,如果想在 python 2.x下使用 tkinter,请先进行安装。需要注意的是,不同 Python 版本下的 tkinter 使用方式可能略有不同,建议采用 Python3.x 版本。本例难度为中级,适合具有 Python 基础和 Tkinter 组件编程知识的用户学习。

2. 示例效果

3. 示例源码

from tkinter import *  
from tkinter.filedialog import *  
from tkinter.messagebox import *  
import os  
  
filename = ""  
  
  
def author():  
    showinfo(title="作者", message="Python")  
  
  
def power():  
    showinfo(title="版权信息", message="课堂练习")  
  
  
def mynew():  
    global top, filename, textPad  
    top.title("未命名文件")  
    filename = None  
    textPad.delete(1.0, END)  
  
  
def myopen():  
    global filename  
    filename = askopenfilename(defaultextension=".txt")  
    if filename == "":  
        filename = None  
    else:  
        top.title("记事本" + os.path.basename(filename))  
        textPad.delete(1.0, END)  
        f = open(filename, 'r')  
        textPad.insert(1.0, f.read())  
        f.close()  
  
  
def mysave():  
    global filename  
    try:  
        f = open(filename, 'w')  
        msg = textPad.get(1.0, 'end')  
        f.write(msg)  
        f.close()  
    except:  
        mysaveas()  
  
  
def mysaveas():  
    global filename  
    f = asksaveasfilename(initialfile="未命名.txt", defaultextension=".txt")  
    filename = f  
    fh = open(f, 'w')  
    msg = textPad.get(1.0, END)  
    fh.write(msg)  
    fh.close()  
    top.title("记事本 " + os.path.basename(f))  
  
  
def cut():  
    global textPad  
    textPad.event_generate("<<Cut>>")  
  
  
def copy():  
    global textPad  
    textPad.event_generate("<<Copy>>")  
  
  
def paste():  
    global textPad  
    textPad.event_generate("<<Paste>>")  
  
  
def undo():  
    global textPad  
    textPad.event_generate("<<Undo>>")  
  
  
def redo():  
    global textPad  
    textPad.event_generate("<<Redo>>")  
  
  
def select_all():  
    global textPad  
    # textPad.event_generate("<<Cut>>")  
    textPad.tag_add("sel", "1.0", "end")  
  
  
def find():  
    t = Toplevel(top)  
    t.title("查找")  
    t.geometry("260x60+200+250")  
    t.transient(top)  
    Label(t, text="查找:").grid(row=0, column=0, sticky="e")  
    v = StringVar()  
    e = Entry(t, width=20, textvariable=v)  
    e.grid(row=0, column=1, padx=2, pady=2, sticky="we")  
    e.focus_set()  
    c = IntVar()  
    Checkbutton(t, text="不区分大小写", variable=c).grid(row=1, column=1, sticky='e')  
    Button(t, text="查找所有", command=lambda: search(v.get(), c.get(),  
                                                  textPad, t, e)).grid(row=0, column=2, sticky="e" + "w", padx=2,  
                                                                       pady=2)  
  
    def close_search():  
        textPad.tag_remove("match", "1.0", END)  
        t.destroy()  
  
    t.protocol("WM_DELETE_WINDOW", close_search)  
  
  
def mypopup(event):  
    # global editmenu  
    editmenu.tk_popup(event.x_root, event.y_root)  
  
  
def search(needle, cssnstv, textPad, t, e):  
    textPad.tag_remove("match", "1.0", END)  
    count = 0  
    if needle:  
        pos = "1.0"  
        while True:  
            pos = textPad.search(needle, pos, nocase=cssnstv, stopindex=END)  
            if not pos:  
                break  
            lastpos = pos + str(len(needle))  
            textPad.tag_add("match", pos, lastpos)  
            count += 1  
            pos = lastpos  
        textPad.tag_config('match', fg='yellow', bg="green")  
        e.focus_set()  
        t.title(str(count) + "个被匹配")  
  
  
top = Tk()  
top.title("记事本")  
top.geometry("600x400+100+50")  
  
menubar = Menu(top)  
  
# 文件功能  
filemenu = Menu(top)  
filemenu.add_command(label="新建", accelerator="Ctrl+N", command=mynew)  
filemenu.add_command(label="打开", accelerator="Ctrl+O", command=myopen)  
filemenu.add_command(label="保存", accelerator="Ctrl+S", command=mysave)  
filemenu.add_command(label="另存为", accelerator="Ctrl+shift+s", command=mysaveas)  
menubar.add_cascade(label="文件", menu=filemenu)  
  
# 编辑功能  
editmenu = Menu(top)  
editmenu.add_command(label="撤销", accelerator="Ctrl+Z", command=undo)  
editmenu.add_command(label="重做", accelerator="Ctrl+Y", command=redo)  
editmenu.add_separator()  
editmenu.add_command(label="剪切", accelerator="Ctrl+X", command=cut)  
editmenu.add_command(label="复制", accelerator="Ctrl+C", command=copy)  
editmenu.add_command(label="粘贴", accelerator="Ctrl+V", command=paste)  
editmenu.add_separator()  
editmenu.add_command(label="查找", accelerator="Ctrl+F", command=find)  
editmenu.add_command(label="全选", accelerator="Ctrl+A", command=select_all)  
menubar.add_cascade(label="编辑", menu=editmenu)  
  
# 关于 功能  
aboutmenu = Menu(top)  
aboutmenu.add_command(label="作者", command=author)  
aboutmenu.add_command(label="版权", command=power)  
menubar.add_cascade(label="关于", menu=aboutmenu)  
  
top['menu'] = menubar  
  
# shortcutbar = Frame(top, height=25, bg='light sea green')  
# shortcutbar.pack(expand=NO, fill=X)  
# Inlabe = Label(top, width=2, bg='antique white')  
# Inlabe.pack(side=LEFT, anchor='nw', fill=Y)  
  
textPad = Text(top, undo=True)  
textPad.pack(expand=YES, fill=BOTH)  
scroll = Scrollbar(textPad)  
textPad.config(yscrollcommand=scroll.set)  
scroll.config(command=textPad.yview)  
scroll.pack(side=RIGHT, fill=Y)  
  
# 热键绑定  
textPad.bind("<Control-N>", mynew)  
textPad.bind("<Control-n>", mynew)  
textPad.bind("<Control-O>", myopen)  
textPad.bind("<Control-o>", myopen)  
textPad.bind("<Control-S>", mysave)  
textPad.bind("<Control-s>", mysave)  
textPad.bind("<Control-A>", select_all)  
textPad.bind("<Control-a>", select_all)  
textPad.bind("<Control-F>", find)  
textPad.bind("<Control-f>", find)  
  
textPad.bind("<Button-3>", mypopup)  
top.mainloop()  

3、登录和注册

1. 案例介绍

本例设计一个用户登录和注册模块,使用 Tkinter 框架构建界面,主要用到画布、文本框、按钮等组件。涉及知识点:Python Tkinter 界面编程、pickle 数据存储。本例实现了基本的用户登录和注册互动界面,并提供用户信息存储和验证。pickle 是 python 语言的一个标准模块,安装 python 后已包含 pickle 库,不需要单独再安装。pickle 模块实现了基本的数据序列化和反序列化。通过 pickle 模块的序列化操作能够将程序中运行的对象信息保存到文件中去,永久存储;通过 pickle 模块的反序列化操作,能够从文件中创建上一次程序保存的对象。本例难度为中级,适合具有 Python 基础和 Tkinter 组件编程知识的用户学习。

贪吃蛇是一款经典的益智游戏,简单又耐玩。该游戏通过控制蛇头方向吃蛋,从而使得蛇变得越来越长。通过上下左右方向键控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴,等到了一定的分数,就能过关,然后继续玩下一关。本例难度为中级,适合具有 Python 基础和 Pygame 编程知识的用户学习。

2. 设计要点

游戏是基于 PyGame 框架制作的,程序核心逻辑如下:游戏界面分辨率是 640*480,蛇和食物都是由 1 个或多个 20*20 像素的正方形块儿(为了方便,下文用点表示 20*20 像素的正方形块儿) 组成,这样共有 32*24 个点,使用 pygame.draw.rect 来绘制每一个点;初始化时蛇的长度是 3,食物是 1 个点,蛇初始的移动的方向是右,用一个数组代表蛇,数组的每个元素是蛇每个点的坐标,因此数组的第一个坐标是蛇尾,最后一个坐标是蛇头;游戏开始后,根据蛇的当前移动方向,将蛇运动方向的前方的那个点 append 到蛇数组的末位,再把蛇尾去掉,蛇的坐标数组就相当于往前挪了一位;如果蛇吃到了食物,即蛇头的坐标等于食物的坐标,那么在第 2 点中蛇尾就不用去掉,就产生了蛇长度增加的效果;食物被吃掉后,随机在空的位置(不能与蛇的身体重合) 再生成一个;通过 PyGame 的 event 监控按键,改变蛇的方向,例如当蛇向右时,下一次改变方向只能向上或者向下;当蛇撞上自身或墙壁,游戏结束,蛇头装上自身,那么蛇坐标数组里就有和舌头坐标重复的数据,撞上墙壁则是蛇头坐标超过了边界,都很好判断;其他细节:做了个开始的欢迎界面;食物的颜色随机生成;吃到实物的时候有声音提示等。

3. 示例效果

4. 示例源码

import pygame  
from os import path  
from sys import exit  
from time import sleep  
from random import choice  
from itertools import product  
from pygame.locals import QUIT, KEYDOWN  
  
  
def direction_check(moving_direction, change_direction):  
    directions = [['up', 'down'], ['left', 'right']]  
    if moving_direction in directions[0] and change_direction in directions[1]:  
        return change_direction  
    elif moving_direction in directions[1] and change_direction in directions[0]:  
        return change_direction  
    return moving_direction  
  
  
class Snake:  
    colors = list(product([0, 64, 128, 192, 255], repeat=3))[1:-1]  
  
    def __init__(self):  
        self.map = {(x, y): 0 for x in range(32) for y in range(24)}  
        self.body = [[100, 100], [120, 100], [140, 100]]  
        self.head = [140, 100]  
        self.food = []  
        self.food_color = []  
        self.moving_direction = 'right'  
        self.speed = 4  
        self.generate_food()  
        self.game_started = False  
  
    def check_game_status(self):  
        if self.body.count(self.head) > 1:  
            return True  
        if self.head[0] < 0 or self.head[0] > 620 or self.head[1] < 0 or self.head[1] > 460:  
            return True  
        return False  
  
    def move_head(self):  
        moves = {  
            'right': (20, 0),  
            'up': (0, -20),  
            'down': (0, 20),  
            'left': (-20, 0)  
        }  
        step = moves[self.moving_direction]  
        self.head[0] += step[0]  
        self.head[1] += step[1]  
  
    def generate_food(self):  
        self.speed = len(  
            self.body) // 16 if len(self.body) // 16 > 4 else self.speed  
        for seg in self.body:  
            x, y = seg  
            self.map[x // 20, y // 20] = 1  
        empty_pos = [pos for pos in self.map.keys() if not self.map[pos]]  
        result = choice(empty_pos)  
        self.food_color = list(choice(self.colors))  
        self.food = [result[0] * 20, result[1] * 20]  
  
  
def main():  
    key_direction_dict = {  
        119: 'up',  # W  
        115: 'down',  # S  
        97: 'left',  # A  
        100: 'right',  # D  
        273: 'up',  # UP  
        274: 'down',  # DOWN  
        276: 'left',  # LEFT  
        275: 'right',  # RIGHT  
    }  
  
    fps_clock = pygame.time.Clock()  
    pygame.init()  
    pygame.mixer.init()  
    snake = Snake()  
    sound = False  
    if path.exists('eat.wav'):  
        sound_wav = pygame.mixer.Sound("eat.wav")  
        sound = True  
    title_font = pygame.font.SysFont('simsunnsimsun', 32)  
    welcome_words = title_font.render(  
        '贪吃蛇', True, (0, 0, 0), (255, 255, 255))  
    tips_font = pygame.font.SysFont('simsunnsimsun', 20)  
    start_game_words = tips_font.render(  
        '点击开始', True, (0, 0, 0), (255, 255, 255))  
    close_game_words = tips_font.render(  
        '按ESC退出', True, (0, 0, 0), (255, 255, 255))  
    gameover_words = title_font.render(  
        '游戏结束', True, (205, 92, 92), (255, 255, 255))  
    win_words = title_font.render(  
        '蛇很长了,你赢了!', True, (0, 0, 205), (255, 255, 255))  
    screen = pygame.display.set_mode((640, 480), 0, 32)  
    pygame.display.set_caption('贪吃蛇')  
    new_direction = snake.moving_direction  
    while 1:  
        for event in pygame.event.get():  
            if event.type == QUIT:  
                exit()  
            elif event.type == KEYDOWN:  
                if event.key == 27:  
                    exit()  
                if snake.game_started and event.key in key_direction_dict:  
                    direction = key_direction_dict[event.key]  
                    new_direction = direction_check(  
                        snake.moving_direction, direction)  
            elif (not snake.game_started) and event.type == pygame.MOUSEBUTTONDOWN:  
                x, y = pygame.mouse.get_pos()  
                if 213 <= x <= 422 and 304 <= y <= 342:  
                    snake.game_started = True  
        screen.fill((255, 255, 255))  
        if snake.game_started:  
            snake.moving_direction = new_direction  # 在这里赋值,而不是在event事件的循环中赋值,避免按键太快  
            snake.move_head()  
            snake.body.append(snake.head[:])  
            if snake.head == snake.food:  
                if sound:  
                    sound_wav.play()  
                snake.generate_food()  
            else:  
                snake.body.pop(0)  
            for seg in snake.body:  
                pygame.draw.rect(screen, [0, 0, 0], [  
                    seg[0], seg[1], 20, 20], 0)  
            pygame.draw.rect(screen, snake.food_color, [  
                snake.food[0], snake.food[1], 20, 20], 0)  
            if snake.check_game_status():  
                screen.blit(gameover_words, (241, 310))  
                pygame.display.update()  
                snake = Snake()  
                new_direction = snake.moving_direction  
                sleep(3)  
            elif len(snake.body) == 512:  
                screen.blit(win_words, (33, 210))  
                pygame.display.update()  
                snake = Snake()  
                new_direction = snake.moving_direction  
                sleep(3)  
        else:  
            screen.blit(welcome_words, (240, 150))  
            screen.blit(start_game_words, (246, 310))  
            screen.blit(close_game_words, (246, 350))  
        pygame.display.update()  
        fps_clock.tick(snake.speed)  
  
  
if __name__ == '__main__':  
    main()  

3、俄罗斯方块

1. 案例介绍

俄罗斯方块是由 4 个小方块组成不同形状的板块,随机从屏幕上方落下,按方向键调整板块的位置和方向,在底部拼出完整的一行或几行。这些完整的横条会消失,给新落下来的板块腾出空间,并获得分数奖励。没有被消除掉的方块不断堆积,一旦堆到顶端,便告输,游戏结束。本例难度为高级,适合具有 Python 进阶和 Pygame 编程技巧的用户学习。

2. 设计要点

边框――由 15*25 个空格组成,方块就落在这里面。盒子――组成方块的其中小方块,是组成方块的基本单元。方块――从边框顶掉下的东西,游戏者可以翻转和改变位置。每个方块由 4 个盒子组成。形状――不同类型的方块。这里形状的名字被叫做 T, S, Z ,J, L, I , O。如下图所示:

模版――用一个列表存放形状被翻转后的所有可能样式。全部存放在变量里,变量名字如 S or J。着陆――当一个方块到达边框的底部或接触到在其他的盒子话,就说这个方块着陆了。那样的话,另一个方块就会开始下落。

3. 示例效果

4. 示例源码

import pygame  
import random  
import os  
  
pygame.init()  
  
GRID_WIDTH = 20  
GRID_NUM_WIDTH = 15  
GRID_NUM_HEIGHT = 25  
WIDTH, HEIGHT = GRID_WIDTH * GRID_NUM_WIDTH, GRID_WIDTH * GRID_NUM_HEIGHT  
SIDE_WIDTH = 200  
SCREEN_WIDTH = WIDTH + SIDE_WIDTH  
WHITE = (0xff, 0xff, 0xff)  
BLACK = (0, 0, 0)  
LINE_COLOR = (0x33, 0x33, 0x33)  
  
CUBE_COLORS = [  
    (0xcc, 0x99, 0x99), (0xff, 0xff, 0x99), (0x66, 0x66, 0x99),  
    (0x99, 0x00, 0x66), (0xff, 0xcc, 0x00), (0xcc, 0x00, 0x33),  
    (0xff, 0x00, 0x33), (0x00, 0x66, 0x99), (0xff, 0xff, 0x33),  
    (0x99, 0x00, 0x33), (0xcc, 0xff, 0x66), (0xff, 0x99, 0x00)  
]  
  
screen = pygame.display.set_mode((SCREEN_WIDTH, HEIGHT))  
pygame.display.set_caption("俄罗斯方块")  
clock = pygame.time.Clock()  
FPS = 30  
  
score = 0  
level = 1  
  
screen_color_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]  
  
# 设置游戏的根目录为当前文件夹  
base_folder = os.path.dirname(__file__)  
  
  
def show_text(surf, text, size, x, y, color=WHITE):  
    font_name = os.path.join(base_folder, 'font/font.ttc')  
    font = pygame.font.Font(font_name, size)  
    text_surface = font.render(text, True, color)  
    text_rect = text_surface.get_rect()  
    text_rect.midtop = (x, y)  
    surf.blit(text_surface, text_rect)  
  
  
class CubeShape(object):  
    SHAPES = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']  
    I = [[(0, -1), (0, 0), (0, 1), (0, 2)],  
         [(-1, 0), (0, 0), (1, 0), (2, 0)]]  
    J = [[(-2, 0), (-1, 0), (0, 0), (0, -1)],  
         [(-1, 0), (0, 0), (0, 1), (0, 2)],  
         [(0, 1), (0, 0), (1, 0), (2, 0)],  
         [(0, -2), (0, -1), (0, 0), (1, 0)]]  
    L = [[(-2, 0), (-1, 0), (0, 0), (0, 1)],  
         [(1, 0), (0, 0), (0, 1), (0, 2)],  
         [(0, -1), (0, 0), (1, 0), (2, 0)],  
         [(0, -2), (0, -1), (0, 0), (-1, 0)]]  
    O = [[(0, 0), (0, 1), (1, 0), (1, 1)]]  
    S = [[(-1, 0), (0, 0), (0, 1), (1, 1)],  
         [(1, -1), (1, 0), (0, 0), (0, 1)]]  
    T = [[(0, -1), (0, 0), (0, 1), (-1, 0)],  
         [(-1, 0), (0, 0), (1, 0), (0, 1)],  
         [(0, -1), (0, 0), (0, 1), (1, 0)],  
         [(-1, 0), (0, 0), (1, 0), (0, -1)]]  
    Z = [[(0, -1), (0, 0), (1, 0), (1, 1)],  
         [(-1, 0), (0, 0), (0, -1), (1, -1)]]  
    SHAPES_WITH_DIR = {  
        'I': I, 'J': J, 'L': L, 'O': O, 'S': S, 'T': T, 'Z': Z  
    }  
  
    def __init__(self):  
        self.shape = self.SHAPES[random.randint(0, len(self.SHAPES) - 1)]  
        # 骨牌所在的行列  
        self.center = (2, GRID_NUM_WIDTH // 2)  
        self.dir = random.randint(0, len(self.SHAPES_WITH_DIR[self.shape]) - 1)  
        self.color = CUBE_COLORS[random.randint(0, len(CUBE_COLORS) - 1)]  
  
    def get_all_gridpos(self, center=None):  
        curr_shape = self.SHAPES_WITH_DIR[self.shape][self.dir]  
        if center is None:  
            center = [self.center[0], self.center[1]]  
  
        return [(cube[0] + center[0], cube[1] + center[1])  
                for cube in curr_shape]  
  
    def conflict(self, center):  
        for cube in self.get_all_gridpos(center):  
            # 超出屏幕之外,说明不合法  
            if cube[0] < 0 or cube[1] < 0 or cube[0] >= GRID_NUM_HEIGHT or \  
                    cube[1] >= GRID_NUM_WIDTH:  
                return True  
  
            # 不为None,说明之前已经有小方块存在了,也不合法  
            if screen_color_matrix[cube[0]][cube[1]] is not None:  
                return True  
  
        return False  
  
    def rotate(self):  
        new_dir = self.dir + 1  
        new_dir %= len(self.SHAPES_WITH_DIR[self.shape])  
        old_dir = self.dir  
        self.dir = new_dir  
        if self.conflict(self.center):  
            self.dir = old_dir  
            return False  
  
    def down(self):  
        # import pdb; pdb.set_trace()  
        center = (self.center[0] + 1, self.center[1])  
        if self.conflict(center):  
            return False  
  
        self.center = center  
        return True  
  
    def left(self):  
        center = (self.center[0], self.center[1] - 1)  
        if self.conflict(center):  
            return False  
        self.center = center  
        return True  
  
    def right(self):  
        center = (self.center[0], self.center[1] + 1)  
        if self.conflict(center):  
            return False  
        self.center = center  
        return True  
  
    def draw(self):  
        for cube in self.get_all_gridpos():  
            pygame.draw.rect(screen, self.color,  
                             (cube[1] * GRID_WIDTH, cube[0] * GRID_WIDTH,  
                              GRID_WIDTH, GRID_WIDTH))  
            pygame.draw.rect(screen, WHITE,  
                             (cube[1] * GRID_WIDTH, cube[0] * GRID_WIDTH,  
                              GRID_WIDTH, GRID_WIDTH),  
                             1)  
  
  
def draw_grids():  
    for i in range(GRID_NUM_WIDTH):  
        pygame.draw.line(screen, LINE_COLOR,  
                         (i * GRID_WIDTH, 0), (i * GRID_WIDTH, HEIGHT))  
  
    for i in range(GRID_NUM_HEIGHT):  
        pygame.draw.line(screen, LINE_COLOR,  
                         (0, i * GRID_WIDTH), (WIDTH, i * GRID_WIDTH))  
  
    pygame.draw.line(screen, WHITE,  
                     (GRID_WIDTH * GRID_NUM_WIDTH, 0),  
                     (GRID_WIDTH * GRID_NUM_WIDTH, GRID_WIDTH * GRID_NUM_HEIGHT))  
  
  
def draw_matrix():  
    for i, row in zip(range(GRID_NUM_HEIGHT), screen_color_matrix):  
        for j, color in zip(range(GRID_NUM_WIDTH), row):  
            if color is not None:  
                pygame.draw.rect(screen, color,  
                                 (j * GRID_WIDTH, i * GRID_WIDTH,  
                                  GRID_WIDTH, GRID_WIDTH))  
                pygame.draw.rect(screen, WHITE,  
                                 (j * GRID_WIDTH, i * GRID_WIDTH,  
                                  GRID_WIDTH, GRID_WIDTH), 2)  
  
  
def draw_score():  
    show_text(screen, u'得分:{}'.format(score), 20, WIDTH + SIDE_WIDTH // 2, 100)  
  
  
def remove_full_line():  
    global screen_color_matrix  
    global score  
    global level  
    new_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]  
    index = GRID_NUM_HEIGHT - 1  
    n_full_line = 0  
    for i in range(GRID_NUM_HEIGHT - 1, -1, -1):  
        is_full = True  
        for j in range(GRID_NUM_WIDTH):  
            if screen_color_matrix[i][j] is None:  
                is_full = False  
                continue  
        if not is_full:  
            new_matrix[index] = screen_color_matrix[i]  
            index -= 1  
        else:  
            n_full_line += 1  
    score += n_full_line  
    level = score // 20 + 1  
    screen_color_matrix = new_matrix  
  
  
def show_welcome(screen):  
    show_text(screen, u'俄罗斯方块', 30, WIDTH / 2, HEIGHT / 2)  
    show_text(screen, u'按任意键开始游戏', 20, WIDTH / 2, HEIGHT / 2 + 50)  
  
  
running = True  
gameover = True  
counter = 0  
live_cube = None  
while running:  
    clock.tick(FPS)  
    for event in pygame.event.get():  
        if event.type == pygame.QUIT:  
            running = False  
        elif event.type == pygame.KEYDOWN:  
            if gameover:  
                gameover = False  
                live_cube = CubeShape()  
                break  
            if event.key == pygame.K_LEFT:  
                live_cube.left()  
            elif event.key == pygame.K_RIGHT:  
                live_cube.right()  
            elif event.key == pygame.K_DOWN:  
                live_cube.down()  
            elif event.key == pygame.K_UP:  
                live_cube.rotate()  
            elif event.key == pygame.K_SPACE:  
                while live_cube.down() == True:  
                    pass  
            remove_full_line()  
  
    # level 是为了方便游戏的难度,level 越高 FPS // level 的值越小  
    # 这样屏幕刷新的就越快,难度就越大  
    if gameover is False and counter % (FPS // level) == 0:  
        # down 表示下移骨牌,返回False表示下移不成功,可能超过了屏幕或者和之前固定的  
        # 小方块冲突了  
        if live_cube.down() == False:  
            for cube in live_cube.get_all_gridpos():  
                screen_color_matrix[cube[0]][cube[1]] = live_cube.color  
            live_cube = CubeShape()  
            if live_cube.conflict(live_cube.center):  
                gameover = True  
                score = 0  
                live_cube = None  
                screen_color_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]  
        # 消除满行  
        remove_full_line()  
    counter += 1  
    # 更新屏幕  
    screen.fill(BLACK)  
    draw_grids()  
    draw_matrix()  
    draw_score()  
    if live_cube is not None:  
        live_cube.draw()  
    if gameover:  
        show_welcome(screen)  
    pygame.display.update()  

4、连连看

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()

读者福利:如果大家对Python感兴趣,这套python学习资料一定对你有用

对于0基础小白入门:

如果你是零基础小白,想快速入门Python是可以考虑的。

一方面是学习时间相对较短,学习内容更全面更集中。
二方面是可以根据这些资料规划好学习计划和方向。

包括:Python激活码+安装包、Python web开发,Python爬虫,Python数据分析,人工智能、机器学习、Python量化交易等习教程。带你从零基础系统性的学好Python!

零基础Python学习资源介绍

① Python所有方向的学习路线图,清楚各个方向要学什么东西

② 600多节Python课程视频,涵盖必备基础、爬虫和数据分析

③ 100多个Python实战案例,含50个超大型项目详解,学习不再是只会理论

④ 20款主流手游迫解 爬虫手游逆行迫解教程包

⑤ 爬虫与反爬虫攻防教程包,含15个大型网站迫解

⑥ 爬虫APP逆向实战教程包,含45项绝密技术详解

⑦ 超300本Python电子好书,从入门到高阶应有尽有

⑧ 华为出品独家Python漫画教程,手机也能学习

⑨ 历年互联网企业Python面试真题,复习时非常方便

在这里插入图片描述

👉Python学习路线汇总👈

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。(全套教程文末领取哈)
在这里插入图片描述

👉Python必备开发工具👈

温馨提示:篇幅有限,已打包文件夹,获取方式在:文末

👉Python学习视频600合集👈

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

👉实战案例👈

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

在这里插入图片描述

👉100道Python练习题👈

检查学习结果。

👉面试刷题👈

在这里插入图片描述

在这里插入图片描述

资料领取

这份完整版的Python全套学习资料已经上传网盘,朋友们如果需要可以点击下方微信卡片免费领取 ↓↓↓【保证100%免费】

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐