编辑
2024-03-29
夺巧
00
请注意,本文编写于 399 天前,最后修改于 399 天前,其中某些信息可能已经过时。

目录

开始前的废话
动机与目标
开始编程

开始前的废话

python一直都以其易学和简约而为人们所知,最近更是乘上了人工智能兴起的风潮,成为了与C/C++和Java一样重要的编程语言。虽然因为语法和性能上的限制使其不能像另外两者一样能够构建大项目,但python自有它自己的独特之处,作为一个脚本语言。
这里就引用一下我在之前留下的关于脚本语言的笔记:

所谓脚本,与预先将源文件编译成可执行文件的编译型语言相对,运行时通过解释器逐行解释,因而运行速度也会慢一些,而换取的则是开发调试时的自由。很多情况下,都可以调出终端输入python,输个表达式按回车当作计算器使用,复杂一些的式子也可以在几行之内解决。倘若忽然忘记了python的某个用法,也可以直接在命令行中实验解决。这种交互式编程是其他编译型语言绝对无法企及的,也因此带来了无限的便利。

当然,我们此处使用python倒不是这个原因,而仅仅是因为用python编写方便,并且配合bat无需编译而已。

说起来,我又想起了自己初识python的时候。那是初中一个夏日的午后,开着空调的机房里我们还在无聊地敲着C++代码(说到底也只是加上了cin和cout的C语言)。老师忽然感慨道编程语言发展得真快,python开始变得越来越流行,而老师在学习编程时用的还是Pascal语言。那时编程还是个相当新奇且有魔力的事情,黑底白字的窗口似乎能创造出一切。所幸,如今的我仍没忘记那时的初心。

动机与目标

闲话少叙,先说说编写这一脚本的动机。唔,或许大家听说过“铁人模式”,也就是游戏中通过某种手段防止SL(save and load)的一种模式。而在欧陆风云4(eu4)里,铁人模式是通过在游戏中进行自动存档来实现的,而(正常)退出游戏也会强制进行存档。当然,对于我这样尽量追求尽善尽美的人来说,倘若游戏中随机出了比较糟心的事件还是难以忍受的,恰好这样铁人模式的实现方式也给了我们很大操作的空间。
因为在游戏中实际上是每进行3个月进行一个自动存档,因而当出现坏事件的时候暂停并回退到上一个自动存档就可以规避了。正常情况下我们只需要按alt+F4强制退出游戏并重新打开就可以回到上一个自动存档,不过有时候还有一些其他需求。比如出现坏事件的一瞬间没反应过来而导致自动存档被覆盖,就需要将新存档删除,而改用上一次的自动存档(游戏会为每一个存档名准备两个自动存档,例如FRA.eu4和FRA_BACKUP.eu4),以及有时候需要先走一段时间再看需不需要重新读档,此时就得把存档备份到别处,需要时再复制回来。而此时需要存放多个同名的存档时,又需要按照时间顺序进行编号。
这样一来,便有了编写脚本来自动化存档管理的需求。考虑到便捷性,我们希望打开脚本时仅需要输入一个字母就能传达给脚本需要做的事情,将在以下列出:

  • 将存档以及备份存档复制到专门存放备份的文件夹中并进行编号 (S: save)
  • 从文件夹中重新读取最新的存档 (L: load)
  • 将存档替换为备份存档 (B: backup)
  • 指定需要备份的存档名 (N: name)
  • 删除备份文件夹中指定的存档编号 (D: delete)
  • 清除备份中存档名下的所有存档 (C: clean)
  • 列出备份文件夹中的所有存档信息 (I: info)
  • 输出帮助文档 (H: help)

开始编程

其实可以看出,列出需要的功能之后,每一个功能都可以实现为一个函数。不过这里再“做作”一点,将存档管理器编写为一个类,而各功能是其中的方法,于是就有了下面的框架代码:

python
# SaveManager.py import os import shutil SAVE_PATH = ( r"C:\Users\34960\Documents\Paradox Interactive\Europa Universalis IV\save games" ) BACKUP_PATH = r"F:\备份\EU4\save" class SaveManager: def __init__(self): file = open("savename.txt", "r") self.savename = file.read().strip() file.close() # S: save SAVE_PATH.save -> BACKUP_PATH.save+no def save(self): pass # L: load SAVE_PATH.save -> SAVE_PATH.save_backup BACKUP_PATH.save+no -> SAVE_PATH.save def load(self): pass # B: backup SAVE_PATH.save_backup -> SAVE_PATH.save def backup(self): pass # N: name change the save name def name(self, name): pass # D: delete delete the save with the selected number def delete(self, number): pass # C: clean clean saves with name savename def clean(self): pass # I: info list all the saves in BACKUP_PATH def info(self): pass # H: help show the help message def help(self): pass

