一个基于Python的tkinter模块实现的游戏活动日历模拟器

文章介绍了一个使用Python的tkinter模块创建的游戏活动日历模拟器,该模拟器能解析Excel表格中的活动数据,展示活动的触发时间、运行时间等信息。用户可以切换开服日期,查看未来活动安排。源代码已提供。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个基于Python的tkinter模块实现的游戏活动日历模拟器

1、Python环境准备

  1. 运行该项目需要Python3以上的版本
  2. 需安装excel解析模块,命令为:pip install xlrd==1.2.0

2、简单介绍

该项目使用Python的tkinter模块编写显示界面,通过解析excel表格中的活动数据,将各个活动的触发时间、运行时间、活动名、活动类型等以直观的方式展现出来。

3、源代码

#encoding: utf-8

import os, sys, time, datetime
import xlrd
import functools
from tkinter import *
from tkinter import messagebox


class ActivityBase:
    def __init__(self, id, name, type, cycleBeginType, actBeginTime, actEndTime, cycle, cycleContinueTime, beginSvrOpenDays, endSvrOpenDays):
        # 活动id
        self.id = id
        # 名称
        self.name = name
        # 活动类型
        self.type = type
        # 周期起始时间类型
        self.cycleBeginType = cycleBeginType
        # 活动开始时间
        self.actBeginTime = actBeginTime
        # 活动结束时间
        self.actEndTime = actEndTime
        # 循环周期
        self.cycle = cycle
        # 周期内持续时间
        self.cycleContinueTime = cycleContinueTime
        # 开始开服天数
        self.beginSvrOpenDays = beginSvrOpenDays
        # 结束开服天数
        self.endSvrOpenDays = endSvrOpenDays

    def __getOneDateZeroClockSec(self, sec):
        return int(time.mktime(time.strptime(time.strftime('%Y-%m-%d', time.localtime(sec)), '%Y-%m-%d')))

    # 获取活动首次开启时间
    def __getActRealBeginTime(self, initTime):
        #按活动开启时间算
        if self.cycleBeginType == 1:
            return self.actBeginTime

        #开服当天零点
        actBeginTime = initTime + (self.beginSvrOpenDays - 1) * 86400
        actBeginTime += (self.actBeginTime - self.__getOneDateZeroClockSec(self.actBeginTime))
        return actBeginTime

    # 获取活动当次循环开启时间
    def getCurCycleBeginTime(self, initTime, now):
        passDay = (now - initTime) // 86400 + 1
        if passDay < self.beginSvrOpenDays or passDay > self.endSvrOpenDays:
            return -1

        if now < self.actBeginTime or now > self.actEndTime:
            return -1

        actBeginTime = self.__getActRealBeginTime(initTime)
        cycle = self.cycle * 86400
        if cycle <= 0:
            return actBeginTime

        return (now - actBeginTime) // cycle * cycle + actBeginTime

    # 获取活动当次循环结束时间
    def getCurCycleEndTime(self, initTime,now):
        if self.cycleContinueTime <= 0:
            return self.actEndTime

        return self.getCurCycleBeginTime(initTime, now) + self.cycleContinueTime

class ContentData:
    def __init__(self, id, color, name, leaveDay, type):
        # 活动id
        self.id = id
        # 显示颜色
        self.color = color
        # 名称
        self.name = name 
        # 活动剩余时间
        self.leaveDay = leaveDay
        # 活动类型
        self.type = type

