├── README.md ├── cultivation.json ├── day05 └── day05-exercise.py ├── day07 └── main.py ├── day08 └── 智能学生管理系统.py ├── day09 └── 二十一点游戏.py ├── day10 └── 猜数字游戏.py ├── day11 └── 灵兽对战系统.py ├── day12 ├── 01-turtle基础.py ├── 02-面向对象画图.py ├── 03-tkinter.py ├── 04-海归绘图整合gui.py └── 05-灵力轨迹绘制.py ├── day13 └── day13-修仙进度跟踪系统.py ├── day14 ├── 01-游戏坐标设计.py ├── 02-游戏状态管理.py ├── arcade-3.1.0-py3-none-any.whl └── day14-贪吃蛇游戏01.py ├── day15 └── day15-贪吃蛇游戏02.py ├── day16 └── day16-乒乓游戏.py ├── day17 └── day17-海龟过马路游戏.py ├── day18 ├── 01-os模块.py ├── 02-pathlib.py ├── 03-shutil.py └── 04-project.py ├── day19 ├── 01-异常处理.py └── 02-简单重试实现.py ├── day20 ├── 01-普通程序执行时间.py └── 02-多线程执行时间.py ├── day24-Tkinter进阶与番茄钟实战 ├── 01-可变类型自动刷新.py └── 02-番茄时钟.py ├── day25-Tkinter密码管理器 └── 01-密码管理器.py ├── day26-闪卡复习系统 └── 01-闪卡复习系统.py ├── day27-日期与邮件发送 ├── 01-datetime.py ├── 02-邮件发送.py └── 03-完整代码.py ├── day28-初识API ├── 01-天气API.py ├── 02-kanye.py ├── background.png └── kanye.png ├── day29-GUI问答系统 ├── 01-api解析.py └── 02-智能问答系统.py └── vault.dat /README.md: -------------------------------------------------------------------------------- 1 | # 100-days-python 2 | Python一百天计划 3 | -------------------------------------------------------------------------------- /cultivation.json: -------------------------------------------------------------------------------- 1 | {"\u97e9\u7acb": {"stage": 0, "last_breakthrough": "2025-03-28T00:51:12.075301"}} -------------------------------------------------------------------------------- /day05/day05-exercise.py: -------------------------------------------------------------------------------- 1 | # 密码生成器项目 2 | import random 3 | letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] 4 | numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] 5 | symbols = ['!', '#', '$', '%', '&', '(', ')', '*', '+'] 6 | 7 | print("Welcome to the PyPassword Generator!") 8 | nr_letters = int(input("您希望密码包含多少个字符?\n")) 9 | nr_symbols = int(input(f"您希望密码包含多少个符号?\n")) 10 | nr_numbers = int(input(f"您希望密码包含多少个数字?\n")) 11 | 12 | #Eazy Level 13 | # password = "" 14 | 15 | # for char in range(1, nr_letters + 1): 16 | # password += random.choice(letters) 17 | 18 | # for char in range(1, nr_symbols + 1): 19 | # password += random.choice(symbols) 20 | 21 | # for char in range(1, nr_numbers + 1): 22 | # password += random.choice(numbers) 23 | 24 | # print(password) 25 | 26 | #Hard Level 27 | password_list = [] 28 | 29 | for char in range(1, nr_letters + 1): 30 | password_list.append(random.choice(letters)) 31 | 32 | for char in range(1, nr_symbols + 1): 33 | password_list += random.choice(symbols) 34 | 35 | for char in range(1, nr_numbers + 1): 36 | password_list += random.choice(numbers) 37 | 38 | print(password_list) 39 | random.shuffle(password_list) 40 | print(password_list) 41 | 42 | password = "" 43 | for char in password_list: 44 | password += char 45 | 46 | print(f"你的密码是: {password}") -------------------------------------------------------------------------------- /day07/main.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # ---------------------- 4 | # 常量定义 5 | # ---------------------- 6 | WORD_LIST = ["PYTHON", "JAVA", "JAVASCRIPT", "RUBY", "HTML", "CSS"] 7 | MAX_LIVES = 6 # 最大生命值 8 | 9 | # ---------------------- 10 | # ASCII绞刑架图形(7个阶段) 11 | # ---------------------- 12 | HANGMAN_STAGES = [ 13 | """ 14 | ------ 15 | | | 16 | O | 17 | /|\ | 18 | / \ | 19 | | 20 | """, # 生命值0(死亡) 21 | """ 22 | ------ 23 | | | 24 | O | 25 | /|\ | 26 | / | 27 | | 28 | """, # 生命值1 29 | """ 30 | ------ 31 | | | 32 | O | 33 | /|\ | 34 | | 35 | | 36 | """, # 生命值2 37 | """ 38 | ------ 39 | | | 40 | O | 41 | /| | 42 | | 43 | | 44 | """, # 生命值3 45 | """ 46 | ------ 47 | | | 48 | O | 49 | | | 50 | | 51 | | 52 | """, # 生命值4 53 | """ 54 | ------ 55 | | | 56 | O | 57 | | 58 | | 59 | | 60 | """, # 生命值5 61 | """ 62 | ------ 63 | | | 64 | | 65 | | 66 | | 67 | | 68 | """, # 生命值6(初始状态) 69 | ] 70 | 71 | 72 | # ---------------------- 73 | # 函数定义 74 | # ---------------------- 75 | def get_random_word() -> str: 76 | """从单词库随机获取一个单词""" 77 | return random.choice(WORD_LIST) 78 | 79 | 80 | def display_hangman(lives: int): 81 | """显示绞刑架当前状态""" 82 | print(HANGMAN_STAGES[lives]) 83 | 84 | 85 | def display_progress(word: str, guessed_letters: set) -> str: 86 | """显示单词猜测进度(例如 P _ T _ O _)""" 87 | return " ".join([letter if letter in guessed_letters else "_" for letter in word]) 88 | 89 | 90 | def validate_input(char: str, guessed: set) -> bool: 91 | """验证玩家输入是否符合规则""" 92 | if len(char) != 1: 93 | print("请输入单个字母!") 94 | return False 95 | if not char.isalpha(): 96 | print("请输入英文字母!") 97 | return False 98 | if char in guessed: 99 | print("这个字母已经猜过了!") 100 | return False 101 | return True 102 | 103 | 104 | # ---------------------- 105 | # 游戏主逻辑 106 | # ---------------------- 107 | def play_game(): 108 | # 初始化游戏数据 109 | target_word = get_random_word() 110 | guessed_letters = set() 111 | incorrect_guesses = [] 112 | lives = MAX_LIVES 113 | 114 | print("=== 欢迎来到Hangman猜词游戏 ===") 115 | print(f"单词长度:{len(target_word)}个字母") 116 | 117 | while lives > 0: 118 | # 显示当前状态 119 | print("\n" + "=" * 30) 120 | display_hangman(lives) 121 | print(f"已猜字母:{', '.join(incorrect_guesses)}") 122 | print("当前进度:", display_progress(target_word, guessed_letters)) 123 | 124 | # 获取玩家输入 125 | guess = input("请输入一个字母:").upper() 126 | 127 | # 输入验证 128 | if not validate_input(guess, guessed_letters.union(incorrect_guesses)): 129 | continue 130 | 131 | # 处理猜测结果 132 | if guess in target_word: 133 | guessed_letters.add(guess) 134 | # 胜利条件判断 135 | if all(letter in guessed_letters for letter in target_word): 136 | print(f"\n恭喜!你猜对了!单词是:{target_word}") 137 | return 138 | else: 139 | incorrect_guesses.append(guess) 140 | lives -= 1 141 | 142 | # 失败处理 143 | print("\n" + "=" * 30) 144 | display_hangman(0) 145 | print(f"很遗憾,游戏结束!正确单词是:{target_word}") 146 | 147 | 148 | # ---------------------- 149 | # 启动游戏 150 | # ---------------------- 151 | if __name__ == "__main__": 152 | play_game() 153 | -------------------------------------------------------------------------------- /day08/智能学生管理系统.py: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | 3 | # 系统初始化数据 4 | 5 | # -------------------------- 6 | 7 | classes = [ 8 | { # 班级1 9 | "name": "筑基一班", 10 | "students": [ 11 | { 12 | "id": 1001, 13 | "name": "韩立", 14 | "scores": {"炼丹术": 92, "御剑术": 88, "阵法": 95}, 15 | }, 16 | { 17 | "id": 1002, 18 | "name": "南宫婉", 19 | "scores": {"炼丹术": 95, "御剑术": 91, "阵法": 89}, 20 | }, 21 | ], 22 | }, 23 | { # 班级2 24 | "name": "金丹二班", 25 | "students": [ 26 | { 27 | "id": 2001, 28 | "name": "厉飞雨", 29 | "scores": {"炼丹术": 85, "御剑术": 93, "阵法": 82}, 30 | } 31 | ], 32 | }, 33 | ] 34 | 35 | # -------------------------- 36 | 37 | # 核心功能函数 38 | 39 | # -------------------------- 40 | 41 | 42 | def add_student(class_index, student_data): 43 | """添加学生到指定班级""" 44 | 45 | classes[class_index]["students"].append(student_data) 46 | 47 | 48 | def find_student(student_id): 49 | """通过ID查找学生(返回第一个匹配项)""" 50 | 51 | return next( 52 | (s for c in classes for s in c["students"] if s["id"] == student_id), None 53 | ) 54 | 55 | 56 | def analyze_scores(): 57 | """全年级成绩分析""" 58 | 59 | # 使用lambda计算各科平均分 60 | 61 | subjects = ["炼丹术", "御剑术", "阵法"] 62 | total_students = sum(1 for c in classes for s in c["students"]) 63 | averages = { 64 | sub: round(sum( 65 | s["scores"][sub] 66 | for c in classes 67 | for s in c["students"] 68 | ) / total_students, 1) 69 | for sub in subjects 70 | } 71 | 72 | # 使用lambda筛选优秀学生(平均分≥90) 73 | 74 | top_students = [ 75 | s 76 | for c in classes 77 | for s in c["students"] 78 | if (lambda scores: sum(scores.values()) / 3 >= 90)(s["scores"]) 79 | ] 80 | 81 | return {"averages": averages, "top_students": top_students} 82 | 83 | 84 | # -------------------------- 85 | 86 | # 主程序交互 87 | 88 | # -------------------------- 89 | 90 | if __name__ == "__main__": 91 | 92 | # 添加新学生 93 | 94 | new_student = { 95 | "id": 2002, 96 | "name": "墨大夫", 97 | "scores": {"炼丹术": 96, "御剑术": 85, "阵法": 90}, 98 | } 99 | 100 | add_student(1, new_student) # 添加到金丹二班 101 | 102 | # 查询学生信息 103 | 104 | target = find_student(1001) 105 | 106 | print(f"找到学生:{target['name']},炼丹术成绩:{target['scores']['炼丹术']}") 107 | 108 | # 生成分析报告 109 | 110 | report = analyze_scores() 111 | 112 | print("\n=== 全年级成绩分析 ===") 113 | 114 | print(f"各科平均分:{report['averages']}") 115 | 116 | print(f"优秀学生名单:{[s['name'] for s in report['top_students']]}") 117 | -------------------------------------------------------------------------------- /day09/二十一点游戏.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # ===================== 4 | # 全局配置 5 | # ===================== 6 | INITIAL_CHIPS = 1000 # 初始筹码 7 | # 全局常量 8 | SUITS = ["♥", "♦", "♠", "♣"] # 花色 9 | RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"] # 点数 10 | CARD_VALUES = { 11 | "A": 11, # 初始按11计算,后续动态调整 12 | "2": 2, 13 | "3": 3, 14 | "4": 4, 15 | "5": 5, 16 | "6": 6, 17 | "7": 7, 18 | "8": 8, 19 | "9": 9, 20 | "10": 10, 21 | "J": 10, 22 | "Q": 10, 23 | "K": 10, 24 | } 25 | 26 | 27 | # ===================== 28 | # 核心函数 29 | # ===================== 30 | def create_deck(num_decks=6): 31 | """创建多副牌并洗牌""" 32 | deck = [[suit, rank] for suit in SUITS for rank in RANKS] * num_decks 33 | random.shuffle(deck) 34 | return deck 35 | 36 | 37 | def calculate_score(hand): 38 | """计算手牌最优点数(自动处理A的1/11值)""" 39 | score = sum(CARD_VALUES[card[1]] for card in hand) 40 | aces = sum(1 for card in hand if card[1] == "A") 41 | 42 | # 动态调整A的值 43 | while score > 21 and aces: 44 | score -= 10 45 | aces -= 1 46 | return score 47 | 48 | 49 | def show_cards(hand, hide_first=False): 50 | """可视化显示手牌(hide_first用于隐藏庄家首牌)""" 51 | display = [] 52 | for i, card in enumerate(hand): 53 | if hide_first and i == 0: 54 | display.append("[隐藏牌]") 55 | else: 56 | display.append(f"{card[0]}{card[1]}") 57 | return " ".join(display) 58 | 59 | 60 | # ===================== 61 | # 游戏流程控制 62 | # ===================== 63 | def player_turn(deck, player_hand): 64 | """玩家操作回合""" 65 | while True: 66 | current_score = calculate_score(player_hand) 67 | print(f"\n你的手牌:{show_cards(player_hand)}") 68 | print(f"当前点数:{current_score}") 69 | 70 | if current_score >= 21: 71 | break 72 | 73 | choice = input("要牌(h)还是停牌(s)?").lower() 74 | if choice == "h": 75 | player_hand.append(deck.pop()) 76 | elif choice == "s": 77 | break 78 | else: 79 | print("无效输入,请输入h或s!") 80 | 81 | return player_hand 82 | 83 | 84 | def dealer_turn(deck, dealer_hand): 85 | """庄家自动操作""" 86 | while calculate_score(dealer_hand) < 17: 87 | dealer_hand.append(deck.pop()) 88 | return dealer_hand 89 | 90 | 91 | def check_winner(player_score, dealer_score): 92 | """胜负判定""" 93 | if player_score > 21: 94 | return "庄家胜!玩家爆牌" 95 | elif dealer_score > 21: 96 | return "玩家胜!庄家爆牌" 97 | elif player_score > dealer_score: 98 | return "玩家胜!" 99 | elif dealer_score > player_score: 100 | return "庄家胜!" 101 | else: 102 | return "平局!" 103 | 104 | 105 | # ===================== 106 | # 主游戏逻辑 107 | # ===================== 108 | def blackjack_game(): 109 | chips = INITIAL_CHIPS 110 | 111 | while chips > 0: 112 | print(f"\n=== 当前筹码:{chips} ===") 113 | bet = int(input("请下注(输入金额):")) 114 | 115 | if bet > chips: 116 | print("筹码不足!") 117 | continue 118 | 119 | # 初始化牌局 120 | deck = create_deck() 121 | player = [deck.pop(), deck.pop()] 122 | dealer = [deck.pop(), deck.pop()] 123 | 124 | # 玩家回合 125 | player = player_turn(deck, player) 126 | player_score = calculate_score(player) 127 | 128 | # 庄家回合 129 | if player_score <= 21: 130 | dealer = dealer_turn(deck, dealer) 131 | dealer_score = calculate_score(dealer) 132 | 133 | # 显示结果 134 | print("\n=== 最终结果 ===") 135 | print(f"庄家手牌:{show_cards(dealer)} → 点数:{dealer_score}") 136 | print(f"你的手牌:{show_cards(player)} → 点数:{player_score}") 137 | 138 | # 结算 139 | result = check_winner(player_score, dealer_score) 140 | print(f"\n结果:{result}") 141 | 142 | if "玩家胜" in result: 143 | chips += bet 144 | elif "庄家胜" in result: 145 | chips -= bet 146 | 147 | # 继续游戏判断 148 | if input("\n继续游戏?(y/n)").lower() != "y": 149 | break 150 | 151 | print(f"\n游戏结束,最终筹码:{chips}") 152 | 153 | 154 | # ===================== 155 | # 启动游戏 156 | # ===================== 157 | if __name__ == "__main__": 158 | print("=== 欢迎来到二十一点赌场 ===") 159 | blackjack_game() 160 | -------------------------------------------------------------------------------- /day10/猜数字游戏.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def create_game(difficulty='normal'): 4 | """创建猜数字游戏闭包""" 5 | # 根据难度设置范围 6 | ranges = { 7 | 'easy': (1, 50), 8 | 'normal': (1, 100), 9 | 'hard': (1, 200) 10 | } 11 | min_num, max_num = ranges[difficulty] 12 | 13 | secret = random.randint(min_num, max_num) 14 | attempts = 0 15 | 16 | def guess(number): 17 | """猜测数字并返回结果""" 18 | nonlocal attempts # 使用外层作用域的attempts 19 | 20 | attempts += 1 21 | 22 | if number < secret: 23 | return "太小了!", False 24 | elif number > secret: 25 | return "太大了!", False 26 | else: 27 | return f"恭喜!用了{attempts}次猜中!", True 28 | 29 | def get_hint(): 30 | """获得提示(闭包访问secret)""" 31 | return f"提示:数字在{min_num}-{max_num}之间" 32 | 33 | return guess, get_hint 34 | 35 | # -------------------------- 36 | # 游戏主程序 37 | # -------------------------- 38 | if __name__ == "__main__": 39 | print("=== 数字猜谜修仙版 ===") 40 | 41 | # 初始化游戏 42 | difficulty = input("选择难度(easy/normal/hard):") 43 | guess_func, hint_func = create_game(difficulty) 44 | print(hint_func()) 45 | 46 | # 游戏循环 47 | while True: 48 | try: 49 | user_guess = int(input("输入你的猜测:")) 50 | result, success = guess_func(user_guess) 51 | print(result) 52 | 53 | if success: 54 | break 55 | except ValueError: 56 | print("请输入有效数字!") 57 | -------------------------------------------------------------------------------- /day11/灵兽对战系统.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | # -------------------------- 5 | # 基础类定义 6 | # -------------------------- 7 | class SpiritBeast: 8 | ELEMENTS = { 9 | "火": {"克": "金", "被克": "水"}, 10 | "水": {"克": "火", "被克": "土"}, 11 | "土": {"克": "水", "被克": "木"}, 12 | "木": {"克": "土", "被克": "金"}, 13 | "金": {"克": "木", "被克": "火"}, 14 | } 15 | 16 | def __init__(self, name, element, power=100): 17 | self.name = name 18 | self.element = element 19 | self.power = power 20 | 21 | def check_element(self, target): 22 | """五行相克判定""" 23 | if self.element == self.ELEMENTS[target.element]["克"]: 24 | return 1.5 # 克制伤害加成 25 | elif self.element == self.ELEMENTS[target.element]["被克"]: 26 | return 0.5 # 被克伤害减免 27 | return 1.0 28 | 29 | def attack(self, target): 30 | damage = random.randint(10, 20) * self.check_element(target) 31 | target.power -= int(damage) 32 | print(f"{self.name}对{target.name}造成{int(damage)}点伤害!") 33 | 34 | def is_alive(self): 35 | return self.power > 0 36 | 37 | 38 | # -------------------------- 39 | # 派生类定义 40 | # -------------------------- 41 | class FirePhoenix(SpiritBeast): 42 | def __init__(self): 43 | super().__init__("朱雀", "火", 200) 44 | 45 | def special_skill(self): 46 | print("朱雀涅槃重生,恢复全部灵力!") 47 | self.power = 200 48 | 49 | 50 | class WaterDragon(SpiritBeast): 51 | def __init__(self): 52 | super().__init__("玄武", "水", 180) 53 | 54 | def heal(self): 55 | recover = random.randint(20, 30) 56 | self.power += recover 57 | print(f"玄武引动水灵,恢复{recover}点灵力!") 58 | 59 | 60 | # -------------------------- 61 | # 对战系统 62 | # -------------------------- 63 | class BattleSystem: 64 | def __init__(self, beast1, beast2): 65 | self.beasts = [beast1, beast2] 66 | 67 | def start_battle(self): 68 | round = 1 69 | while all(b.is_alive() for b in self.beasts): 70 | print(f"\n=== 第{round}回合 ===") 71 | attacker, defender = random.sample(self.beasts, 2) 72 | 73 | # 特殊技能触发 74 | if isinstance(attacker, FirePhoenix) and random.random() < 0.3: 75 | attacker.special_skill() 76 | elif isinstance(attacker, WaterDragon) and random.random() < 0.3: 77 | attacker.heal() 78 | else: 79 | attacker.attack(defender) 80 | 81 | # 显示状态 82 | for b in self.beasts: 83 | print(f"{b.name} 剩余灵力:{b.power}") 84 | 85 | round += 1 86 | 87 | winner = next(b for b in self.beasts if b.is_alive()) 88 | print(f"\n战斗结束!胜者:{winner.name}") 89 | 90 | 91 | # -------------------------- 92 | # 启动对战 93 | # -------------------------- 94 | if __name__ == "__main__": 95 | zhuque = FirePhoenix() 96 | xuanwu = WaterDragon() 97 | 98 | arena = BattleSystem(zhuque, xuanwu) 99 | arena.start_battle() -------------------------------------------------------------------------------- /day12/01-turtle基础.py: -------------------------------------------------------------------------------- 1 | import turtle 2 | 3 | # 初始化画布 4 | screen = turtle.Screen() 5 | screen.title("修仙图腾") 6 | screen.bgcolor("black") 7 | 8 | # 创建画笔 9 | pen = turtle.Turtle() 10 | pen.color("gold") 11 | pen.pensize(3) 12 | 13 | # 绘制五行阵 14 | for _ in range(5): 15 | pen.forward(100) 16 | pen.right(144) # 五角星角度 17 | 18 | pen.hideturtle() 19 | turtle.done() 20 | -------------------------------------------------------------------------------- /day12/02-面向对象画图.py: -------------------------------------------------------------------------------- 1 | import turtle 2 | 3 | 4 | class SacredPattern(turtle.Turtle): 5 | """绘制宗门秘传图案""" 6 | 7 | def __init__(self): 8 | super().__init__() 9 | self.speed(0) 10 | self.color("cyan") 11 | 12 | def draw_lotus(self, petals=8): 13 | """绘制灵力莲花""" 14 | for _ in range(petals): 15 | self.circle(50, 60) 16 | self.left(120) 17 | self.circle(50, 60) 18 | self.left(120) 19 | self.right(360 / petals) 20 | 21 | 22 | # 使用示例 23 | lotus = SacredPattern() 24 | lotus.draw_lotus() 25 | turtle.done() 26 | -------------------------------------------------------------------------------- /day12/03-tkinter.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | 3 | 4 | class SectGUI: 5 | def __init__(self): 6 | self.window = Tk() 7 | self.window.title("青云宗管理系统") 8 | 9 | # 组件布局 10 | Label(self.window, text="弟子姓名:").grid(row=0, column=0) 11 | self.name_entry = Entry(self.window) 12 | self.name_entry.grid(row=0, column=1) 13 | 14 | Button(self.window, text="录入", command=self.add_disciple).grid( 15 | row=1, columnspan=2 16 | ) 17 | 18 | # 弟子列表 19 | self.listbox = Listbox(self.window) 20 | self.listbox.grid(row=2, columnspan=2) 21 | 22 | self.window.mainloop() 23 | 24 | def add_disciple(self): 25 | name = self.name_entry.get() 26 | self.listbox.insert(END, name) 27 | self.name_entry.delete(0, END) 28 | 29 | 30 | # 启动GUI 31 | if __name__ == "__main__": 32 | SectGUI() 33 | -------------------------------------------------------------------------------- /day12/04-海归绘图整合gui.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | import turtle 3 | from turtle import RawTurtle 4 | 5 | class TurtleCanvas: 6 | """将海龟绘图嵌入GUI""" 7 | def __init__(self, master): 8 | self.canvas = Canvas(master) 9 | self.canvas.pack(side=RIGHT) 10 | 11 | # 在Canvas中创建海龟 12 | self.t = RawTurtle(self.canvas) 13 | self.t.speed(0) 14 | 15 | # 控制面板 16 | control_frame = Frame(master) 17 | control_frame.pack(side=LEFT) 18 | 19 | Button(control_frame, text="画圆", command=self.draw_circle).pack() 20 | Button(control_frame, text="清空", command=self.clear).pack() 21 | 22 | def draw_circle(self): 23 | self.t.color("red") 24 | self.t.circle(50) 25 | 26 | def clear(self): 27 | self.t.reset() 28 | 29 | # 使用示例 30 | root = Tk() 31 | app = TurtleCanvas(root) 32 | root.mainloop() 33 | -------------------------------------------------------------------------------- /day12/05-灵力轨迹绘制.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | import turtle 3 | from turtle import RawTurtle 4 | from tkinter import colorchooser, filedialog 5 | 6 | class SpiritualPainter: 7 | def __init__(self): 8 | self.window = Tk() 9 | self.window.title("灵力轨迹绘制器") 10 | 11 | # 绘图区 12 | self.canvas = Canvas(self.window, width=600, height=500) 13 | self.canvas.pack(side=LEFT) 14 | self.t = RawTurtle(self.canvas) 15 | self.t.speed(0) 16 | 17 | # 控制面板 18 | control_frame = Frame(self.window) 19 | control_frame.pack(side=RIGHT, padx=10) 20 | 21 | # 颜色选择 22 | self.color_btn = Button(control_frame, text="选择颜色", command=self.choose_color) 23 | self.color_btn.pack(pady=5) 24 | 25 | # 图形选择 26 | self.shape_var = StringVar(value="circle") 27 | OptionMenu(control_frame, self.shape_var, 28 | "circle", "star", "spiral").pack(pady=5) 29 | 30 | # 绘制按钮 31 | Button(control_frame, text="绘制", command=self.draw).pack(pady=5) 32 | Button(control_frame, text="保存", command=self.save).pack(pady=5) 33 | Button(control_frame, text="清空", command=self.clear).pack(pady=5) 34 | 35 | self.current_color = "black" 36 | 37 | def choose_color(self): 38 | color = colorchooser.askcolor()[1] 39 | if color: 40 | self.current_color = color 41 | 42 | def draw(self): 43 | self.t.color(self.current_color) 44 | shape = self.shape_var.get() 45 | 46 | if shape == "circle": 47 | self.t.circle(50) 48 | elif shape == "star": 49 | for _ in range(5): 50 | self.t.forward(100) 51 | self.t.right(144) 52 | elif shape == "spiral": 53 | for i in range(50): 54 | self.t.forward(i*2) 55 | self.t.right(91) 56 | 57 | def clear(self): 58 | self.t.reset() 59 | 60 | def save(self): 61 | path = filedialog.asksaveasfilename(defaultextension=".eps") 62 | if path: 63 | self.canvas.postscript(file=path, colormode='color') 64 | 65 | if __name__ == "__main__": 66 | app = SpiritualPainter() 67 | app.window.mainloop() 68 | -------------------------------------------------------------------------------- /day13/day13-修仙进度跟踪系统.py: -------------------------------------------------------------------------------- 1 | import json 2 | from functools import reduce 3 | from datetime import datetime 4 | 5 | 6 | def log_activity(action): 7 | """修炼日志装饰器""" 8 | 9 | def decorator(func): 10 | def wrapper(self, *args): 11 | print(f"{self.name}开始{action}...") 12 | result = func(self, *args) 13 | print(f"{action}完成!当前境界:{self.current_stage}") 14 | return result 15 | 16 | return wrapper 17 | 18 | return decorator 19 | 20 | 21 | class CultivationTracker: 22 | def __init__(self): 23 | self.disciples = {} 24 | self._load_data() 25 | 26 | def _load_data(self): 27 | try: 28 | with open("cultivation.json", encoding='utf-8') as f: 29 | data = json.load(f) 30 | self.disciples = { 31 | name: Cultivator(name, **stats) for name, stats in data.items() 32 | } 33 | except FileNotFoundError: 34 | pass 35 | 36 | def save_data(self): 37 | data = { 38 | name: {"stage": cult._stage, "last_breakthrough": cult.last_breakthrough} 39 | for name, cult in self.disciples.items() 40 | } 41 | with open("cultivation.json", "w") as f: 42 | json.dump(data, f) 43 | 44 | def add_disciple(self, name): 45 | if name not in self.disciples: 46 | self.disciples[name] = Cultivator(name) 47 | 48 | @log_activity("批量突破检测") 49 | def check_breakthroughs(self): 50 | return list( 51 | filter(lambda cult: cult.check_breakthrough(), self.disciples.values()) 52 | ) 53 | 54 | def power_ranking(self): 55 | return sorted(self.disciples.values(), key=lambda x: x.power, reverse=True) 56 | 57 | 58 | class Cultivator: 59 | def __init__(self, name, stage=0, last_breakthrough=None): 60 | self.name = name 61 | self._stage = stage 62 | self.power = 1000 * (2**stage) 63 | self.last_breakthrough = last_breakthrough or datetime.now().isoformat() 64 | 65 | def check_breakthrough(self): 66 | if self.power >= 2000 * (2**self._stage): 67 | self._stage += 1 68 | self.last_breakthrough = datetime.now().isoformat() 69 | return True 70 | return False 71 | 72 | @log_activity("运转周天") 73 | def meditate(self, hours): 74 | self.power += hours * 10 75 | 76 | 77 | # 使用示例 78 | tracker = CultivationTracker() 79 | tracker.add_disciple("韩立") 80 | tracker.disciples["韩立"].power = 9800 81 | 82 | print("=== 战力排行榜 ===") 83 | for i, cult in enumerate(tracker.power_ranking(), 1): 84 | print(f"{i}. {cult.name}: {cult.power}") 85 | 86 | tracker.save_data() 87 | -------------------------------------------------------------------------------- /day14/01-游戏坐标设计.py: -------------------------------------------------------------------------------- 1 | class Vector2: 2 | """二维坐标类""" 3 | 4 | def __init__(self, x, y): 5 | self.x = x 6 | self.y = y 7 | 8 | def __add__(self, other): 9 | return Vector2(self.x + other.x, self.y + other.y) 10 | 11 | def __eq__(self, other): 12 | return self.x == other.x and self.y == other.y 13 | 14 | 15 | # 方向常量 16 | DIRECTIONS = { 17 | "up": Vector2(0, 1), 18 | "down": Vector2(0, -1), 19 | "left": Vector2(-1, 0), 20 | "right": Vector2(1, 0), 21 | } 22 | -------------------------------------------------------------------------------- /day14/02-游戏状态管理.py: -------------------------------------------------------------------------------- 1 | class GameState: 2 | def __init__(self, width=20, height=20): 3 | self.snake = [Vector2(5, 5)] # 蛇身坐标列表 4 | self.direction = DIRECTIONS["right"] 5 | self.food = self._generate_food() 6 | self.score = 0 7 | self.game_over = False 8 | 9 | def _generate_food(self): 10 | """在随机位置生成食物(不与蛇身重叠)""" 11 | while True: 12 | new_food = Vector2( 13 | random.randint(0, 19), 14 | random.randint(0, 19) 15 | ) 16 | if new_food not in self.snake: 17 | return new_food 18 | -------------------------------------------------------------------------------- /day14/arcade-3.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zesheng-Wang/100-days-python/dd849a1e907f8bf299dfe27ef09e8c7d98be7c5a/day14/arcade-3.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /day14/day14-贪吃蛇游戏01.py: -------------------------------------------------------------------------------- 1 | import arcade # 导入 arcade 库,用于图形渲染和游戏开发 2 | import random # 导入 random 库,用于生成随机数 3 | 4 | # 定义一个二维向量类,用于表示位置和方向 5 | class Vector2: 6 | def __init__(self, x, y): 7 | self.x = x # x 坐标 8 | self.y = y # y 坐标 9 | 10 | # 定义向量加法运算,使得可以直接相加两个 Vector2 对象 11 | def __add__(self, other): 12 | return Vector2(self.x + other.x, self.y + other.y) 13 | 14 | # 定义等于运算,使得可以比较两个 Vector2 是否相等 15 | def __eq__(self, other): 16 | return self.x == other.x and self.y == other.y 17 | 18 | # 方向字典,存储上下左右移动的单位向量 19 | DIRECTIONS = { 20 | "up": Vector2(0, 1), # 向上移动 21 | "down": Vector2(0, -1), # 向下移动 22 | "left": Vector2(-1, 0), # 向左移动 23 | "right": Vector2(1, 0), # 向右移动 24 | } 25 | 26 | # 游戏状态类,存储蛇的位置、方向、食物等信息 27 | class GameState: 28 | def __init__(self, width=20, height=20): 29 | self.snake = [Vector2(5, 5)] # 初始化蛇的位置,初始长度为 1 30 | self.direction = DIRECTIONS["right"] # 初始方向向右 31 | self.food = self._generate_food() # 生成食物 32 | self.score = 0 # 分数 33 | self.game_over = False # 游戏是否结束的标志 34 | 35 | # 生成食物,确保食物不会出现在蛇身上 36 | def _generate_food(self): 37 | while True: 38 | new_food = Vector2(random.randint(0, 19), random.randint(0, 19)) # 在 0-19 范围内随机生成食物位置 39 | if new_food not in self.snake: # 确保食物不会出现在蛇身上 40 | return new_food 41 | 42 | # 贪吃蛇游戏窗口类,继承自 arcade.Window 43 | class SnakeGame(arcade.Window): 44 | def __init__(self): 45 | super().__init__(width=400, height=400, title="贪吃蛇修仙版") # 设置窗口大小和标题 46 | self.cell_size = 20 # 每个格子的大小 47 | self.game = GameState() # 创建游戏状态对象 48 | arcade.set_background_color(arcade.color.BLACK) # 设置背景颜色为黑色 49 | 50 | # 控制蛇的移动速度(单位:秒) 51 | self.move_interval = 0.2 # 每 0.2 秒移动一次 52 | self.time_since_last_move = 0.0 # 记录距离上次移动的时间 53 | 54 | # 绘制游戏内容 55 | def on_draw(self): 56 | self.clear() # 清除屏幕 57 | 58 | # 绘制蛇的每个身体部分 59 | for segment in self.game.snake: 60 | arcade.draw_rect_filled( 61 | arcade.rect.XYWH( 62 | segment.x * self.cell_size + 1, # 计算绘制的 x 坐标 63 | segment.y * self.cell_size + 1, # 计算绘制的 y 坐标 64 | self.cell_size - 2, # 宽度 65 | self.cell_size - 2 # 高度 66 | ), 67 | arcade.color.GREEN # 设置蛇的颜色为绿色 68 | ) 69 | 70 | # 绘制食物 71 | arcade.draw_circle_filled( 72 | self.game.food.x * self.cell_size + self.cell_size // 2, # 计算食物的 x 坐标 73 | self.game.food.y * self.cell_size + self.cell_size // 2, # 计算食物的 y 坐标 74 | self.cell_size // 2 - 2, # 计算食物的半径 75 | arcade.color.RED # 设置食物的颜色为红色 76 | ) 77 | 78 | # 显示分数 79 | arcade.draw_text( 80 | f"修为: {self.game.score}", # 文字内容 81 | 10, self.height - 30, # 文字位置(左上角) 82 | arcade.color.WHITE, # 文字颜色 83 | 16 # 文字大小 84 | ) 85 | 86 | # 游戏逻辑更新,每帧调用一次 87 | def on_update(self, delta_time): 88 | if not self.game.game_over: # 只有在游戏未结束时才更新 89 | self.time_since_last_move += delta_time # 累积时间 90 | 91 | # 只有当累计时间超过移动间隔时才进行移动 92 | if self.time_since_last_move >= self.move_interval: 93 | self.time_since_last_move = 0.0 # 重置计时器 94 | self._move_snake() # 移动蛇 95 | self._check_collisions() # 检查碰撞 96 | 97 | # 移动蛇 98 | def _move_snake(self): 99 | new_head = self.game.snake[-1] + self.game.direction # 计算新的蛇头位置 100 | self.game.snake.append(new_head) # 将新的头部添加到蛇身 101 | 102 | if new_head == self.game.food: # 如果蛇吃到了食物 103 | self.game.score += 1 # 分数加 1 104 | self.game.food = self.game._generate_food() # 生成新的食物 105 | else: 106 | self.game.snake.pop(0) # 如果没有吃到食物,则移除蛇尾(保持长度) 107 | 108 | # 检查蛇是否撞墙或咬到自己 109 | def _check_collisions(self): 110 | head = self.game.snake[-1] # 获取蛇头位置 111 | if not (0 <= head.x < 20 and 0 <= head.y < 20): # 检查是否超出边界 112 | self.game.game_over = True # 结束游戏 113 | if head in self.game.snake[:-1]: # 检查是否咬到自己 114 | self.game.game_over = True # 结束游戏 115 | 116 | # 处理键盘输入 117 | def on_key_press(self, key, modifiers): 118 | # 上方向键,防止直接反向 119 | if key == arcade.key.UP and self.game.direction != DIRECTIONS["down"]: 120 | self.game.direction = DIRECTIONS["up"] 121 | # 下方向键,防止直接反向 122 | elif key == arcade.key.DOWN and self.game.direction != DIRECTIONS["up"]: 123 | self.game.direction = DIRECTIONS["down"] 124 | # 左方向键,防止直接反向 125 | elif key == arcade.key.LEFT and self.game.direction != DIRECTIONS["right"]: 126 | self.game.direction = DIRECTIONS["left"] 127 | # 右方向键,防止直接反向 128 | elif key == arcade.key.RIGHT and self.game.direction != DIRECTIONS["left"]: 129 | self.game.direction = DIRECTIONS["right"] 130 | 131 | # 运行游戏 132 | if __name__ == "__main__": 133 | game = SnakeGame() # 创建游戏窗口 134 | arcade.run() # 运行游戏 135 | -------------------------------------------------------------------------------- /day15/day15-贪吃蛇游戏02.py: -------------------------------------------------------------------------------- 1 | # 导入必要的库 2 | import arcade # 游戏开发库 3 | import random # 随机数生成 4 | from typing import List # 类型提示支持 5 | 6 | # ---------------------- 7 | # 基础类定义 8 | # ---------------------- 9 | class Vector2: 10 | """二维坐标类""" 11 | def __init__(self, x: int, y: int): 12 | self.x = x # x坐标 13 | self.y = y # y坐标 14 | 15 | def __add__(self, other): 16 | """向量加法""" 17 | return Vector2(self.x + other.x, self.y + other.y) 18 | 19 | def __eq__(self, other): 20 | """等值比较""" 21 | return self.x == other.x and self.y == other.y 22 | 23 | def __mod__(self, value): 24 | """用于实现循环边界(取模运算)""" 25 | return Vector2(self.x % value, self.y % value) 26 | 27 | # 方向常量字典(使用Vector2表示方向向量) 28 | DIRECTIONS = { 29 | "up": Vector2(0, 1), # 上 30 | "down": Vector2(0, -1), # 下 31 | "left": Vector2(-1, 0), # 左 32 | "right": Vector2(1, 0) # 右 33 | } 34 | 35 | # ---------------------- 36 | # 蛇类继承体系 37 | # ---------------------- 38 | class BaseSnake: 39 | """蛇基类(所有蛇类的共同行为)""" 40 | def __init__(self, start_pos: Vector2): 41 | self.body: List[Vector2] = [start_pos] # 身体坐标列表 42 | self.direction: Vector2 = DIRECTIONS["right"] # 初始方向向右 43 | self.grow_counter = 0 # 生长计数器(记录需要生长的节数) 44 | 45 | def move(self): 46 | """基础移动逻辑(所有蛇类共用)""" 47 | new_head = self.body[-1] + self.direction # 计算新头部位置 48 | self.body.append(new_head) # 将新头部添加到身体 49 | 50 | # 根据生长计数器判断是否缩短尾部 51 | if self.grow_counter > 0: 52 | self.grow_counter -= 1 53 | else: 54 | self.body.pop(0) # 移除尾部保持长度 55 | 56 | def check_collision(self, grid_size: int) -> bool: 57 | """碰撞检测(子类可重写)返回是否碰撞自身""" 58 | head = self.body[-1] # 获取头部位置 59 | return head in self.body[:-1] # 检查头部是否在身体其他部分 60 | 61 | def grow(self, amount: int = 1): 62 | """生长机制(延长蛇身)""" 63 | self.grow_counter += amount # 增加生长计数器 64 | 65 | class TeleportSnake(BaseSnake): 66 | """瞬移蛇(穿越边界实现循环地图)""" 67 | def move(self): 68 | """重写移动方法实现边界穿越""" 69 | super().move() # 先执行基础移动 70 | head = self.body[-1] # 获取移动后的头部 71 | self.body[-1] = head % 20 # 对坐标取模实现循环边界(假设20x20网格) 72 | 73 | class SplitSnake(BaseSnake): 74 | """分身蛇(可分裂身体)""" 75 | def __init__(self, start_pos: Vector2): 76 | super().__init__(start_pos) 77 | self.children: List[List[Vector2]] = [] # 存储所有分身身体的列表 78 | 79 | def split(self): 80 | """分裂身体创建分身""" 81 | if len(self.body) >= 4: # 至少4节才能分裂 82 | split_point = len(self.body) // 2 # 计算分裂点(取中间) 83 | new_body = self.body[split_point:] # 后半部分作为分身 84 | self.body = self.body[:split_point] # 保留前半部分作为本体 85 | self.children.append(new_body) # 将分身添加到列表 86 | 87 | def update_children(self): 88 | """更新所有分身的位置(模拟移动)""" 89 | for child in self.children: 90 | if len(child) > 1: 91 | child.pop(0) # 移除分身尾部实现移动效果 92 | 93 | # ---------------------- 94 | # 游戏主类 95 | # ---------------------- 96 | class AdvancedSnakeGame(arcade.Window): 97 | """游戏主窗口类(继承arcade.Window)""" 98 | def __init__(self): 99 | # 初始化窗口(800x600像素,标题为"灵蛇进化录") 100 | super().__init__(800, 600, "灵蛇进化录") 101 | self.grid_size = 20 # 网格尺寸(20x20) 102 | self.cell_size = 30 # 每个网格的像素大小 103 | self.snake: BaseSnake = TeleportSnake(Vector2(10, 10)) # 创建瞬移蛇实例(初始位置中心) 104 | self.food = self.generate_food() # 生成食物 105 | self.score = 0 # 玩家得分 106 | self.game_over = False # 游戏结束标志 107 | 108 | # 移动时间控制相关 109 | self.move_interval = 0.15 # 移动间隔(秒) 110 | self.time_since_move = 0.0 # 累计时间 111 | 112 | arcade.set_background_color(arcade.color.BLACK) # 设置背景颜色 113 | 114 | def generate_food(self) -> Vector2: 115 | """生成不在蛇身上的随机食物位置""" 116 | while True: 117 | # 随机生成坐标(0到19之间) 118 | pos = Vector2(random.randint(0,19), random.randint(0,19)) 119 | if pos not in self.snake.body: # 确保食物不在蛇身上 120 | return pos 121 | 122 | def on_draw(self): 123 | """绘制游戏画面(每帧自动调用)""" 124 | self.clear() # 清空画面 125 | 126 | # 绘制食物(红色圆形) 127 | arcade.draw_circle_filled( 128 | self.food.x * self.cell_size + self.cell_size//2, # 计算x屏幕坐标 129 | self.food.y * self.cell_size + self.cell_size//2, # 计算y屏幕坐标 130 | self.cell_size//2 - 2, # 半径(留2像素边距) 131 | arcade.color.RED # 颜色 132 | ) 133 | 134 | # 绘制主蛇身体(交替颜色实现流光效果) 135 | for i, seg in enumerate(self.snake.body): 136 | # 交替使用两种绿色 137 | color = arcade.color.GREEN if i%2==0 else arcade.color.LIME_GREEN 138 | # 绘制矩形代表蛇身节 139 | arcade.draw_rect_filled( 140 | # 创建矩形参数(XYWH格式) 141 | arcade.rect.XYWH( 142 | seg.x * self.cell_size + 1, # 计算x位置(留1像素边距) 143 | seg.y * self.cell_size + 1, # 计算y位置 144 | self.cell_size - 2, # 宽度 145 | self.cell_size - 2 # 高度 146 | ), 147 | color # 填充颜色 148 | ) 149 | 150 | # 如果使用分身蛇,绘制分身身体 151 | if isinstance(self.snake, SplitSnake): 152 | for child in self.snake.children: # 遍历所有分身 153 | # 只绘制最后3节(-3表示倒数第三个元素开始) 154 | for seg in child[-3:]: 155 | # 类似主蛇绘制,可以调整颜色参数 156 | arcade.draw_rect_filled(...) # 实际代码需要完整参数 157 | 158 | # 在左上角绘制分数(白色文字) 159 | arcade.draw_text( 160 | f"修为: {self.score}", # 显示分数 161 | 10, self.height-40, # 坐标(10, 560) 162 | arcade.color.WHITE, 20 # 颜色和字号 163 | ) 164 | 165 | def on_update(self, delta_time: float): 166 | """游戏状态更新(每帧调用)""" 167 | if self.game_over: 168 | return # 游戏结束停止更新 169 | 170 | self.time_since_move += delta_time # 累计时间 171 | # 达到移动间隔时执行移动 172 | if self.time_since_move >= self.move_interval: 173 | self.time_since_move = 0 # 重置计时器 174 | self.snake.move() # 移动蛇 175 | 176 | # 检测是否吃到食物 177 | if self.snake.body[-1] == self.food: 178 | self.score += 1 # 增加分数 179 | self.snake.grow(3) # 生长3节 180 | self.food = self.generate_food() # 生成新食物 181 | 182 | # 检测碰撞(调用蛇类的碰撞检测方法) 183 | if self.snake.check_collision(self.grid_size): 184 | self.game_over = True # 触发游戏结束 185 | 186 | def on_key_press(self, key: int, modifiers: int): 187 | """键盘按下事件处理""" 188 | if self.game_over: 189 | return # 游戏结束不响应 190 | 191 | # 方向键控制(防止180度转向) 192 | if key == arcade.key.UP and self.snake.direction != DIRECTIONS["down"]: 193 | self.snake.direction = DIRECTIONS["up"] 194 | elif key == arcade.key.DOWN and self.snake.direction != DIRECTIONS["up"]: 195 | self.snake.direction = DIRECTIONS["down"] 196 | elif key == arcade.key.LEFT and self.snake.direction != DIRECTIONS["right"]: 197 | self.snake.direction = DIRECTIONS["left"] 198 | elif key == arcade.key.RIGHT and self.snake.direction != DIRECTIONS["left"]: 199 | self.snake.direction = DIRECTIONS["right"] 200 | 201 | # 空格键触发分身分裂(仅SplitSnake有效) 202 | if key == arcade.key.SPACE and isinstance(self.snake, SplitSnake): 203 | self.snake.split() # 调用分裂方法 204 | 205 | # 程序入口 206 | if __name__ == "__main__": 207 | game = AdvancedSnakeGame() # 创建游戏实例 208 | arcade.run() # 运行游戏循环 -------------------------------------------------------------------------------- /day16/day16-乒乓游戏.py: -------------------------------------------------------------------------------- 1 | import arcade # 导入 arcade 游戏框架 2 | import random # 导入 random 模块,用于生成随机数 3 | 4 | # ---------------------- 5 | # 常量定义 6 | # ---------------------- 7 | SCREEN_WIDTH = 800 # 游戏窗口宽度 8 | SCREEN_HEIGHT = 600 # 游戏窗口高度 9 | PADDLE_COLORS = [arcade.color.BLUE, arcade.color.RED] # 左右球拍颜色 10 | 11 | 12 | class Vector2: 13 | """二维向量类(用于表示位置和速度)""" 14 | 15 | def __init__(self, x, y): 16 | self.x = x # X 坐标或速度 17 | self.y = y # Y 坐标或速度 18 | 19 | def __add__(self, other): 20 | # 向量加法 21 | return Vector2(self.x + other.x, self.y + other.y) 22 | 23 | def __mul__(self, scalar): 24 | # 向量乘以标量 25 | return Vector2(self.x * scalar, self.y * scalar) 26 | 27 | 28 | class Paddle: 29 | """球拍类""" 30 | 31 | def __init__(self, side: str): 32 | self.side = side # 表示是左边还是右边球拍('left' 或 'right') 33 | self.width = 15 # 球拍宽度 34 | self.height = 80 # 球拍高度 35 | self.speed = 8 # 球拍移动速度 36 | self.score = 0 # 当前得分 37 | # 初始化球拍位置(左边在 x=50,右边在 x=750) 38 | self.pos = Vector2(50 if side == "left" else 750, 300) 39 | 40 | 41 | class Ball: 42 | """球类""" 43 | 44 | def __init__(self): 45 | self.radius = 10 # 球的半径 46 | self.reset() # 初始化位置和速度 47 | self.trail = [] # 用于记录球的移动轨迹,产生尾迹效果 48 | 49 | def reset(self): 50 | """重置球的位置和速度""" 51 | self.pos = Vector2(400, 300) # 回到中心 52 | self.vel = Vector2(5, random.choice([-4, 4])) # 设置初始速度 53 | 54 | 55 | # ---------------------- 56 | # 游戏主类 57 | # ---------------------- 58 | class PongGame(arcade.Window): 59 | def __init__(self): 60 | # 初始化窗口 61 | super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "乒乓修仙传") 62 | self.left_paddle = Paddle("left") # 创建左球拍 63 | self.right_paddle = Paddle("right") # 创建右球拍 64 | self.ball = Ball() # 创建球对象 65 | self.game_active = False # 游戏是否处于进行中状态 66 | 67 | self.keys_pressed = set() # 保存当前按下的按键 68 | arcade.set_background_color(arcade.color.DARK_GREEN) # 设置背景色 69 | 70 | def on_draw(self): 71 | self.clear() # 清屏 72 | 73 | # 画中间的分割线 74 | arcade.draw_line( 75 | SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT, arcade.color.WHITE, 2 76 | ) 77 | 78 | # 绘制两个球拍 79 | for paddle, color in zip([self.left_paddle, self.right_paddle], PADDLE_COLORS): 80 | arcade.draw_rect_filled( 81 | arcade.rect.XYWH( 82 | paddle.pos.x, paddle.pos.y, paddle.width, paddle.height 83 | ), 84 | color, 85 | ) 86 | 87 | # 绘制球的尾迹 88 | for i, pos in enumerate(self.ball.trail): 89 | alpha = 255 * (1 - i / len(self.ball.trail)) # 越靠前越透明 90 | arcade.draw_circle_filled( 91 | pos.x, pos.y, self.ball.radius, (255, 255, 255, int(alpha)) 92 | ) 93 | 94 | # 绘制球体 95 | arcade.draw_circle_filled( 96 | self.ball.pos.x, self.ball.pos.y, self.ball.radius, arcade.color.WHITE 97 | ) 98 | 99 | # 显示比分 100 | self._draw_score() 101 | 102 | def _draw_score(self): 103 | # 显示左边比分 104 | arcade.draw_text( 105 | f"{self.left_paddle.score}", 106 | SCREEN_WIDTH / 2 - 60, 107 | 550, 108 | arcade.color.WHITE, 109 | 40, 110 | align="center", 111 | anchor_x="center", 112 | ) 113 | # 显示右边比分 114 | arcade.draw_text( 115 | f"{self.right_paddle.score}", 116 | SCREEN_WIDTH / 2 + 60, 117 | 550, 118 | arcade.color.WHITE, 119 | 40, 120 | align="center", 121 | anchor_x="center", 122 | ) 123 | 124 | def on_update(self, delta_time): 125 | if not self.game_active: 126 | return # 如果游戏未开始,则不更新 127 | 128 | self._move_paddles() # 处理球拍移动 129 | 130 | self.ball.pos += self.ball.vel # 更新球的位置 131 | 132 | # 更新尾迹(只保留最近 5 个位置) 133 | self.ball.trail.append(Vector2(self.ball.pos.x, self.ball.pos.y)) 134 | if len(self.ball.trail) > 5: 135 | self.ball.trail.pop(0) 136 | 137 | self._check_wall_collision() # 检查上下边界碰撞 138 | self._check_paddle_collision() # 检查与球拍碰撞 139 | self._check_score() # 检查是否得分 140 | 141 | def _move_paddles(self): 142 | """根据按键移动球拍""" 143 | 144 | # 左球拍控制(W 和 S) 145 | if arcade.key.W in self.keys_pressed: 146 | self.left_paddle.pos.y = min( 147 | SCREEN_HEIGHT - self.left_paddle.height / 2, 148 | self.left_paddle.pos.y + self.left_paddle.speed, 149 | ) 150 | if arcade.key.S in self.keys_pressed: 151 | self.left_paddle.pos.y = max( 152 | self.left_paddle.height / 2, 153 | self.left_paddle.pos.y - self.left_paddle.speed, 154 | ) 155 | 156 | # 右球拍控制(方向键 ↑ 和 ↓) 157 | if arcade.key.UP in self.keys_pressed: 158 | self.right_paddle.pos.y = min( 159 | SCREEN_HEIGHT - self.right_paddle.height / 2, 160 | self.right_paddle.pos.y + self.right_paddle.speed, 161 | ) 162 | if arcade.key.DOWN in self.keys_pressed: 163 | self.right_paddle.pos.y = max( 164 | self.right_paddle.height / 2, 165 | self.right_paddle.pos.y - self.right_paddle.speed, 166 | ) 167 | 168 | def _check_wall_collision(self): 169 | """检测上下边界碰撞,撞到后反弹""" 170 | if ( 171 | self.ball.pos.y < self.ball.radius 172 | or self.ball.pos.y > SCREEN_HEIGHT - self.ball.radius 173 | ): 174 | self.ball.vel.y *= -1 # Y方向反弹 175 | 176 | def _check_paddle_collision(self): 177 | """检测球与球拍是否碰撞""" 178 | for paddle in [self.left_paddle, self.right_paddle]: 179 | if abs(self.ball.pos.x - paddle.pos.x) < ( 180 | paddle.width / 2 + self.ball.radius 181 | ) and abs(self.ball.pos.y - paddle.pos.y) < ( 182 | paddle.height / 2 + self.ball.radius 183 | ): 184 | # 计算偏移量(用来控制弹射方向) 185 | offset = (self.ball.pos.y - paddle.pos.y) / (paddle.height / 2) 186 | self.ball.vel.x *= -1.1 # 反弹并加速 187 | self.ball.vel.y = offset * 8 # 根据偏移决定Y速度 188 | 189 | # 限制最大速度 190 | self.ball.vel = Vector2( 191 | max(-10, min(10, self.ball.vel.x)), 192 | max(-8, min(8, self.ball.vel.y)), 193 | ) 194 | 195 | def _check_score(self): 196 | """检查球是否飞出边界,判断得分""" 197 | if self.ball.pos.x < 0: 198 | self.right_paddle.score += 1 # 右边得分 199 | self._reset_round() # 重置回合 200 | elif self.ball.pos.x > SCREEN_WIDTH: 201 | self.left_paddle.score += 1 # 左边得分 202 | self._reset_round() 203 | 204 | def _reset_round(self): 205 | """重置球的位置,暂停游戏""" 206 | self.ball.reset() 207 | self.game_active = False 208 | 209 | def on_key_press(self, key, modifiers): 210 | self.keys_pressed.add(key) # 添加按下的键到集合 211 | if key == arcade.key.SPACE and not self.game_active: 212 | self.game_active = True # 按空格开始游戏 213 | 214 | def on_key_release(self, key, modifiers): 215 | if key in self.keys_pressed: 216 | self.keys_pressed.remove(key) # 松开按键时从集合中移除 217 | 218 | 219 | # 程序入口 220 | if __name__ == "__main__": 221 | game = PongGame() # 创建游戏窗口 222 | arcade.run() # 启动游戏主循环 223 | -------------------------------------------------------------------------------- /day17/day17-海龟过马路游戏.py: -------------------------------------------------------------------------------- 1 | import turtle # 导入turtle模块,用于绘制游戏界面 2 | import random # 导入random模块,用于生成随机数 3 | import time # 导入time模块,用于控制游戏帧率 4 | 5 | # ==================== 6 | # 游戏初始化设置 7 | # ==================== 8 | win = turtle.Screen() # 创建游戏窗口 9 | win.setup(width=600, height=600) # 设置窗口大小 10 | win.bgcolor("lightgray") # 设置窗口背景颜色为浅灰色 11 | win.title("龟仙人の马路大冒险") # 设置窗口标题 12 | win.tracer(0) # 关闭自动刷新,提高性能,需要手动调用win.update()刷新屏幕 13 | 14 | # ==================== 15 | # 玩家角色 16 | # ==================== 17 | player = turtle.Turtle() # 创建玩家角色 18 | player.shape("turtle") # 设置形状为乌龟 19 | player.color("darkgreen") # 设置颜色为深绿色 20 | player.penup() # 关闭画笔,防止乌龟移动时画出轨迹 21 | player.setheading(90) # 设置乌龟朝向上方(90度) 22 | player.goto(0, -250) # 将玩家初始化位置放在底部中央 23 | 24 | # ==================== 25 | # 计分系统 26 | # ==================== 27 | score = 0 # 初始化得分为0 28 | score_display = turtle.Turtle() # 创建用于显示分数的乌龟对象 29 | score_display.hideturtle() # 隐藏乌龟形状 30 | score_display.penup() # 关闭画笔 31 | score_display.goto(-280, 260) # 设置分数显示位置(屏幕左上角) 32 | score_display.write(f"得分: {score}", font=("Arial", 14, "normal")) # 绘制分数文本 33 | 34 | # ==================== 35 | # 障碍物系统 36 | # ==================== 37 | car_list = [] # 用于存储所有障碍物的小车 38 | colors = ["red", "blue", "orange", "purple", "brown"] # 定义障碍物颜色列表 39 | 40 | 41 | def create_car(): 42 | """生成新的障碍物(小车)""" 43 | car = turtle.Turtle() # 创建新的乌龟对象作为小车 44 | car.shape("square") # 设置形状为方块 45 | car.shapesize(1, 2) # 调整大小,使其变成长方形(1格高,2格宽) 46 | car.color(random.choice(colors)) # 随机设置小车颜色 47 | car.penup() # 关闭画笔 48 | car.speed(0) # 设置小车绘制速度为最快 49 | 50 | # 随机生成小车起始Y坐标,范围在-200到200之间 51 | start_y = random.randint(-200, 200) 52 | # 计算小车的速度,基础速度2-5,并随着得分增加提高难度 53 | speed = random.randint(2, 5) + score // 2 54 | car.goto(300, start_y) # 将小车放置在右侧屏幕外(x=300) 55 | car.speed = -speed # 设置小车的移动速度,向左移动 56 | 57 | car_list.append(car) # 将新生成的小车加入到列表中 58 | 59 | 60 | # 初始生成5个障碍物 61 | for _ in range(5): 62 | create_car() 63 | 64 | 65 | # ==================== 66 | # 玩家控制 67 | # ==================== 68 | def move_up(): 69 | """玩家向上移动""" 70 | y = player.ycor() # 获取当前玩家的Y坐标 71 | if y < 250: # 限制玩家不能超出屏幕顶部 72 | player.sety(y + 20) # 向上移动20个像素 73 | check_success() # 检测是否成功到达终点 74 | 75 | 76 | win.listen() # 监听键盘输入 77 | win.onkeypress(move_up, "Up") # 当按下“↑”键时,调用move_up函数 78 | 79 | 80 | # ==================== 81 | # 游戏逻辑 82 | # ==================== 83 | def check_collision(): 84 | """检测玩家是否撞到障碍物""" 85 | for car in car_list: 86 | if player.distance(car) < 25: # 如果玩家与小车的距离小于25,则判定为碰撞 87 | return True 88 | return False 89 | 90 | 91 | def check_success(): 92 | """检测玩家是否成功穿越马路""" 93 | global score 94 | if player.ycor() >= 250: # 如果玩家到达屏幕顶部 95 | score += 10 # 增加得分 96 | score_display.clear() # 清空旧的分数 97 | score_display.write(f"得分: {score}", font=("Arial", 14, "normal")) # 更新分数 98 | player.goto(0, -250) # 将玩家重置到起点 99 | 100 | 101 | def game_over(): 102 | """游戏结束处理""" 103 | global game_running 104 | game_running = False # 设置游戏状态为停止 105 | over = turtle.Turtle() # 创建游戏结束提示文本的乌龟对象 106 | over.hideturtle() # 隐藏乌龟 107 | over.write("GAME OVER!", align="center", font=("Arial", 24, "bold")) # 显示"GAME OVER!" 108 | 109 | 110 | # ==================== 111 | # 主游戏循环 112 | # ==================== 113 | game_running = True # 游戏运行状态 114 | last_car_time = time.time() # 记录上一次生成障碍物的时间 115 | 116 | while game_running: 117 | win.update() # 刷新屏幕 118 | 119 | # 移动所有障碍物 120 | for car in car_list: 121 | car.setx(car.xcor() + car.speed) # 让小车向左移动 122 | 123 | # 当小车超出左边界时,移除并生成新小车 124 | if car.xcor() < -320: 125 | car.hideturtle() # 隐藏小车 126 | car_list.remove(car) # 从列表中移除 127 | create_car() # 生成新的小车 128 | 129 | # 每隔2秒生成一个新的小车 130 | if time.time() - last_car_time > 2: 131 | create_car() 132 | last_car_time = time.time() # 更新最后一次生成时间 133 | 134 | # 检测玩家是否撞到障碍物 135 | if check_collision(): 136 | game_over() 137 | 138 | time.sleep(0.016) # 约60帧每秒(1秒 / 60 ≈ 0.016) 139 | 140 | win.mainloop() # 进入turtle事件循环 141 | -------------------------------------------------------------------------------- /day18/01-os模块.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # 获取当前工作目录(相当于Linux的pwd) 4 | current_path = os.getcwd() 5 | print(f"当前修炼洞府:{current_path}") 6 | 7 | # 列出目录内容(相当于ls命令) 8 | file_list = os.listdir(".") 9 | print("目录藏宝图:", file_list) 10 | 11 | # 递归遍历目录(深度优先搜索) 12 | for root, dirs, files in os.walk("../"): 13 | print(f"\n发现秘境:{root}") 14 | print("内有洞天:", dirs) 15 | print("藏经阁秘籍:", files) 16 | 17 | import os 18 | 19 | # 示例路径 20 | example_path = "/home/user/docs/file.txt" 21 | 22 | # 1. 拼接路径 - 自动处理不同操作系统的路径分隔符 23 | new_path = os.path.join("dir1", "dir2", "file.txt") 24 | print(new_path) # 输出: dir1/dir2/file.txt (Linux/macOS) 或 dir1\dir2\file.txt (Windows) 25 | 26 | # 2. 获取绝对路径 27 | abs_path = os.path.abspath("relative/path") 28 | print(abs_path) # 输出当前工作目录下relative/path的绝对路径 29 | 30 | # 3. 获取路径的目录部分(去掉文件名) 31 | dir_name = os.path.dirname(example_path) 32 | print(dir_name) # 输出: /home/user/docs 33 | 34 | # 4. 获取路径的文件名部分(最后一个组成部分) 35 | file_name = os.path.basename(example_path) 36 | print(file_name) # 输出: file.txt 37 | 38 | # 5. 分割路径为目录和文件名两部分 39 | dir_part, file_part = os.path.split(example_path) 40 | print(dir_part, file_part) # 输出: ('/home/user/docs', 'file.txt') 41 | 42 | # 6. 分割文件扩展名 43 | file_name, ext = os.path.splitext(file_part) 44 | print(file_name, ext) # 输出: ('file', '.txt') 45 | 46 | # 7. 检查路径是否存在 47 | exists = os.path.exists(example_path) 48 | print(f"路径存在: {exists}") 49 | 50 | # 8. 检查是否是目录 51 | is_dir = os.path.isdir("/home/user/docs") 52 | print(f"是目录: {is_dir}") 53 | 54 | # 9. 检查是否是文件 55 | is_file = os.path.isfile(example_path) 56 | print(f"是文件: {is_file}") 57 | 58 | # 10. 创建目录(递归创建) 59 | os.makedirs("new/directory/path", exist_ok=True) # exist_ok=True表示目录存在时不报错 60 | 61 | # 11. 获取当前工作目录 62 | cwd = os.getcwd() 63 | print(f"当前工作目录: {cwd}") 64 | 65 | # 12. 路径标准化(处理多余的斜杠和..) 66 | norm_path = os.path.normpath("/home//user/../docs/file.txt") 67 | print(norm_path) # 输出: /home/docs/file.txt 68 | -------------------------------------------------------------------------------- /day18/02-pathlib.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | # 创建Path对象(自动适配操作系统) 4 | 5 | current_dir = Path.cwd() 6 | home_dir = Path.home() # 用户主目录 7 | # 路径拼接(使用/运算符) 8 | log_file = current_dir / "logs" / "app.log" 9 | # 文件属性检测 10 | print(f"是否存在:{log_file.exists()}") 11 | print(f"是文件吗:{log_file.is_file()}") 12 | print(f"文件大小:{log_file.stat().st_size}字节") 13 | -------------------------------------------------------------------------------- /day18/03-shutil.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | # 1. 复制文件 (保留权限,不保留元数据) 4 | shutil.copy("src.txt", "dst.txt") # 类似 cp src.txt dst.txt 5 | 6 | # 2. 复制文件 (保留元数据,如修改时间) 7 | shutil.copy2("src.txt", "dst.txt") # 比 copy 更完整 8 | 9 | # 3. 递归复制整个目录树 10 | shutil.copytree("src_dir", "dst_dir") # 类似 cp -r src_dir dst_dir 11 | 12 | # 4. 递归删除目录树(慎用!不可逆操作) 13 | shutil.rmtree("dir_to_delete") # 类似 rm -rf dir_to_delete 14 | 15 | # 5. 移动文件/目录(可跨磁盘) 16 | shutil.move("old_path", "new_path") # 类似 mv old_path new_path 17 | 18 | 19 | # 1. 大文件流式复制(避免内存溢出) 20 | with open("src.iso", "rb") as src, open("dst.iso", "wb") as dst: 21 | shutil.copyfileobj(src, dst, length=16*1024) # 16KB 缓冲区 22 | 23 | # 2. 保留文件权限(类似 chmod) 24 | shutil.copymode("src.txt", "dst.txt") # 仅复制权限 25 | shutil.copystat("src.txt", "dst.txt") # 复制权限和元数据 26 | 27 | # 3. 处理同名目录(自动覆盖) 28 | shutil.rmtree("dst_dir", ignore_errors=True) # 先删除旧目录 29 | shutil.copytree("src_dir", "dst_dir") # 再复制新目录 -------------------------------------------------------------------------------- /day18/04-project.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def file_mover(target_dir: Path): 5 | """按扩展名自动分类文件到对应目录""" 6 | classification_rules = { 7 | "武学秘籍": ["pdf", "docx", "txt"], 8 | "灵丹妙药": ["jpg", "png", "gif"], 9 | "法宝图纸": ["py", "java", "cpp"], 10 | "天材地宝": ["zip", "rar", "7z"], 11 | } 12 | 13 | for file in target_dir.glob("*"): 14 | if file.is_file(): 15 | # 提取扩展名并转换为小写 16 | extension = file.suffix[1:].lower() 17 | 18 | # 查找匹配的分类 19 | target_category = "杂物" # 默认分类 20 | for category, suffix_list in classification_rules.items(): 21 | if extension in suffix_list: 22 | target_category = category 23 | break 24 | 25 | # 创建分类目录并移动文件 26 | target_directory = target_dir / target_category 27 | target_directory.mkdir(exist_ok=True) 28 | file.rename(target_directory / file.name) 29 | print(f"【{file.name}】已移送至【{target_category}】") 30 | 31 | 32 | if __name__ == "__main__": 33 | file_mover(Path(r"C:\乱七八糟")) 34 | -------------------------------------------------------------------------------- /day19/01-异常处理.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | print(10 / 0) # 这里会抛出 ZeroDivisionError 4 | 5 | try: 6 | age = int(input("请输入年龄:")) 7 | print(f"您今年{age}岁") 8 | except ValueError: 9 | print("输入的不是数字!") 10 | 11 | 12 | try: 13 | file = open("data.txt", "r") 14 | content = file.read() 15 | number = int(content) 16 | except FileNotFoundError: 17 | print("文件不存在") 18 | except ValueError: 19 | print("文件内容不是数字") 20 | except Exception as e: 21 | print(f"发生未知错误:{e}") 22 | finally: 23 | file.close() # 无论是否出错都会执行 24 | 25 | 26 | class InsufficientFundsError(Exception): 27 | """余额不足异常""" 28 | 29 | def __init__(self, balance, amount): 30 | self.balance = balance 31 | self.amount = amount 32 | super().__init__(f"余额不足!当前余额:{balance},需要:{amount}") 33 | 34 | 35 | def withdraw(balance, amount): 36 | if amount > balance: 37 | raise InsufficientFundsError(balance, amount) 38 | return balance - amount 39 | 40 | 41 | # 使用示例 42 | try: 43 | withdraw(100, 200) 44 | except InsufficientFundsError as e: 45 | print(e) 46 | # 传统方式需要手动关闭 47 | file = open("data.txt") 48 | try: 49 | data = file.read() 50 | finally: 51 | file.close() 52 | 53 | # 现代方式(推荐) 54 | with open("data.txt") as file: 55 | data = file.read() 56 | 57 | 58 | # 离开with块后自动关闭文件 59 | class Timer: 60 | """计时上下文管理器""" 61 | 62 | def __enter__(self): 63 | self.start = time.time() 64 | return self 65 | 66 | def __exit__(self, exc_type, exc_val, traceback): 67 | self.end = time.time() 68 | print(f"耗时:{self.end - self.start:.2f}秒") 69 | 70 | 71 | # 使用示例 72 | with Timer(): 73 | time.sleep(1.5) 74 | 75 | 76 | import logging 77 | 78 | logging.basicConfig( 79 | filename="error.log", level=logging.ERROR, format="%(asctime)s - %(message)s" 80 | ) 81 | 82 | try: 83 | 1 / 0 84 | except Exception as e: 85 | logging.error("发生数学错误", exc_info=True) 86 | -------------------------------------------------------------------------------- /day19/02-简单重试实现.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | 4 | 5 | def retry_operation(max_retries=3, delay=1): 6 | def decorator(func): 7 | def wrapper(*args, **kwargs): 8 | retries = 0 9 | while retries < max_retries: 10 | try: 11 | return func(*args, **kwargs) 12 | except Exception as e: 13 | print(f"重试 {retries+1}/{max_retries},原因:{e}") 14 | retries += 1 15 | time.sleep(delay) 16 | raise Exception("超过最大重试次数") 17 | 18 | return wrapper 19 | 20 | return decorator 21 | 22 | 23 | @retry_operation(max_retries=5) 24 | def connect_server(): 25 | # 模拟不稳定的连接 26 | if random.random() < 0.7: 27 | raise ConnectionError("连接失败") 28 | return "连接成功" 29 | -------------------------------------------------------------------------------- /day20/01-普通程序执行时间.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | # 单线程示例 4 | def task(name): 5 | print(f"{name} 开始工作") 6 | time.sleep(2) 7 | print(f"{name} 工作完成") 8 | # 顺序执行(耗时约6秒) 9 | 10 | start = time.time() 11 | 12 | task("工人1") 13 | task("工人2") 14 | task("工人3") 15 | 16 | end = time.time() 17 | print(f"总耗时{end - start}秒") -------------------------------------------------------------------------------- /day20/02-多线程执行时间.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | 5 | def task(name): 6 | print(f"{name} 开始工作") 7 | time.sleep(2) 8 | print(f"{name} 工作完成") 9 | 10 | start = time.time() 11 | threads = [] 12 | for i in range(1, 4): 13 | t = threading.Thread(target=task, args=(f"工人{i}",)) 14 | threads.append(t) 15 | t.start() 16 | for t in threads: 17 | t.join() # 等待所有线程完成 18 | 19 | end = time.time() 20 | print(f"总耗时{end - start}秒") 21 | -------------------------------------------------------------------------------- /day24-Tkinter进阶与番茄钟实战/01-可变类型自动刷新.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import ttk 3 | 4 | 5 | def start_counter(): 6 | """动态计数器演示""" 7 | counter = tk.IntVar(value=0) # 可变整型变量 8 | 9 | def update(): 10 | counter.set(counter.get() + 1) 11 | root.after(1000, update) # 定时回调 12 | 13 | label = ttk.Label(root, textvariable=counter) # 自动绑定 14 | label.pack(pady=20) 15 | update() # 启动循环 16 | 17 | 18 | root = tk.Tk() 19 | ttk.Button(root, text="启动动态计数器", command=start_counter).pack(pady=50) 20 | root.mainloop() 21 | -------------------------------------------------------------------------------- /day24-Tkinter进阶与番茄钟实战/02-番茄时钟.py: -------------------------------------------------------------------------------- 1 | # 导入必要的库 2 | import tkinter as tk # 导入GUI库 3 | from tkinter import ttk # 导入tkinter的增强组件 4 | import pystray # 系统托盘库 5 | from PIL import Image, ImageDraw # 图像处理库(用于创建托盘图标) 6 | import threading # 多线程支持 7 | 8 | class TomatoTimer: 9 | def __init__(self, master): 10 | """番茄钟计时器核心类""" 11 | self.master = master # 主窗口对象 12 | self.time_left = tk.IntVar(value=25 * 60) # 剩余时间变量(默认25分钟) 13 | self.is_running = False # 计时器运行状态标志 14 | 15 | # 时间显示标签 16 | self.time_label = ttk.Label( 17 | master, 18 | textvariable=self.time_left, # 绑定到时间变量 19 | font=("Helvetica", 48) # 设置大字体 20 | ) 21 | self.time_label.pack(pady=20) # 放置标签并添加垂直间距 22 | 23 | # 按钮框架容器 24 | self.btn_frame = ttk.Frame(master) 25 | self.btn_frame.pack() 26 | 27 | # 开始/暂停按钮 28 | ttk.Button( 29 | self.btn_frame, 30 | text="开始", 31 | command=self.start # 绑定启动方法 32 | ).pack(side="left") # 左对齐 33 | 34 | # 重置按钮 35 | ttk.Button( 36 | self.btn_frame, 37 | text="重置", 38 | command=self.reset # 绑定重置方法 39 | ).pack(side="left") # 左对齐 40 | 41 | def start(self): 42 | """启动/暂停计时器""" 43 | self.is_running = not self.is_running # 切换运行状态 44 | if self.is_running: 45 | self.countdown() # 如果正在运行则开始倒计时 46 | 47 | def reset(self): 48 | """重置计时器到初始状态""" 49 | self.is_running = False # 停止计时 50 | self.time_left.set(25 * 60) # 重置为25分钟 51 | 52 | def countdown(self): 53 | """倒计时核心逻辑""" 54 | if self.is_running and self.time_left.get() > 0: 55 | self.time_left.set(self.time_left.get() - 1) # 秒数减1 56 | self.master.after(1000, self.countdown) # 1秒后递归调用 57 | elif self.time_left.get() == 0: 58 | self.show_notification() # 时间为0时显示通知 59 | 60 | def show_notification(self): 61 | """显示时间到通知""" 62 | self.master.iconify() # 最小化主窗口 63 | self.master.bell() # 播放系统提示音 64 | 65 | # 创建弹出窗口 66 | popup = tk.Toplevel() 67 | popup.title("时间到!") 68 | ttk.Label(popup, text="🍅 该休息啦!").pack(pady=20) # 添加标签 69 | ttk.Button(popup, text="好的", command=popup.destroy).pack() # 关闭按钮 70 | 71 | 72 | class SystemTray: 73 | """系统托盘图标管理类""" 74 | def __init__(self, master): 75 | self.master = master # 主窗口引用 76 | self.icon = self.create_icon() # 创建托盘图标 77 | self.menu = pystray.Menu( # 创建托盘菜单 78 | pystray.MenuItem("显示主界面", self.show_window), # 菜单项1 79 | pystray.MenuItem("退出程序", self.quit) # 菜单项2 80 | ) 81 | # 创建托盘图标实例 82 | self.tray = pystray.Icon( 83 | "tomato_timer", # 图标名称 84 | self.icon, # 图标图像 85 | "番茄钟", # 悬停提示文本 86 | self.menu # 关联菜单 87 | ) 88 | 89 | # 在新线程中运行托盘图标(避免阻塞主线程) 90 | self.thread = threading.Thread(target=self.tray.run, daemon=True) 91 | self.thread.start() 92 | 93 | def create_icon(self): 94 | """创建简单的红色方块托盘图标""" 95 | image = Image.new('RGB', (64, 64), (255, 255, 255)) # 创建白色背景图像 96 | dc = ImageDraw.Draw(image) # 获取绘图对象 97 | dc.rectangle((16, 16, 48, 48), fill='red') # 绘制红色方块 98 | return image 99 | 100 | def show_window(self, icon, item): 101 | """显示主窗口的回调函数""" 102 | self.master.after(0, self.master.deiconify) # 在主线程中恢复窗口 103 | 104 | def quit(self, icon, item): 105 | """退出程序的回调函数""" 106 | self.master.after(0, self.master.destroy) # 在主线程中销毁窗口 107 | 108 | 109 | class TomatoApp(tk.Tk): 110 | """主应用程序类""" 111 | def __init__(self): 112 | super().__init__() 113 | self.title("番茄修仙钟") # 设置窗口标题 114 | self.geometry("300x250") # 设置窗口大小 115 | # 设置窗口关闭按钮行为(最小化到托盘) 116 | self.protocol("WM_DELETE_WINDOW", self.minimize_to_tray) 117 | 118 | # 初始化组件 119 | self.timer = TomatoTimer(self) # 创建计时器实例 120 | self.tray = SystemTray(self) # 创建托盘图标实例 121 | 122 | # 样式配置 123 | self.style = ttk.Style() 124 | self.style.configure("TButton", font=("微软雅黑", 12)) # 按钮字体 125 | self.style.configure("Red.TButton", foreground="red") # 红色按钮样式 126 | 127 | def minimize_to_tray(self): 128 | """最小化到托盘的方法""" 129 | self.withdraw() # 隐藏主窗口 130 | 131 | def run(self): 132 | """启动主循环""" 133 | self.mainloop() 134 | 135 | 136 | if __name__ == "__main__": 137 | app = TomatoApp() # 创建应用实例 138 | app.run() # 运行应用 -------------------------------------------------------------------------------- /day25-Tkinter密码管理器/01-密码管理器.py: -------------------------------------------------------------------------------- 1 | import json 2 | from cryptography.fernet import Fernet # 导入Fernet对称加密模块 3 | import base64 # 导入base64编码模块 4 | import tkinter as tk # 导入GUI库 5 | from tkinter import ttk, messagebox # 导入tkinter的增强组件和消息框 6 | import pyperclip # 剪贴板操作库 7 | from tkinter.simpledialog import askstring # 简单输入对话框 8 | 9 | 10 | class StatusBar(ttk.Frame): 11 | """状态栏组件""" 12 | def __init__(self, master): 13 | super().__init__(master) 14 | self.label = ttk.Label(self, relief='sunken', anchor='w') 15 | self.label.pack(fill='x') 16 | self.pack(side='bottom', fill='x') 17 | 18 | def show(self, text, timeout=0): 19 | """显示状态信息 20 | 21 | 参数: 22 | text: 要显示的文本 23 | timeout: 自动清除时间(秒),0表示不清除 24 | """ 25 | self.label.config(text=text) 26 | if timeout > 0: 27 | self.after(timeout * 1000, lambda: self.label.config(text='')) 28 | 29 | 30 | class PasswordVault: 31 | """密码保险箱加密核心类""" 32 | def __init__(self, master_key): 33 | """初始化密码保险箱 34 | 35 | 参数: 36 | master_key: 用户提供的主密钥,用于生成加密密钥 37 | """ 38 | # 生成加密密钥: 39 | # 1. 将主密钥补足到32字节(不足补空格,超过截断) 40 | # 2. 进行base64 url安全编码 41 | self.key = base64.urlsafe_b64encode(master_key.ljust(32)[:32].encode()) 42 | # 创建Fernet加密器实例 43 | self.cipher = Fernet(self.key) 44 | 45 | def encrypt(self, plaintext): 46 | """加密明文数据 47 | 48 | 参数: 49 | plaintext: 要加密的明文字符串 50 | 51 | 返回: 52 | 加密后的密文字符串 53 | """ 54 | # 1. 将明文编码为bytes 55 | # 2. 使用Fernet加密 56 | # 3. 将加密结果解码为字符串 57 | return self.cipher.encrypt(plaintext.encode()).decode() 58 | 59 | def decrypt(self, ciphertext): 60 | """解密密文数据 61 | 62 | 参数: 63 | ciphertext: 要解密的密文字符串 64 | 65 | 返回: 66 | 解密后的明文字符串 67 | """ 68 | # 1. 将密文编码为bytes 69 | # 2. 使用Fernet解密 70 | # 3. 将解密结果解码为字符串 71 | return self.cipher.decrypt(ciphertext.encode()).decode() 72 | 73 | 74 | class PasswordManager(tk.Tk): 75 | """密码管理器主界面""" 76 | def __init__(self): 77 | super().__init__() 78 | self.title("秘钥宝匣 v1.0") 79 | self.geometry("800x600") 80 | 81 | # 先显示主密码输入对话框 82 | self.show_master_key_dialog() 83 | 84 | # 如果用户输入了主密码,初始化界面 85 | if hasattr(self, 'vault'): 86 | self._create_widgets() 87 | self.load_vault() 88 | 89 | def show_master_key_dialog(self): 90 | """显示主密码输入对话框""" 91 | master_key = askstring("主密码", "请输入主密码:", show='*') 92 | if master_key: 93 | self.vault = PasswordVault(master_key) 94 | else: 95 | self.destroy() # 用户取消输入,关闭程序 96 | 97 | def _create_widgets(self): 98 | """创建界面组件""" 99 | # 顶部工具栏 100 | toolbar = ttk.Frame(self) 101 | toolbar.pack(fill="x", padx=5, pady=5) 102 | 103 | ttk.Button(toolbar, text="新增", command=self.add_entry).pack(side="left") 104 | ttk.Button(toolbar, text="编辑", command=self.edit_entry).pack( 105 | side="left", padx=5 106 | ) 107 | ttk.Button(toolbar, text="删除", command=self.delete_entry).pack(side="left") 108 | ttk.Button(toolbar, text="复制密码", command=self.copy_password).pack(side="left", padx=5) 109 | ttk.Button(toolbar, text="显示密码", command=self.toggle_password).pack(side="left") 110 | 111 | # 搜索框 112 | self.search_var = tk.StringVar() 113 | search_box = ttk.Entry(toolbar, textvariable=self.search_var) 114 | search_box.pack(side="right", padx=5) 115 | search_box.bind("", self.filter_entries) 116 | 117 | # 密码列表 118 | columns = ("website", "username", "password") 119 | self.tree = ttk.Treeview( 120 | self, columns=columns, show="headings", selectmode="browse" 121 | ) 122 | 123 | # 设置列宽和标题 124 | self.tree.heading("website", text="网站/应用") 125 | self.tree.column("website", width=200) 126 | self.tree.heading("username", text="用户名") 127 | self.tree.column("username", width=150) 128 | self.tree.heading("password", text="密码") 129 | self.tree.column("password", width=100) 130 | 131 | # 添加滚动条 132 | scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview) 133 | self.tree.configure(yscrollcommand=scrollbar.set) 134 | scrollbar.pack(side="right", fill="y") 135 | self.tree.pack(fill="both", expand=True) 136 | 137 | # 状态栏 138 | self.status_bar = StatusBar(self) 139 | 140 | # 绑定双击事件 141 | self.tree.bind("", lambda e: self.copy_password()) 142 | 143 | # 密码显示状态 144 | self.passwords_visible = False 145 | 146 | def save_vault(self): 147 | """保存加密保险库到文件""" 148 | data = [] 149 | for item in self.tree.get_children(): 150 | values = self.tree.item(item)["values"] 151 | # 如果密码是掩码形式(••••••••),则不更新加密数据 152 | if values[2] == '•' * 8: 153 | continue 154 | data.append( 155 | { 156 | "website": values[0], 157 | "username": values[1], 158 | "password": self.vault.encrypt(values[2]), 159 | } 160 | ) 161 | 162 | with open("vault.dat", "w") as f: 163 | json.dump(data, f, indent=2) 164 | self.status_bar.show("保险库已保存") 165 | 166 | def load_vault(self): 167 | """从文件加载保险库数据""" 168 | try: 169 | with open("vault.dat") as f: 170 | data = json.load(f) 171 | for item in data: 172 | self.tree.insert( 173 | "", "end", values=(item["website"], item["username"], "•" * 8) 174 | ) 175 | self.status_bar.show("保险库已加载") 176 | except FileNotFoundError: 177 | self.status_bar.show("未找到保险库文件,已创建新库") 178 | except json.JSONDecodeError: 179 | self.status_bar.show("保险库文件损坏") 180 | 181 | def copy_password(self): 182 | """复制密码到剪贴板""" 183 | selected = self.tree.selection() 184 | if selected: 185 | item = self.tree.item(selected[0]) 186 | # 如果是掩码形式,需要先解密 187 | if item["values"][2] == '•' * 8: 188 | # 在实际应用中,这里需要从数据源获取加密密码 189 | # 这里简化处理,直接提示用户 190 | messagebox.showwarning("警告", "请先点击'显示密码'查看密码") 191 | return 192 | 193 | pyperclip.copy(item["values"][2]) 194 | self.status_bar.show("密码已复制(15秒后清除)", 15) 195 | self.after(15000, pyperclip.copy, "") # 15秒后自动清除 196 | 197 | def toggle_password(self): 198 | """切换密码显示/隐藏状态""" 199 | selected = self.tree.selection() 200 | if selected: 201 | item = self.tree.item(selected[0]) 202 | values = list(item["values"]) 203 | 204 | if values[2] == '•' * 8: # 当前是掩码状态,显示真实密码 205 | # 在实际应用中,这里需要从数据源获取加密密码并解密 206 | # 这里简化处理,假设密码已经是明文 207 | values[2] = "password123" # 这里应该是解密后的密码 208 | else: # 当前是明文状态,显示掩码 209 | values[2] = '•' * 8 210 | 211 | self.tree.item(selected[0], values=values) 212 | 213 | def add_entry(self): 214 | """新增密码条目""" 215 | dialog = tk.Toplevel(self) 216 | dialog.title("新增条目") 217 | dialog.transient(self) # 设为模态窗口 218 | dialog.grab_set() # 获取焦点 219 | 220 | # 网站/应用 221 | ttk.Label(dialog, text="网站/应用:").grid(row=0, column=0, sticky="e", padx=5, pady=5) 222 | website = ttk.Entry(dialog) 223 | website.grid(row=0, column=1, padx=5, pady=5) 224 | 225 | # 用户名 226 | ttk.Label(dialog, text="用户名:").grid(row=1, column=0, sticky="e", padx=5, pady=5) 227 | username = ttk.Entry(dialog) 228 | username.grid(row=1, column=1, padx=5, pady=5) 229 | 230 | # 密码 231 | ttk.Label(dialog, text="密码:").grid(row=2, column=0, sticky="e", padx=5, pady=5) 232 | password = ttk.Entry(dialog, show="*") 233 | password.grid(row=2, column=1, padx=5, pady=5) 234 | 235 | def save(): 236 | """保存新条目""" 237 | if not website.get() or not password.get(): 238 | messagebox.showerror("错误", "网站和密码不能为空") 239 | return 240 | 241 | self.tree.insert("", "end", values=(website.get(), username.get(), "•" * 8)) 242 | self.save_vault() 243 | dialog.destroy() 244 | 245 | ttk.Button(dialog, text="保存", command=save).grid(row=3, columnspan=2, pady=10) 246 | 247 | def edit_entry(self): 248 | """编辑选中的条目""" 249 | selected = self.tree.selection() 250 | if not selected: 251 | messagebox.showwarning("警告", "请先选择要编辑的条目") 252 | return 253 | 254 | item = self.tree.item(selected[0]) 255 | values = item["values"] 256 | 257 | dialog = tk.Toplevel(self) 258 | dialog.title("编辑条目") 259 | dialog.transient(self) 260 | dialog.grab_set() 261 | 262 | # 网站/应用 263 | ttk.Label(dialog, text="网站/应用:").grid(row=0, column=0, sticky="e", padx=5, pady=5) 264 | website = ttk.Entry(dialog) 265 | website.insert(0, values[0]) 266 | website.grid(row=0, column=1, padx=5, pady=5) 267 | 268 | # 用户名 269 | ttk.Label(dialog, text="用户名:").grid(row=1, column=0, sticky="e", padx=5, pady=5) 270 | username = ttk.Entry(dialog) 271 | username.insert(0, values[1]) 272 | username.grid(row=1, column=1, padx=5, pady=5) 273 | 274 | # 密码 275 | ttk.Label(dialog, text="密码:").grid(row=2, column=0, sticky="e", padx=5, pady=5) 276 | password = ttk.Entry(dialog) 277 | password.insert(0, values[2] if values[2] != '•' * 8 else "") 278 | password.grid(row=2, column=1, padx=5, pady=5) 279 | 280 | def save(): 281 | """保存编辑结果""" 282 | if not website.get(): 283 | messagebox.showerror("错误", "网站不能为空") 284 | return 285 | 286 | new_values = ( 287 | website.get(), 288 | username.get(), 289 | password.get() if password.get() else values[2] 290 | ) 291 | self.tree.item(selected[0], values=new_values) 292 | self.save_vault() 293 | dialog.destroy() 294 | 295 | ttk.Button(dialog, text="保存", command=save).grid(row=3, columnspan=2, pady=10) 296 | 297 | def delete_entry(self): 298 | """删除选中的条目""" 299 | selected = self.tree.selection() 300 | if not selected: 301 | messagebox.showwarning("警告", "请先选择要删除的条目") 302 | return 303 | 304 | if messagebox.askyesno("确认", "确定要删除选中的条目吗?"): 305 | self.tree.delete(selected[0]) 306 | self.save_vault() 307 | 308 | def filter_entries(self, event=None): 309 | """根据搜索框内容过滤条目""" 310 | query = self.search_var.get().lower() 311 | 312 | for item in self.tree.get_children(): 313 | values = self.tree.item(item)["values"] 314 | # 检查网站名或用户名是否包含搜索词 315 | if query in values[0].lower() or query in values[1].lower(): 316 | self.tree.item(item, tags=('match',)) 317 | self.tree.detach(item) # 先移除 318 | self.tree.reattach(item, '', 'end') # 重新附加到可见区域 319 | else: 320 | self.tree.detach(item) # 不匹配的条目隐藏 321 | 322 | 323 | if __name__ == "__main__": 324 | app = PasswordManager() 325 | app.mainloop() -------------------------------------------------------------------------------- /day26-闪卡复习系统/01-闪卡复习系统.py: -------------------------------------------------------------------------------- 1 | """ 2 | 记忆卡间隔重复系统 - 记忆熔炉 v1.2 3 | 作者:AI助手 4 | 功能说明: 5 | 1. 使用tkinter构建的图形界面应用程序 6 | 2. 支持卡片正反面翻转动画效果 7 | 3. 实现基于SM-2改进的间隔重复算法 8 | 4. 记忆曲线可视化功能 9 | 5. 自动保存/加载学习进度 10 | """ 11 | 12 | # 导入必要的库 13 | import tkinter as tk 14 | from tkinter import ttk, messagebox 15 | from datetime import datetime, timedelta 16 | import json 17 | import os 18 | from matplotlib.figure import Figure 19 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 20 | 21 | 22 | class FlashCard(tk.Canvas): 23 | """可翻转的记忆卡片组件""" 24 | 25 | def __init__(self, master, front_text, back_text, **kwargs): 26 | """ 27 | 初始化记忆卡片 28 | :param master: 父容器 29 | :param front_text: 正面文字内容 30 | :param back_text: 背面文字内容 31 | """ 32 | super().__init__(master, width=300, height=200, bg="white", **kwargs) 33 | self.front_text = front_text # 卡片正面内容 34 | self.back_text = back_text # 卡片背面内容 35 | self.is_front = True # 当前显示是否为正面 36 | 37 | # 初始化绘制卡片正面 38 | self.draw_card(self.front_text) 39 | # 绑定点击事件 40 | self.bind("", self.flip) 41 | 42 | def draw_card(self, text): 43 | """绘制卡片内容""" 44 | self.delete("all") # 清空画布 45 | # 绘制蓝色边框 46 | self.create_rectangle(5, 5, 295, 195, outline="#4B8BBE", width=2) 47 | # 添加文字内容(支持自动换行) 48 | self.create_text( 49 | 150, 100, text=text, font=("微软雅黑", 14), width=280, tags="text" 50 | ) 51 | 52 | def flip(self, event=None): 53 | """执行卡片翻转动画""" 54 | # 使用宽度变化模拟3D翻转效果 55 | for i in range(0, 90, 5): 56 | self.configure(width=300 * (1 - abs(i - 45) / 45)) # 动态调整宽度 57 | self.update() 58 | self.master.after(20) # 控制动画速度 59 | 60 | # 切换显示内容 61 | new_text = self.back_text if self.is_front else self.front_text 62 | self.is_front = not self.is_front # 切换正反面状态 63 | self.draw_card(new_text) 64 | 65 | 66 | class Card: 67 | """记忆卡片数据模型""" 68 | 69 | def __init__(self, front, back): 70 | """ 71 | 初始化卡片数据 72 | :param front: 正面内容 73 | :param back: 背面内容 74 | """ 75 | self.front = front # 卡片正面问题 76 | self.back = back # 卡片背面答案 77 | self.interval = 1 # 当前复习间隔(天) 78 | self.ease_factor = 2.5 # 易度因子(调整间隔用) 79 | self.next_review = datetime.now() # 下次复习时间 80 | self.review_count = 0 # 总复习次数 81 | self.history = [] # 复习历史记录(日期,评分) 82 | 83 | 84 | class SpacedRepetition: 85 | """间隔重复调度算法(基于SM-2改进)""" 86 | 87 | def update_card(self, card, quality): 88 | """ 89 | 更新卡片记忆参数 90 | :param card: 要更新的卡片对象 91 | :param quality: 用户评分(0-3) 92 | """ 93 | # 评分对应参数映射表(易度调整值,间隔调整系数) 94 | quality_map = { 95 | 0: (0.0, 0.8), # 忘记 96 | 1: (0.4, 0.9), # 困难 97 | 2: (0.6, 1.1), # 一般 98 | 3: (1.0, 1.3), # 容易 99 | } 100 | 101 | # 获取调整参数 102 | ease_delta, interval_mod = quality_map.get(quality, (0, 1)) 103 | # 调整易度因子(最低1.3) 104 | card.ease_factor = max(1.3, card.ease_factor + ease_delta) 105 | # 计算新间隔(取整) 106 | card.interval = int(round(card.interval * card.ease_factor * interval_mod)) 107 | # 设置下次复习时间 108 | card.next_review = datetime.now() + timedelta(days=card.interval) 109 | card.review_count += 1 # 增加复习次数 110 | # 记录复习历史 111 | card.history.append((datetime.now().strftime("%Y-%m-%d"), quality)) 112 | 113 | 114 | class FlashCardApp(tk.Tk): 115 | """主应用程序""" 116 | 117 | def __init__(self): 118 | super().__init__() 119 | # 窗口基本设置 120 | self.title("记忆熔炉 v1.2") 121 | self.geometry("600x800") 122 | self.scheduler = SpacedRepetition() # 初始化调度器 123 | self.deck = self.load_deck() # 加载卡片数据 124 | self.current_card = None # 当前显示的卡片 125 | 126 | # 初始化界面 127 | self.create_widgets() 128 | self.show_next_card() # 显示第一个卡片 129 | # 设置关闭事件处理 130 | self.protocol("WM_DELETE_WINDOW", self.on_close) 131 | 132 | def create_widgets(self): 133 | """创建界面组件""" 134 | # 控制按钮面板 135 | control_frame = ttk.Frame(self) 136 | control_frame.pack(pady=10, fill="x") 137 | 138 | # 定义评分按钮配置 139 | buttons = [ 140 | ("忘记 (0)", 0, "#FF6B6B"), # 红色 141 | ("困难 (1)", 1, "#FFD93D"), # 黄色 142 | ("一般 (2)", 2, "#6C5CE7"), # 紫色 143 | ("记住 (3)", 3, "#00B894"), # 绿色 144 | ] 145 | 146 | # 创建并排列评分按钮 147 | for text, q, color in buttons: 148 | btn = ttk.Button( 149 | control_frame, 150 | text=text, 151 | command=lambda q=q: self.rate_card(q), 152 | style=f"{color}.TButton", 153 | ) 154 | btn.pack(side="left", padx=5, expand=True) 155 | 156 | # 卡片显示区域 157 | self.card_frame = ttk.Frame(self) 158 | self.card_frame.pack(pady=20, fill="both", expand=True) 159 | 160 | # 初始化统计图表 161 | self.figure = Figure(figsize=(5, 3), dpi=100) 162 | self.ax = self.figure.add_subplot(111) 163 | self.canvas = FigureCanvasTkAgg(self.figure, self) 164 | self.canvas.get_tk_widget().pack(fill="both", expand=True) 165 | 166 | # 配置按钮样式 167 | self.style = ttk.Style() 168 | self.style.configure("TButton", font=("微软雅黑", 10)) 169 | # 为不同按钮设置颜色 170 | for color in ["#FF6B6B", "#FFD93D", "#6C5CE7", "#00B894"]: 171 | self.style.configure( 172 | f"{color}.TButton", foreground="white", background=color 173 | ) 174 | 175 | def load_deck(self): 176 | """加载卡片数据""" 177 | # 默认卡片数据(当文件不存在时使用) 178 | default_cards = [ 179 | { 180 | "front": "Python的GIL是指?", 181 | "back": "全局解释器锁 (Global Interpreter Lock)", 182 | }, 183 | {"front": "@staticmethod的作用", "back": "声明静态方法,不需要实例参数"}, 184 | ] 185 | 186 | try: 187 | # 尝试从JSON文件加载数据 188 | with open("flashcards.json", "r", encoding="utf-8") as f: 189 | data = json.load(f) 190 | return [Card(**item) for item in data] 191 | except FileNotFoundError: 192 | # 文件不存在时使用默认卡片 193 | return [Card(**item) for item in default_cards] 194 | 195 | def save_deck(self): 196 | """保存卡片数据到JSON文件""" 197 | data = [] 198 | for card in self.deck: 199 | data.append( 200 | { 201 | "front": card.front, 202 | "back": card.back, 203 | "interval": card.interval, 204 | "ease_factor": card.ease_factor, 205 | "next_review": card.next_review.strftime("%Y-%m-%d %H:%M:%S"), 206 | "review_count": card.review_count, 207 | "history": card.history, 208 | } 209 | ) 210 | 211 | # 写入JSON文件 212 | with open("flashcards.json", "w", encoding="utf-8") as f: 213 | json.dump(data, f, ensure_ascii=False, indent=2) 214 | 215 | def show_next_card(self): 216 | """显示下一个待复习的卡片""" 217 | # 清空当前卡片显示区域 218 | for widget in self.card_frame.winfo_children(): 219 | widget.destroy() 220 | 221 | # 筛选需要复习的卡片(下次复习时间已到) 222 | due_cards = [c for c in self.deck if datetime.now() > c.next_review] 223 | if due_cards: 224 | self.current_card = due_cards[0] 225 | # 创建并显示卡片组件 226 | FlashCard( 227 | self.card_frame, self.current_card.front, self.current_card.back 228 | ).pack() 229 | self.update_chart() # 更新统计图表 230 | else: 231 | self.update_chart() 232 | messagebox.showinfo("完成", "今日所有卡片已复习完成!") 233 | 234 | def rate_card(self, quality): 235 | """处理用户评分""" 236 | if self.current_card: 237 | self.scheduler.update_card(self.current_card, quality) 238 | self.show_next_card() 239 | 240 | def update_chart(self): 241 | """更新记忆曲线图表""" 242 | self.ax.clear() # 清空旧图表 243 | 244 | # 收集所有卡片的历史数据 245 | dates = [] 246 | intervals = [] 247 | for card in self.deck: 248 | if card.history: 249 | # 获取最近一次复习日期 250 | last_date = datetime.strptime(card.history[-1][0], "%Y-%m-%d") 251 | dates.append(last_date) 252 | intervals.append(card.interval) 253 | 254 | # 绘制散点图 255 | if dates and intervals: 256 | self.ax.scatter(dates, intervals, c="#6C5CE7", alpha=0.7) 257 | self.ax.set_title("记忆间隔趋势") 258 | self.ax.set_ylabel("下次复习间隔(天)") 259 | self.ax.grid(True) 260 | self.figure.autofmt_xdate() # 自动调整日期格式 261 | self.canvas.draw() # 重绘画布 262 | 263 | def on_close(self): 264 | """处理窗口关闭事件""" 265 | self.save_deck() # 保存数据 266 | self.destroy() 267 | 268 | 269 | if __name__ == "__main__": 270 | app = FlashCardApp() 271 | app.mainloop() 272 | -------------------------------------------------------------------------------- /day27-日期与邮件发送/01-datetime.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | import pytz # 需安装pytz 4 | 5 | # 创建特定时区时间 6 | 7 | tz_shanghai = pytz.timezone("Asia/Shanghai") 8 | now = datetime.now(tz_shanghai) 9 | print(f"上海时间: {now.strftime('%Y-%m-%d %H:%M:%S %Z')}") 10 | 11 | # 时间间隔计算 12 | new_year = datetime(2026, 1, 1, tzinfo=tz_shanghai) 13 | countdown = new_year - now 14 | print(f"距离2026新年还有: {countdown.days}天 {countdown.seconds//3600}小时") 15 | 16 | 17 | # 周期性任务判断 18 | def is_weekday(date): 19 | return 0 <= date.weekday() < 5 20 | 21 | 22 | print(f"今天是否工作日: {is_weekday(now)}") 23 | -------------------------------------------------------------------------------- /day27-日期与邮件发送/02-邮件发送.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.multipart import MIMEMultipart 3 | from email.mime.text import MIMEText 4 | from email.mime.application import MIMEApplication 5 | import os 6 | 7 | 8 | class EmailSender: 9 | def __init__(self): 10 | self.smtp_server = os.getenv("SMTP_SERVER", "smtp.example.com") 11 | self.smtp_port = int(os.getenv("SMTP_PORT", 465)) 12 | self.username = os.getenv("EMAIL_USER") 13 | self.password = os.getenv("EMAIL_PWD") 14 | 15 | def send(self, to_addrs, subject, content, attachments=[]): 16 | msg = MIMEMultipart() 17 | msg["From"] = f"时光管理系统 <{self.username}>" 18 | msg["To"] = ", ".join(to_addrs) 19 | msg["Subject"] = subject 20 | 21 | # HTML正文 22 | html = f""" 23 | 24 |