其中SAVE_PATH和BACKUP_PATH分别是存放游戏存档和存放备份的文件夹路径,而在构造函数中我们通过访问脚本同目录下的savename.txt文件获知了当前需要备份的存档名。剩下的便是八个方法对应八个功能。

python
# S: save SAVE_PATH.save -> BACKUP_PATH.save+no def save(self): number = 1 # number-1为备份目录内最大存档编号,下一个备份存档编号为number while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): number += 1 # 备份savename.eu4 if os.path.exists(SAVE_PATH + "\\" + self.savename + ".eu4"): shutil.copy(SAVE_PATH + "\\" + self.savename + ".eu4", BACKUP_PATH) os.rename( BACKUP_PATH + "\\" + self.savename + ".eu4", BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4", ) print("Save file copied and renamed to " + self.savename + str(number) + ".eu4") else: print("Save file not found") # 备份savename_Backup.eu4 if os.path.exists(SAVE_PATH + "\\" + self.savename + "_Backup.eu4"): shutil.copy(SAVE_PATH + "\\" + self.savename + "_Backup.eu4", BACKUP_PATH) print("Backup file copied") else: print("Backup file not found")
python
# L: load SAVE_PATH.save -> SAVE_PATH.save_backup BACKUP_PATH.save+no -> SAVE_PATH.save def load(self): number = 1 while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): number += 1 number -= 1 # 需要加载的存档编号 if number == 0: print("Save file not found") return # save->save_backup if os.path.exists(SAVE_PATH + "\\" + self.savename + ".eu4"): if os.path.exists(SAVE_PATH + "\\" + self.savename + "_Backup.eu4"): os.remove(SAVE_PATH + "\\" + self.savename + "_Backup.eu4") os.rename(SAVE_PATH + "\\" + self.savename + ".eu4", SAVE_PATH + "\\" + self.savename + "_Backup.eu4") print("Save file renamed to " + self.savename + "_Backup.eu4") # 加载备份存档 file_path = BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4" shutil.copy(file_path, SAVE_PATH) os.rename( SAVE_PATH + "\\" + self.savename + str(number) + ".eu4", SAVE_PATH + "\\" + self.savename + ".eu4", ) print(self.savename + str(number) + ".eu4 " + "copied and renamed to " + self.savename + ".eu4")
python
def backup(self): if os.path.exists(SAVE_PATH + "\\" + self.savename + "_Backup.eu4"): if os.path.exists(SAVE_PATH + "\\" + self.savename + ".eu4"): os.remove(SAVE_PATH + "\\" + self.savename + ".eu4") os.rename( SAVE_PATH + "\\" + self.savename + "_Backup.eu4", SAVE_PATH + "\\" + self.savename + ".eu4", ) print(self.savename + "_Backup.eu4 " + "renamed to " + self.savename + ".eu4") else: print("Backup file not found")
python
# N: name change the save name def name(self, name): file = open("savename.txt", "w") file.write(name) file.close() self.savename = name
python
# D: delete delete the save with the selected number def delete(self, number): # 默认删除最新的存档 if number == -1: number = 1 while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): number += 1 number -= 1 if os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): os.remove(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4") print(self.savename + str(number) + ".eu4 " + "deleted") i = 1 while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number + i) + ".eu4"): os.rename( BACKUP_PATH + "\\" + self.savename + str(number + i) + ".eu4", BACKUP_PATH + "\\" + self.savename + str(number + i - 1) + ".eu4", ) print(self.savename + str(number + i) + ".eu4 " + "renamed to " + self.savename + str(number + i - 1) + ".eu4") i += 1 else: print(self.savename + number +".eu4 not found")
python
# C: clean clean saves with name savename def clean(self): for filename in os.listdir(BACKUP_PATH): if filename.startswith(self.savename): os.remove(BACKUP_PATH + "\\" + filename) print(filename + " deleted")
python
# I: info list all the saves in BACKUP_PATH def info(self): dict = {} for filename in os.listdir(BACKUP_PATH): if filename.endswith(".eu4") and not filename.endswith("_Backup.eu4"): name = "" for c in filename: if c == ".": name = "" break if c.isdigit(): break name += c if name != "": if name not in dict.keys(): dict[name] = 1 else: dict[name] += 1 for key in dict.keys(): print( key + ": " + str(dict[key]))
python
# H: help show the help message def help(self): print("S: SAVE_PATH.save -> BACKUP_PATH.save+no") print("L: SAVE_PATH.save -> SAVE_PATH.save_backup BACKUP_PATH.save+no -> SAVE_PATH.save") print("B: SAVE_PATH.save_backup -> SAVE_PATH.save") print("N + new_name: change the save name") print("D + number: delete the save with the selected number") print("C: clean saves with name savename") print("I: list all the saves in BACKUP_PATH") print("H: show the help message") print("Q: quit the program")