class ActivityCalendarMgr:
    def __init__(self):
        self.__curDay = 0
        self.__initTime = 0
        self.__oneDay = 0
        self.__startDay = 0
        self.__curWeek = 0
        self.__labW = 150
        self.__labH = 40
        self.__labHNum = 18
        self.__bottomH = 30
        self.__winW = self.__labW * 7
        self.__winH = self.__labH * self.__labHNum + self.__bottomH
        self.__root = Tk()
        self.__labelList = list()
        self.__cycleBeginTypeInt = {
            u'活动开始时间' : 1,
            u'开始开服天数' : 2,
            u'角色创建天数' : 3
        }
        self.__actInfoList = list()

    #初始化
    def __init(self, load= True, year = None, month = None, day = None):
        if year != None and month != None and day != None:
            self.__curDay = datetime.datetime(year, month, day)
        else:
            self.__curDay = datetime.datetime.now()
        self.__initTime = int(time.mktime(time.strptime(self.__curDay.strftime('%Y-%m-%d'), '%Y-%m-%d')))
        # 设置一天为基准的变量
        self.__oneDay = datetime.timedelta(days=1)
        # 这周起始日期,即程序显示起始日期
        self.__startDay = self.__curDay - self.__oneDay * self.__curDay.weekday()
        # 当前处于第几周
        self.__curWeek = 0

        if load == True:
            self.__readExcel('活动配置表.xlsm', '活动配置表')
            self.__componentInit()

        self.__showContent()

    #读取excel数据
    def __readExcel(self, xls_file, sheet_name):
        fieldList = [u'ID', u'名称', u'类型', u'周期起始时间类型', u'活动开始时间', u'活动结束时间', u'循环周期', u'周期内持续时间', u'开始开服天数', u'结束开服天数']
        titleDic = dict()

        xls = xlrd.open_workbook(xls_file);
        sheet = xls.sheet_by_name(sheet_name);

        # 取出列名
        for col in range(0, sheet.ncols):
            title = sheet.cell_value(0, col)
            titleDic[title] = col

        fieldDic = dict()

        # 一行一行读出列值
        for row in range(1, sheet.nrows):
            for field in fieldList:
                col = titleDic[field]
                fieldDic.setdefault(field, []).append(sheet.cell_value(row, col))

        for row in range(0, sheet.nrows - 1):
            id = int(fieldDic[u'ID'][row])
            name = fieldDic[u'名称'][row]
            type = fieldDic[u'类型'][row]
            cycleBeginType = self.__cycleBeginTypeInt[fieldDic[u'周期起始时间类型'][row]]
            timeBuf = time.strptime(fieldDic[u'活动开始时间'][row], '%Y/%m/%d %H:%M:%S')
            actBeginTime = int(time.mktime(timeBuf))
            timeBuf = time.strptime(fieldDic[u'活动结束时间'][row], '%Y/%m/%d %H:%M:%S')
            actEndTime = int(time.mktime(timeBuf))
            if actEndTime < self.__initTime:
                continue

            toIntValue = lambda x : 0 if x == '' else int(x)
            cycle = toIntValue(fieldDic[u'循环周期'][row])
            cycleContinueTime = toIntValue(fieldDic[u'周期内持续时间'][row])
            beginSvrOpenDays = toIntValue(fieldDic[u'开始开服天数'][row])
            endSvrOpenDays = toIntValue(fieldDic[u'结束开服天数'][row])

            actInfo = ActivityBase(id, name, type, cycleBeginType, actBeginTime, 
                actEndTime, cycle, cycleContinueTime, beginSvrOpenDays, endSvrOpenDays)

            self.__actInfoList.append(actInfo)

    #翻页
    def __turnPage(self, num):
        if num < 0 and self.__curWeek <= 0:
            return
        self.__curWeek += num
        #调用一下刷新内容
        self.__showContent()

    #切换开服日期
    def __changeOpenDay(self, t1, t2, t3):
        year = int(t1.get().strip())
        month = int(t2.get().strip())
        day = int(t3.get().strip())

        try:
            date = datetime.datetime(year, month, day)
        except:
            messagebox.showwarning(u'警示', u'输入日期 %d/%d/%d 不合法'%(year, month, day))
            self.__root.focus_force()
            return

        self.__init(False, year, month, day)

    #按键输入捕获
    def __keyPressBind(self, e, t1, t2, m):
        #lambda e: e if e.keycode != 299 and e.char in set('0123456789') else 'break'
        if (e.keycode == 8     #Space
            or e.keycode == 9  #Tab
            or e.keycode == 35 #End
            or e.keycode == 36 #Home
            or e.keycode == 46 #Delete
            ):
            pass
        elif e.keycode == 37: #Left
            if t1 != None and e.widget.index(INSERT) == 0:
                t1.focus_set()
                t1.icursor(END)
        elif e.keycode == 39: #Right
            if t2 != None and e.widget.index(INSERT) == len(e.widget.get().strip()):
                t2.focus_set()
                t2.icursor(0)
        elif e.char in set('0123456789'):
            num = len(e.widget.get().strip())
            flag = e.widget.select_present()

            #数据输入满了,偏移光标(讨巧使用了模拟按下TAB键操作)
            if t2 != None and flag != True and num == m - 1:
                t2.focus_set()
                t2.select_range(0, END)
                t2.icursor(END)

            if flag != True and num >= m:
                return 'break'
        else:
            return 'break'

        return ''

    #ctrl+a捕获
    def __selectAllBind(self, e):
        e.widget.select_range(0, END)
        e.widget.icursor(END)
        return 'break'

    #组件初始化
    def __componentInit(self):
        textHegiht = self.__bottomH - 3
        startX = 458
        text1Weight = 32
        text2Weight = 18
        separateWeight = 10

        text1 = Entry(self.__root, background = 'white', borderwidth = 1, takefocus = True)
        text1.insert(0, datetime.date.today().year)
        text1.bind('<Control-a>', self.__selectAllBind)
        text1.bind('<Control-A>', self.__selectAllBind)
        text1.place(x = startX, y = 0, width = text1Weight, height = textHegiht)

        separate1 = Text(self.__root, background = 'white', borderwidth = 0)
        separate1.insert(INSERT, '-')
        separate1.config(state = DISABLED)
        separate1.place(x = startX + text1Weight, y = 6, width = separateWeight, height = self.__bottomH)

        text2 = Entry(self.__root, background = 'white', borderwidth = 1, takefocus = True)
        text2.insert(0, datetime.date.today().month)
        text2.bind('<Control-a>', self.__selectAllBind)
        text2.bind('<Control-A>', self.__selectAllBind)
        text2.place(x = startX + text1Weight + separateWeight, y = 0, width = text2Weight, height = textHegiht)

        separate2 = Text(self.__root, background = 'white', borderwidth = 0)
        separate2.insert(INSERT, '-')
        separate2.config(state = DISABLED)
        separate2.place(x = startX + text1Weight + text2Weight + separateWeight, y = 6, width = separateWeight, height = self.__bottomH)

        text3 = Entry(self.__root, background = 'white', borderwidth = 1, takefocus = True)
        text3.insert(0, datetime.date.today().day)
        text3.bind('<Control-a>', self.__selectAllBind)
        text3.bind('<Control-A>', self.__selectAllBind)
        text3.place(x = startX + text1Weight + text2Weight + separateWeight * 2, y = 0, width = text2Weight, height = textHegiht)

        #输入框绑定按键行为
        text1.bind('<KeyPress>', lambda event : self.__keyPressBind(event, None, text2, 4))
        text2.bind('<KeyPress>', lambda event : self.__keyPressBind(event, text1, text3, 2))
        text3.bind('<KeyPress>', lambda event : self.__keyPressBind(event, text2, None, 2))

        #切换开服日期按钮
        button = Button(self.__root, text = '切换开服日期', command = lambda : self.__changeOpenDay(text1, text2, text3), bg = 'white', activebackground = 'white')
        button.place(x = startX + text1Weight + text2Weight * 2 + separateWeight * 2 + 10, y = 0, width = 78, height = textHegiht)

        #绑定翻页行为(键盘左右键、鼠标滚轮)
        self.__root.bind('<Left>', lambda event : self.__turnPage(-1) if event.widget != text1 and event.widget != text2 and event.widget != text3 else 'break')
        self.__root.bind('<Right>', lambda event : self.__turnPage(1) if event.widget != text1 and event.widget != text2 and event.widget != text3 else 'break')
        self.__root.bind('<MouseWheel>', lambda event : self.__turnPage(-1) if event.delta > 0 else self.__turnPage(1))
        
        #绑定聚焦行为
        self.__root.bind('<Button-1>', lambda event : self.__root.focus_set() if event.widget != text1 and event.widget != text2 and event.widget != text3 else 'break')

    def __showContent(self):
        # 边框类型(relief参数: flat(默认), groove, raised, ridge, solid, sunken)
        frame = 'groove'
        # 边框宽度(像素)
        borderwidth = 1

        #1.清屏(只执行一次)
        if len(self.__labelList) == 0:
            for y_index in range(self.__labHNum):
                for x_index in range(7):
                    label = Label(self.__root, bg = 'white', relief = frame, bd = borderwidth)
                    label.place(x = self.__labW * x_index, y = self.__labH * y_index + self.__bottomH, width = self.__labW, height = self.__labH)

        #2.释放旧标签内存
        for l in self.__labelList:
            l.destroy()
        self.__labelList = list()

        #3.显示顶部栏
        labels = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
        for index in range(7):
            day = self.__startDay + self.__oneDay * (self.__curWeek * 7 + index)
            title = str(labels[index]) + '\n' + day.strftime('%Y-%m-%d')
            color = 'grey'
            if day == self.__curDay:
                color = 'yellow'

            label = Label(self.__root, bg = color, text = title, relief = frame, bd = borderwidth)
            label.place(x = + self.__labW * index, y = self.__bottomH, width = self.__labW, height = self.__labH)
            self.__labelList.append(label)

        #4.内容栏数据提取
        fieldSet = [set() for row in range(7)]
        contentList = [list() for row in range(7)]
        for index in range(7):
            day = self.__startDay + self.__oneDay * (self.__curWeek * 7 + index)
            color = 'SpringGreen'
            if day < self.__curDay:
                continue
            elif day == self.__curDay:
                color = 'yellow'

            for actInfo in self.__actInfoList:
                # 强行给 curTime 加20个小时,兼容特殊活动
                curTime = int(time.mktime(time.strptime(day.strftime('%Y-%m-%d'), '%Y-%m-%d'))) + 72000
                beginTime = actInfo.getCurCycleBeginTime(self.__initTime, curTime)
                if beginTime == -1:
                    continue
                endTime = actInfo.getCurCycleEndTime(self.__initTime, curTime)
                
                if beginTime <= curTime and curTime <= endTime:
                    if actInfo.id in fieldSet[index]:
                        continue

                    leaveDay = (endTime - curTime) // 86400 + 1
                    if index + leaveDay > 7:
                        leaveDay = 7 - index

                    contentList[index].append(ContentData(actInfo.id, color, actInfo.name, int(leaveDay), actInfo.type))

                    for i in range(leaveDay):
                        fieldSet[index + i].add(actInfo.id)

        #5.数据刷新显示
        yIndex = 0
        for index in range(7):
            #先排序一下,先比活动剩余时间,时间越长越靠前;再比活动id,id越小越靠前
            contentList[index].sort(key = functools.cmp_to_key(lambda x, y : x.leaveDay - y.leaveDay if x.leaveDay != y.leaveDay else y.id - x.id), reverse = True)

            for data in contentList[index]:
                yIndex += 1
                if yIndex <= self.__labHNum:
                    showText = u'%s\n%d(%s)'%(data.name, data.id, data.type)
                    label = Label(self.__root, bg = data.color, text = showText, relief = frame, bd = borderwidth)
                    label.place(x = + self.__labW * index, y = yIndex * self.__labH + 5 + self.__bottomH, width = self.__labW * data.leaveDay, height = self.__labH - 10)
                    self.__labelList.append(label)

        #6.显示上限提示
        if yIndex > self.__labHNum:
            messagebox.showwarning(u'警示', u'本周有%d个活动,显示上限为%d个,超出部分不显示'%(yIndex, self.__labHNum - 1))
            self.__root.focus_force()
            return

    def run(self):
        #显示活动日历基本框架
        self.__root.title('活动日历')
        self.__root.geometry(str(self.__winW) + 'x' + str(self.__winH) + '+400+100')
        self.__root.config(background = 'white')
        self.__root.resizable(False, False)

        #初始化数据
        self.__init()

        self.__root.mainloop()


if __name__== '__main__':
    mgr = ActivityCalendarMgr()
    mgr.run()

4、源代码及活动配置表下载

下载地址:游戏活动日历模拟器Python源码

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彼 方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值