{subject}

25 |
{content}
26 | 27 | """ 28 | msg.attach(MIMEText(html, "html")) 29 | 30 | # 添加附件 31 | for file in attachments: 32 | with open(file, "rb") as f: 33 | part = MIMEApplication(f.read()) 34 | part.add_header( 35 | "Content-Disposition", "attachment", filename=os.path.basename(file) 36 | ) 37 | msg.attach(part) 38 | 39 | # SSL加密发送 40 | with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server: 41 | server.login(self.username, self.password) 42 | server.sendmail(self.username, to_addrs, msg.as_string()) 43 | -------------------------------------------------------------------------------- /day27-日期与邮件发送/03-完整代码.py: -------------------------------------------------------------------------------- 1 | import os 2 | import smtplib 3 | import threading 4 | from datetime import datetime, timedelta 5 | from email.mime.multipart import MIMEMultipart 6 | from email.mime.text import MIMEText 7 | from email.mime.application import MIMEApplication 8 | from zoneinfo import ZoneInfo 9 | import pytz 10 | 11 | 12 | # ==================== 13 | # 邮件发送核心模块 14 | # ==================== 15 | class EmailSender: 16 | """安全邮件发送器(支持SSL/TLS)""" 17 | 18 | def __init__(self): 19 | self.smtp_server = os.getenv("SMTP_SERVER", "smtp.example.com") 20 | self.smtp_port = int(os.getenv("SMTP_PORT", 465)) 21 | self.username = os.getenv("EMAIL_USER") 22 | self.password = os.getenv("EMAIL_PWD") 23 | self.timeout = 10 # 秒 24 | 25 | def send_email(self, to_addrs, subject, content, attachments=None, html=False): 26 | """ 27 | 发送邮件 28 | :param to_addrs: 收件人列表 29 | :param subject: 邮件主题 30 | :param content: 邮件内容 31 | :param attachments: 附件路径列表 32 | :param html: 是否使用HTML格式 33 | """ 34 | msg = MIMEMultipart() 35 | msg["From"] = f"自动化系统 <{self.username}>" 36 | msg["To"] = ", ".join(to_addrs) 37 | msg["Subject"] = subject 38 | msg["Date"] = datetime.now(pytz.utc).strftime("%a, %d %b %Y %H:%M:%S +0000") 39 | 40 | # 添加正文 41 | content_type = "html" if html else "plain" 42 | msg.attach(MIMEText(content, content_type, "utf-8")) 43 | 44 | # 添加附件 45 | if attachments: 46 | for file_path in attachments: 47 | with open(file_path, "rb") as f: 48 | part = MIMEApplication(f.read()) 49 | filename = os.path.basename(file_path) 50 | part.add_header( 51 | "Content-Disposition", "attachment", filename=filename 52 | ) 53 | msg.attach(part) 54 | 55 | try: 56 | with smtplib.SMTP_SSL( 57 | self.smtp_server, self.smtp_port, timeout=self.timeout 58 | ) as server: 59 | server.login(self.username, self.password) 60 | server.sendmail(self.username, to_addrs, msg.as_string()) 61 | print(f"邮件成功发送至 {', '.join(to_addrs)}") 62 | except Exception as e: 63 | print(f"邮件发送失败: {str(e)}") 64 | raise 65 | 66 | 67 | # ==================== 68 | # 日报生成系统 69 | # ==================== 70 | class DailyReporter: 71 | """智能日报生成器""" 72 | 73 | def __init__(self, timezone="Asia/Shanghai"): 74 | self.timezone = ZoneInfo(timezone) 75 | self.template = """ 76 | 77 | 83 | 84 | 85 |
86 |