最后是main函数的编写,采用循环结构

python
if __name__ == "__main__": while True: print("Current save name: " + manager.savename) command = input("Enter a command: ").upper() if command == "S": manager.save() elif command == "L": manager.load() elif command == "B": manager.backup() elif command.startswith("N") and len(command) > 2: manager.name(command[2:]) elif command.startswith("D"): if len(command) > 2: manager.delete(int(command[2:])) else: manager.delete(-1) elif command == "C": manager.clean() elif command == "I": manager.info() elif command == "H": manager.help() elif command == "Q": break else: print("Invalid command. Enter 'H' for help.") print("\n")

如此一来,整个程序就完成啦。还可以加入一些ANSI控制符来引入更多丰富多彩的颜色来使界面更加美观。
然后再编写一个bat脚本来一键运行。
下面来看看具体成果:

Snipaste_2024-03-29_20-47-41.png

Snipaste_2024-03-29_20-45-40.png

Snipaste_2024-03-29_20-46-50.png

编写代码时不仅感慨道,现在有了人工智能的帮助,只需要好好构思就能很快地写出整洁的代码。而我经过了差不多两个多小时的编程后,不仅解决了备份存档繁杂的问题,还锻炼了编程能力,又能顺带着写出一篇合适的博客,真是一举三得呢。
果然,偷懒是人类进步最大的动力啊……

最后附上全部代码:

python
# SaveManager.py import os import shutil SAVE_PATH = ( r"C:\Users\34960\Documents\Paradox Interactive\Europa Universalis IV\save games" ) BACKUP_PATH = r"F:\备份\EU4\save" class SaveManager: def __init__(self): file = open("savename.txt", "r") self.savename = file.read().strip() file.close() # S: save SAVE_PATH.save -> BACKUP_PATH.save+no def save(self): number = 1 # number-1为备份目录内最大存档编号,下一个备份存档编号为number while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): number += 1 # 备份savename.eu4 if os.path.exists(SAVE_PATH + "\\" + self.savename + ".eu4"): shutil.copy(SAVE_PATH + "\\" + self.savename + ".eu4", BACKUP_PATH) os.rename( BACKUP_PATH + "\\" + self.savename + ".eu4", BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4", ) print("Save file copied and renamed to " + self.savename + str(number) + ".eu4") else: print("Save file not found") # 备份savename_Backup.eu4 if os.path.exists(SAVE_PATH + "\\" + self.savename + "_Backup.eu4"): shutil.copy(SAVE_PATH + "\\" + self.savename + "_Backup.eu4", BACKUP_PATH) print("Backup file copied") else: print("Backup file not found") # L: load SAVE_PATH.save -> SAVE_PATH.save_backup BACKUP_PATH.save+no -> SAVE_PATH.save def load(self): number = 1 while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): number += 1 number -= 1 # 需要加载的存档编号 if number == 0: print("Save file not found") return # save->save_backup if os.path.exists(SAVE_PATH + "\\" + self.savename + ".eu4"): if os.path.exists(SAVE_PATH + "\\" + self.savename + "_Backup.eu4"): os.remove(SAVE_PATH + "\\" + self.savename + "_Backup.eu4") os.rename(SAVE_PATH + "\\" + self.savename + ".eu4", SAVE_PATH + "\\" + self.savename + "_Backup.eu4") print("Save file renamed to " + self.savename + "_Backup.eu4") # 加载备份存档 file_path = BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4" shutil.copy(file_path, SAVE_PATH) os.rename( SAVE_PATH + "\\" + self.savename + str(number) + ".eu4", SAVE_PATH + "\\" + self.savename + ".eu4", ) print(self.savename + str(number) + ".eu4 " + "copied and renamed to " + self.savename + ".eu4") # B: backup SAVE_PATH.save_backup -> SAVE_PATH.save def backup(self): if os.path.exists(SAVE_PATH + "\\" + self.savename + "_Backup.eu4"): if os.path.exists(SAVE_PATH + "\\" + self.savename + ".eu4"): os.remove(SAVE_PATH + "\\" + self.savename + ".eu4") os.rename( SAVE_PATH + "\\" + self.savename + "_Backup.eu4", SAVE_PATH + "\\" + self.savename + ".eu4", ) print(self.savename + "_Backup.eu4 " + "renamed to " + self.savename + ".eu4") else: print("Backup file not found") # N: name change the save name def name(self, name): file = open("savename.txt", "w") file.write(name) file.close() self.savename = name # D: delete delete the save with the selected number def delete(self, number): # 默认删除最新的存档 if number == -1: number = 1 while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): number += 1 number -= 1 if os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4"): os.remove(BACKUP_PATH + "\\" + self.savename + str(number) + ".eu4") print(self.savename + str(number) + ".eu4 " + "deleted") i = 1 while os.path.exists(BACKUP_PATH + "\\" + self.savename + str(number + i) + ".eu4"): os.rename( BACKUP_PATH + "\\" + self.savename + str(number + i) + ".eu4", BACKUP_PATH + "\\" + self.savename + str(number + i - 1) + ".eu4", ) print(self.savename + str(number + i) + ".eu4 " + "renamed to " + self.savename + str(number + i - 1) + ".eu4") i += 1 else: print(self.savename + number +".eu4 not found") # C: clean clean saves with name savename def clean(self): for filename in os.listdir(BACKUP_PATH): if filename.startswith(self.savename): os.remove(BACKUP_PATH + "\\" + filename) print(filename + " deleted") # I: info list all the saves in BACKUP_PATH def info(self): dict = {} for filename in os.listdir(BACKUP_PATH): if filename.endswith(".eu4") and not filename.endswith("_Backup.eu4"): name = "" for c in filename: if c == ".": name = "" break if c.isdigit(): break name += c if name != "": if name not in dict.keys(): dict[name] = 1 else: dict[name] += 1 for key in dict.keys(): if key!=self.savename: print(ANSI_CYAN + key + ": " + str(dict[key]) + ANSI_MAGENTA) else: print(ANSI_YELLOW + key + ": " + str(dict[key]) + ANSI_MAGENTA) # H: help show the help message def help(self): print(ANSI_GREEN) print("S: SAVE_PATH.save -> BACKUP_PATH.save+no") print("L: SAVE_PATH.save -> SAVE_PATH.save_backup BACKUP_PATH.save+no -> SAVE_PATH.save") print("B: SAVE_PATH.save_backup -> SAVE_PATH.save") print("N + new_name: change the save name") print("D + number: delete the save with the selected number") print("C: clean saves with name savename") print("I: list all the saves in BACKUP_PATH") print("H: show the help message") print("Q: quit the program") print(ANSI_MAGENTA) if __name__ == "__main__": manager = SaveManager() ANSI_RED = '\033[91m' ANSI_GREEN = '\033[92m' ANSI_YELLOW = '\033[93m' ANSI_BLUE = '\033[94m' ANSI_MAGENTA = '\033[95m' ANSI_CYAN = '\033[96m' ANSI_RESET = '\033[0m' while True: print(ANSI_BLUE + "Current save name: " + ANSI_YELLOW + manager.savename + ANSI_MAGENTA) command = input(ANSI_BLUE + "Enter a command: " + ANSI_MAGENTA).upper() if command == "S": manager.save() elif command == "L": manager.load() elif command == "B": manager.backup() elif command.startswith("N") and len(command) > 2: manager.name(command[2:]) elif command.startswith("D"): if len(command) > 2: manager.delete(int(command[2:])) else: manager.delete(-1) elif command == "C": manager.clean() elif command == "I": manager.info() elif command == "H": manager.help() elif command == "Q": break else: print(ANSI_RED + "Invalid command. Enter 'H' for help." + ANSI_MAGENTA) print("\n")
bat
// SaveManager.bat @echo off python "SaveManager.py"