{date} 修仙日报

87 |
88 | 89 |
90 |
91 |

📊 今日统计

92 |

修炼时长: {practice_hours} 小时

93 |

丹药炼制: {pills_made} 颗

94 |

心法突破: {breakthroughs} 次

95 |
96 | 97 |
98 |

📅 明日计划

99 |
    {next_plan}
100 |
101 |
102 | 103 | 104 | """ 105 | 106 | def generate_report(self, data): 107 | """生成HTML日报""" 108 | plan_items = "\n".join([f"
  • {item}
  • " for item in data["next_plan"]]) 109 | return self.template.format( 110 | date=datetime.now(self.timezone).strftime("%Y-%m-%d"), 111 | practice_hours=data["practice_hours"], 112 | pills_made=data["pills_made"], 113 | breakthroughs=data["breakthroughs"], 114 | next_plan=plan_items, 115 | ) 116 | 117 | def schedule_daily_report( 118 | self, send_time="09:00", recipients=None, attachments=None 119 | ): 120 | """ 121 | 每日定时发送日报 122 | :param send_time: 发送时间(格式HH:MM) 123 | :param recipients: 收件人列表 124 | :param attachments: 附件路径列表 125 | """ 126 | 127 | def get_next_run(): 128 | now = datetime.now(self.timezone) 129 | target = datetime.strptime(send_time, "%H:%M").time() 130 | target_dt = now.replace( 131 | hour=target.hour, minute=target.minute, second=0, microsecond=0 132 | ) 133 | if now >= target_dt: 134 | target_dt += timedelta(days=1) 135 | return target_dt 136 | 137 | def send_task(): 138 | # 生成模拟数据 139 | report_data = { 140 | "practice_hours": 6.5, 141 | "pills_made": 42, 142 | "breakthroughs": 3, 143 | "next_plan": [ 144 | "完成心法第三重修炼", 145 | "宗门资源调配会议", 146 | "炼丹房设备维护", 147 | ], 148 | } 149 | 150 | # 生成并发送邮件 151 | emailer = EmailSender() 152 | html_content = self.generate_report(report_data) 153 | 154 | emailer.send_email( 155 | to_addrs=recipients or [os.getenv("DEFAULT_RECIPIENT")], 156 | subject=f"修仙日报 {datetime.now().strftime('%Y-%m-%d')}", 157 | content=html_content, 158 | attachments=attachments, 159 | html=True, 160 | ) 161 | 162 | # 重新调度下一次任务 163 | threading.Timer( 164 | (get_next_run() - datetime.now(self.timezone)).total_seconds(), 165 | send_task, 166 | ).start() 167 | 168 | # 初始启动 169 | initial_delay = (get_next_run() - datetime.now(self.timezone)).total_seconds() 170 | threading.Timer(initial_delay, send_task).start() 171 | 172 | 173 | # ==================== 174 | # 使用示例 175 | # ==================== 176 | if __name__ == "__main__": 177 | # 配置环境变量(实际使用时应通过.env文件或系统环境变量配置) 178 | os.environ.update( 179 | { 180 | "SMTP_SERVER": "smtp.xiuxian.com", 181 | "SMTP_PORT": "465", 182 | "EMAIL_USER": "system@xiuxian.com", 183 | "EMAIL_PWD": "your_encrypted_password", 184 | "DEFAULT_RECIPIENT": "master@xiuxian.com", 185 | } 186 | ) 187 | 188 | # 初始化日报系统 189 | reporter = DailyReporter(timezone="Asia/Shanghai") 190 | 191 | # 添加示例附件 192 | sample_attachments = ["data/修炼记录.pdf", "data/丹药库存.xlsx"] 193 | 194 | # 启动每日9:00自动发送 195 | reporter.schedule_daily_report( 196 | send_time="09:00", 197 | recipients=["master@xiuxian.com", "assistant@xiuxian.com"], 198 | attachments=sample_attachments, 199 | ) 200 | 201 | # 保持主线程运行 202 | while True: 203 | pass 204 | -------------------------------------------------------------------------------- /day28-初识API/01-天气API.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | # 示例:调用天气API 5 | def get_weather(city): 6 | # 通过API端点获取数据 7 | response = requests.get(f"") 8 | # 解析返回的JSON格式数据 9 | return response.json()["temperature"] 10 | 11 | 12 | print(get_weather("shanghai")) 13 | -------------------------------------------------------------------------------- /day28-初识API/02-kanye.py: -------------------------------------------------------------------------------- 1 | from tkinter import * # 导入 Tkinter GUI 库中的所有组件 2 | import requests # 导入用于发送 HTTP 请求的 requests 库 3 | 4 | def get_quote(): 5 | """获取 Kanye 随机语录并更新画布文本""" 6 | response = requests.get("https://api.kanye.rest") # 向 Kanye.rest API 发送 GET 请求 7 | response.raise_for_status() # 如果响应状态不是 200,会抛出异常 8 | data = response.json() # 将响应内容解析为 JSON 字典 9 | quote = data["quote"] # 从字典中提取 "quote" 字段 10 | canvas.itemconfig(quote_text, text=quote) # 更新画布上 quote_text 对象的文本内容 11 | 12 | # —— 创建主窗口 —— 13 | window = Tk() # 创建 Tkinter 应用主窗口实例 14 | window.title("Kanye 天机语") # 设置窗口标题 15 | window.config(padx=50, pady=50) # 设置窗口内边距,左右上下各留白 50 像素 16 | 17 | # —— 配置画布与背景 —— 18 | canvas = Canvas(width=300, height=414) # 创建画布,宽 300、高 414 像素 19 | background_img = PhotoImage(file="background.png") # 加载背景图片,文件名须与脚本同目录 20 | canvas.create_image(150, 207, image=background_img) # 在画布中心(150,207)绘制背景图片 21 | quote_text = canvas.create_text( 22 | 150, 207, # 文本位置:画布中心 23 | text="Kanye Quote Goes HERE", # 初始显示的占位文本 24 | width=250, # 文本框宽度,超出自动换行 25 | font=("Arial", 30, "bold"), # 字体:Arial,字号 30,加粗 26 | fill="white" # 文本颜色:白色 27 | ) 28 | canvas.grid(row=0, column=0) # 使用 grid 布局,将画布放置在第 0 行第 0 列 29 | 30 | # —— 配置按钮 —— 31 | kanye_img = PhotoImage(file="kanye.png") # 加载按钮图标图片 32 | kanye_button = Button( 33 | image=kanye_img, # 将按钮显示为图片 34 | highlightthickness=0, # 去除按钮点击时的高亮边框 35 | command=get_quote # 单击按钮时调用 get_quote 函数 36 | ) 37 | kanye_button.grid(row=1, column=0) # 将按钮放置在第 1 行第 0 列 38 | 39 | # —— 进入 Tkinter 主事件循环 —— 40 | window.mainloop() # 启动事件循环,保持窗口运行并响应用户操作 41 | -------------------------------------------------------------------------------- /day28-初识API/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zesheng-Wang/100-days-python/dd849a1e907f8bf299dfe27ef09e8c7d98be7c5a/day28-初识API/background.png -------------------------------------------------------------------------------- /day28-初识API/kanye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zesheng-Wang/100-days-python/dd849a1e907f8bf299dfe27ef09e8c7d98be7c5a/day28-初识API/kanye.png -------------------------------------------------------------------------------- /day29-GUI问答系统/01-api解析.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def get_question(): 5 | """获取天机题库问题""" 6 | params = { 7 | "amount": 1, 8 | "type": "multiple", # 多选题 9 | "category": 18, # 计算机类 10 | "encode": "url3986", # 编码格式 11 | } 12 | 13 | try: 14 | response = requests.get("https://opentdb.com/api.php", params=params, timeout=5) 15 | data = response.json() 16 | return data["results"][0] 17 | except Exception as e: 18 | print(f"天机获取失败: {str(e)}") 19 | return None 20 | 21 | 22 | result = get_question() 23 | print(result) 24 | # 示例数据结构 25 | """ 26 | { 27 | 'category': 'Science: Computers', 28 | 'type': 'multiple', 29 | 'difficulty': 'easy', 30 | 'question': 'HTML是什么的缩写?', 31 | 'correct_answer': 'Hyper Text Markup Language', 32 | 'incorrect_answers': ['..."] 33 | } 34 | """ 35 | { 36 | "type": "multiple", 37 | "difficulty": "easy", 38 | "category": "Science%3A%20Computers", 39 | "question": "What%20programming%20language%20was%20GitHub%20written%20in%3F", 40 | "correct_answer": "Ruby", 41 | "incorrect_answers": ["JavaScript", "Python", "Lua"], 42 | } 43 | -------------------------------------------------------------------------------- /day29-GUI问答系统/02-智能问答系统.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | import requests 3 | import html 4 | from random import shuffle 5 | from urllib.parse import unquote 6 | 7 | 8 | def decode_api_data(data): 9 | """完整解码API返回数据""" 10 | return { 11 | "category": html.unescape(unquote(data["category"])), 12 | "question": html.unescape(unquote(data["question"])), 13 | "correct_answer": html.unescape(unquote(data["correct_answer"])), 14 | "incorrect_answers": [ 15 | html.unescape(unquote(ans)) for ans in data["incorrect_answers"] 16 | ], 17 | } 18 | 19 | 20 | def get_question(): 21 | """获取天机题库问题""" 22 | params = { 23 | "amount": 1, 24 | "type": "multiple", # 多选题 25 | "category": 18, # 计算机类 26 | "encode": "url3986", # 编码格式 27 | } 28 | 29 | try: 30 | response = requests.get("https://opentdb.com/api.php", params=params, timeout=5) 31 | data = response.json() 32 | data = data["results"][0] 33 | return decode_api_data(data) # 使用解码函数 34 | except Exception as e: 35 | print(f"天机获取失败: {str(e)}") 36 | return None 37 | 38 | 39 | class QuizApp: 40 | def __init__(self): 41 | self.window = Tk() 42 | self.window.title("天机问答") 43 | self.window.config(padx=20, pady=20, bg="#2C3E50") 44 | 45 | # 分数显示 46 | self.score = 0 47 | self.score_label = Label( 48 | text=f"修为:{self.score}", 49 | fg="white", 50 | bg="#2C3E50", 51 | font=("微软雅黑", 20, "bold"), 52 | ) 53 | self.score_label.grid(row=0, column=1) 54 | 55 | # 问题画布 56 | self.canvas = Canvas(width=400, height=300, bg="#34495E", highlightthickness=0) 57 | self.question_text = self.canvas.create_text( 58 | 200, 59 | 150, 60 | text="天机加载中...", 61 | width=380, 62 | fill="white", 63 | font=("微软雅黑", 18), 64 | ) 65 | self.canvas.grid(row=1, column=0, columnspan=2, pady=20) 66 | 67 | # 选项按钮 68 | self.buttons = [] 69 | for i in range(4): 70 | btn = Button( 71 | text="", 72 | width=35, 73 | height=2, 74 | bg="#3498DB", 75 | fg="white", 76 | font=("微软雅黑", 12), 77 | command=lambda idx=i: self.check_answer(idx), 78 | ) 79 | btn.grid(row=2 + i, column=0, columnspan=2, pady=5) 80 | self.buttons.append(btn) 81 | 82 | # 下一题按钮 83 | self.next_btn = Button( 84 | text="⟳ 下一题", 85 | command=self.next_question, 86 | state=DISABLED, 87 | bg="#27AE60", 88 | font=("微软雅黑", 14), 89 | ) 90 | self.next_btn.grid(row=6, column=0, columnspan=2, pady=10) 91 | 92 | self.current_question = None 93 | self.next_question() # 初始加载 94 | 95 | self.window.mainloop() 96 | 97 | def decode_text(self, text): 98 | """解码HTML特殊字符""" 99 | return html.unescape(text) 100 | 101 | def next_question(self): 102 | """加载新问题""" 103 | self.next_btn.config(state=DISABLED) 104 | question_data = get_question() 105 | 106 | if question_data: 107 | # 处理问题数据 108 | self.current_question = { 109 | "question": self.decode_text(question_data["question"]), 110 | "correct": self.decode_text(question_data["correct_answer"]), 111 | "options": [ 112 | self.decode_text(ans) for ans in question_data["incorrect_answers"] 113 | ] 114 | + [self.decode_text(question_data["correct_answer"])], 115 | } 116 | shuffle(self.current_question["options"]) # 随机选项顺序 117 | 118 | # 更新界面 119 | self.canvas.itemconfig( 120 | self.question_text, text=self.current_question["question"] 121 | ) 122 | for i, btn in enumerate(self.buttons): 123 | btn.config(text=self.current_question["options"][i], bg="#3498DB") 124 | else: 125 | self.canvas.itemconfig(self.question_text, text="天机不可测,请稍后再试...") 126 | 127 | def check_answer(self, selected_idx): 128 | """验证答案""" 129 | selected = self.current_question["options"][selected_idx] 130 | correct = self.current_question["correct"] 131 | 132 | # 高亮显示结果 133 | for i, option in enumerate(self.current_question["options"]): 134 | if option == correct: 135 | self.buttons[i].config(bg="#27AE60") # 正确答案绿色 136 | elif i == selected_idx: 137 | self.buttons[i].config(bg="#E74C3C") # 错误答案红色 138 | 139 | # 更新分数 140 | if selected == correct: 141 | self.score += 10 142 | self.score_label.config(text=f"修为:{self.score}") 143 | 144 | self.next_btn.config(state=NORMAL) # 启用下一题按钮 145 | 146 | 147 | if __name__ == "__main__": 148 | QuizApp() 149 | -------------------------------------------------------------------------------- /vault.dat: -------------------------------------------------------------------------------- 1 | [] --------------------------------------------------------------------------------