<think>
我们计划创建一个包含两个主要窗口的应用程序:信息输入窗口和任务管理窗口。
1. 信息输入窗口:用于创建新的采集任务,包括任务名称、资源类型、采集数量、开始时间等。
2. 自动脚本任务界面:显示任务列表,每个任务有开始、暂停、删除按钮,以及进度条和状态显示。
我们将使用PySimpleGUI作为GUI库,并考虑以下功能:
- 任务生成:通过信息输入窗口生成任务,并保存为文件(如JSON格式)。
- 任务导入:从文件中导入任务。
- 任务状态:包括“等待中”、“运行中”、“暂停”、“完成”等。
- 任务排序和排队:按照开始时间或优先级排序,依次执行(这里我们按开始时间排序,并采用队列方式执行,但注意同时只能执行一个任务,其他任务排队等待)。
- 任务执行:使用多线程执行任务,避免阻塞GUI。每个任务执行时更新进度条和状态。
- 异常处理:捕获任务执行过程中的异常,并更新任务状态。
设计思路:
1. 使用一个列表来存储任务(在内存中),同时定期保存到文件。
2. 任务执行器:一个后台线程,从任务队列中取出任务执行(按开始时间排序,且状态为“等待中”的任务,到达开始时间后执行)。
3. 每个任务是一个字典或对象,包含:任务ID、任务名称、资源类型、采集数量、开始时间、状态、进度、线程控制标志(如暂停事件)等。
注意:由于GUI和多线程的交互,需要注意线程安全。
我们将创建以下主要部分:
- GUI布局:两个窗口的布局。
- 任务数据结构:定义任务字典的结构。
- 文件操作:保存任务列表到文件,从文件导入。
- 多线程任务执行:使用threading模块,每个任务在单独的线程中运行,但任务执行器线程负责管理任务启动(排队),而每个任务运行在自己的线程中,这样我们可以同时运行多个任务?或者按照队列一个一个执行?根据需求,我们这里按照排队执行,即同一时间只有一个任务在执行。所以任务执行器每次只取一个任务,执行完再取下一个。但注意,如果有任务设置了开始时间,那么需要等待开始时间到达。
但是,我们允许任务在等待时,其他任务也可以被安排(但执行只有一个)。因此,我们可以用一个工作线程来执行当前任务,而其他任务在队列中等待。当当前任务完成或暂停,才启动下一个。
实际上,我们设计一个任务队列(按开始时间排序),然后有一个工作线程循环检查队列,取出第一个可执行任务(开始时间已到且状态为等待)执行,执行过程中,根据暂停事件来暂停,根据停止事件来停止。
然而,为了简化,我们可以这样:每个任务有自己的线程,但通过一个信号量控制同时只有一个任务在执行(资源采集可能不允许并发,比如游戏限制)。或者我们采用单线程执行所有任务,即一个工作线程按顺序执行任务。这里我们选择单线程顺序执行。
步骤:
1. 创建一个任务执行器线程,它循环检查任务列表,找出状态为“等待中”且开始时间小于等于当前时间的任务,将其状态改为“运行中”,然后执行该任务(模拟采集过程,使用循环和延时,并检查暂停事件)。
2. 在任务执行过程中,可以暂停(通过一个事件对象),也可以被删除(需要终止线程,但终止线程不安全,所以我们采用协作式停止,即设置一个标志)。
由于我们采用顺序执行,同一时间只有一个任务在执行,所以任务执行器线程每次只处理一个任务。当任务完成或出错后,再处理下一个。
但注意,我们允许在任务运行过程中暂停和删除当前任务。删除任务时,如果任务正在运行,我们需要终止它。然而,安全地终止一个线程在Python中并不简单。因此,我们采用在每个任务执行函数中定期检查停止标志的方式。
因此,每个任务可以有以下控制事件:
- pause_event: 用于暂停和继续(set()表示暂停,clear()表示继续)
- stop_event: 用于停止任务(当需要删除任务时,设置stop_event)
任务执行函数(模拟任务):
while 进度 < 100:
检查pause_event是否被设置,如果是,则等待直到被清除(或者等待一段时间再检查)
检查stop_event是否被设置,如果是,则终止循环,任务状态设为“已取消”
否则,执行一步,更新进度
但是,任务执行器线程在执行任务时,会阻塞直到任务完成。这样我们就无法同时更新GUI和响应其他事件。所以我们需要将每个任务放在单独的线程中执行,而任务执行器线程只负责启动任务线程,然后等待任务线程结束(或暂停、停止等操作由任务线程自己处理)?这样任务执行器线程启动一个任务线程后,就可以继续去检查下一个任务(但我们要求同一时间只有一个任务运行,所以任务执行器线程在启动一个任务后,需要等待该任务线程结束才启动下一个)。
因此,任务执行器线程可以这样:
while True:
排序任务列表,找到下一个应该运行的任务(状态为等待,开始时间<=当前时间)
如果没有,休眠一段时间
如果有,启动该任务的任务线程(并标记为运行中),然后等待该线程结束(join)或等待其完成(但这样会阻塞,无法处理新任务添加?)
为了解决这个问题,我们可以让任务执行器线程只负责启动任务线程,然后立即去检查下一个任务?但这样会导致同时运行多个任务。所以我们需要一个锁来保证同时只有一个任务在运行。
因此,我们使用一个全局锁(例如:execution_lock),任务执行器线程在启动任务线程前获取锁,启动后释放锁?不行,因为启动后任务线程就运行了,我们希望任务执行器线程等待任务线程结束。所以这样:
while True:
获取锁(保证只有一个任务执行器在工作?或者我们只有一个执行器线程,所以不需要锁?)
然后检查任务,启动任务线程,然后等待任务线程结束(这样执行器线程就被占用了)。这样不好,因为无法在任务运行过程中添加新任务并立即排序。
另一种方案:我们不用任务执行器线程,而是当任务被添加到列表或任务状态变化时,主动尝试启动任务。启动任务的条件是:当前没有任务运行,且任务列表中有任务满足运行条件(开始时间已到,状态为等待)。然后启动该任务(在一个单独的线程中运行),并设置一个标志表示当前有任务在运行。当任务完成时,重置标志,并再次检查任务列表。
这样,我们可以通过事件(如任务添加、任务完成)来触发任务启动。
考虑到复杂度,我们采用一个简单的方案:有一个专门的线程(任务执行器)循环执行以下步骤:
1. 获取任务队列(按开始时间排序)中第一个状态为“等待中”且开始时间<=当前时间的任务。
2. 如果没有,休眠1秒。
3. 如果有,则将该任务的状态设为“运行中”,并在这个线程中执行任务(所以任务执行是顺序的,且在这个线程中执行,所以不会同时执行多个任务)。
4. 执行任务过程中,定期检查暂停事件和停止事件(这个事件由GUI操作设置)。
但是,这样任务执行器线程在执行任务时就会阻塞,直到任务完成。这样我们在执行任务的过程中就无法暂停(因为执行任务的循环在任务执行器线程中,我们可以检查事件,但暂停事件是由GUI设置的,所以可以)。删除任务操作:如果删除的是当前正在执行的任务,那么我们需要停止当前任务。因此,我们在任务执行循环中检查停止标志(针对当前任务)。
然而,这样设计意味着任务执行器线程在执行一个任务时,其他任务只能等待,直到当前任务完成(或被删除)才会检查下一个任务。这符合排队执行的要求。
具体步骤:
任务执行器线程的主循环:
while True:
current_task = None
# 查找可运行任务
for task in sorted(tasks, key=lambda x: x['start_time']):
if task['status'] == '等待中' and task['start_time'] <= datetime.now():
current_task = task
break
if current_task is None:
time.sleep(1)
continue
current_task['status'] = '运行中'
# 更新GUI(通过设置一个GUI更新事件或使用窗口写操作,注意线程安全)
# 执行任务
progress = 0
current_task['progress'] = progress
# 模拟任务执行,每一步都检查暂停和停止
current_task['pause_event'] = threading.Event() # 暂停事件,如果设置则表示暂停
current_task['stop_event'] = threading.Event() # 停止事件,如果设置则表示停止
try:
while progress < 100:
# 检查停止事件
if current_task['stop_event'].is_set():
current_task['status'] = '已取消'
progress = 0
break
# 检查暂停事件
if current_task['pause_event'].is_set():
# 等待暂停事件被清除
time.sleep(0.1)
continue
# 执行一步
time.sleep(0.1) # 模拟工作
progress += 1
current_task['progress'] = progress
# 更新GUI中的进度条(注意:跨线程更新GUI,需要使用window.write_event_value)
# 例如:window.write_event_value('更新进度', (task_id, progress))
# 循环正常结束,说明任务完成
if progress >= 100:
current_task['status'] = '完成'
except Exception as e:
current_task['status'] = f'错误: {str(e)}'
finally:
# 清理,将当前任务从运行中移除,然后循环会继续下一个
# 注意:我们需要重置事件吗?可以,因为下次运行前会重新创建事件
# 但是,任务执行器线程会继续循环,所以可以继续下一个任务
pass
但是,这个任务执行器线程在执行任务时,如何响应暂停和停止呢?暂停和停止是由GUI按钮触发的,所以我们需要在任务字典中存储每个任务的控制事件(pause_event和stop_event)。当用户点击暂停按钮时,设置该任务的pause_event;当点击继续,清除pause_event;当点击删除,设置stop_event。
因此,在任务执行器线程执行某个任务期间,它会检查这个任务的事件。而事件是在GUI线程中设置的(安全,因为事件是线程安全的)。
然而,这样设计后,任务执行器线程在执行一个任务时会阻塞,无法处理其他任务,直到当前任务完成。这符合顺序执行的预期。
但是,我们如何更新GUI?比如进度条。我们可以在任务执行循环中定期发送事件给GUI更新界面。PySimpleGUI支持从其他线程发送事件:使用window.write_event_value(event, value)。
因此,我们定义一个事件,例如'UPDATE_PROGRESS',然后发送任务ID和当前进度。GUI线程收到事件后,更新对应任务的进度条。
另外,任务状态变化(如开始运行、完成)也需要更新GUI。同样,我们可以在状态变化时发送事件。
考虑到代码结构,我们设计:
- 任务执行器线程只负责执行任务,并在执行过程中更新任务的状态和进度(在任务字典中),然后发送事件通知GUI更新。
- GUI线程负责处理用户操作(开始、暂停、删除)和更新界面。
由于任务执行器线程会长时间运行,我们需要在程序退出时停止它。
现在,我们开始编写代码,步骤如下:
1. 导入必要的库。
2. 定义任务数据结构:每个任务是一个字典,包含字段:task_id, name, resource_type, amount, start_time, status, progress, pause_event, stop_event。
3. 创建GUI布局:两个窗口,先显示主窗口(任务管理窗口),通过按钮打开信息输入窗口。
4. 实现任务管理窗口,包括任务列表(表格或列表)、每个任务的操作按钮(开始、暂停、删除)和进度条。
5. 实现信息输入窗口,用于添加新任务。
6. 文件操作:保存任务列表到JSON文件,从JSON文件导入。
7. 任务执行器线程(后台线程)的实现。
8. 事件循环:处理GUI事件和任务执行器线程发送的事件。
注意:暂停和继续按钮其实是同一个按钮的两种状态(暂停/继续),所以我们可以用一个按钮,根据状态改变文本。
由于代码较长,我将分步骤实现,并尽量保持结构清晰。
我们开始写代码:
</think>
以下是完整的Python + PySimpleGUI实现的自动采集资源游戏脚本代码,包含信息输入模块和任务管理界面:
```python
import PySimpleGUI as sg
import json
import time
import os
import threading
from datetime import datetime, timedelta
from queue import Queue
# 任务数据结构
class ResourceTask:
def __init__(self, name, resource_type, amount, start_time=None):
self.id = int(time.time() * 1000) # 唯一ID
self.name = name
self.resource_type = resource_type
self.amount = amount
self.progress = 0
self.status = "等待中" # 等待中/运行中/已完成/已暂停
self.start_time = start_time or datetime.now()
self.actual_start = None
self.completion_time = None
self.thread = None
self.stop_event = threading.Event()
self.pause_event = threading.Event()
# 任务管理器
class TaskManager:
def __init__(self):
self.tasks = []
self.current_task = None
self.task_queue = Queue()
self.task_thread = threading.Thread(target=self._process_queue, daemon=True)
self.task_thread.start()
def add_task(self, task):
self.tasks.append(task)
self.task_queue.put(task)
self.sort_tasks()
return task
def sort_tasks(self):
self.tasks.sort(key=lambda x: x.start_time)
def get_task_by_id(self, task_id):
for task in self.tasks:
if task.id == task_id:
return task
return None
def start_task(self, task_id):
task = self.get_task_by_id(task_id)
if task and task.status == "等待中":
task.status = "运行中"
task.actual_start = datetime.now()
task.thread = threading.Thread(target=self._execute_task, args=(task,), daemon=True)
task.thread.start()
return True
return False
def pause_task(self, task_id):
task = self.get_task_by_id(task_id)
if task and task.status == "运行中":
task.status = "已暂停"
task.pause_event.set()
return True
return False
def resume_task(self, task_id):
task = self.get_task_by_id(task_id)
if task and task.status == "已暂停":
task.status = "运行中"
task.pause_event.clear()
return True
return False
def delete_task(self, task_id):
task = self.get_task_by_id(task_id)
if task:
if task.status == "运行中":
task.stop_event.set()
self.tasks = [t for t in self.tasks if t.id != task_id]
return True
return False
def _process_queue(self):
while True:
if not self.task_queue.empty() and self.current_task is None:
task = self.task_queue.get()
if task.status == "等待中":
self.start_task(task.id)
time.sleep(1)
def _execute_task(self, task):
try:
for i in range(1, 101):
if task.stop_event.is_set():
task.status = "已取消"
break
while task.pause_event.is_set():
time.sleep(0.5)
task.progress = i
time.sleep(0.1) # 模拟采集过程
task.status = "已完成" if task.progress == 100 else "已取消"
task.completion_time = datetime.now()
except Exception as e:
task.status = f"错误: {str(e)}"
finally:
self.current_task = None
# GUI布局
def create_input_window():
layout = [
[sg.Text("任务名称:"), sg.Input(key='-TASK_NAME-')],
[sg.Text("资源类型:"), sg.Combo(['金币', '木材', '矿石', '食物'], key='-RESOURCE_TYPE-')],
[sg.Text("采集数量:"), sg.Spin([i for i in range(1, 1001)], initial_value=100, key='-AMOUNT-')],
[sg.Text("开始时间:"),
sg.Input(key='-START_DATE-', size=(10, 1)),
sg.CalendarButton("选择日期", target='-START_DATE-', format='%Y-%m-%d'),
sg.Input(key='-START_TIME-', size=(8, 1), default_text=datetime.now().strftime("%H:%M:%S"))],
[sg.Button("添加任务", key='-ADD_TASK-'), sg.Button("导入任务", key='-IMPORT_TASK-')]
]
return sg.Window("任务信息输入", layout, finalize=True)
def create_task_window():
task_layout = [
[sg.Table(values=[],
headings=['ID', '任务名称', '资源类型', '数量', '状态', '进度', '开始时间'],
key='-TASK_TABLE-',
auto_size_columns=False,
col_widths=[10, 15, 10, 8, 10, 15, 20],
justification='left',
enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True, expand_y=True)],
[sg.ProgressBar(100, orientation='h', size=(40, 20), key='-PROGRESS_BAR-', expand_x=True)],
[sg.Button("开始", key='-START-'),
sg.Button("暂停", key='-PAUSE-'),
sg.Button("继续", key='-RESUME-'),
sg.Button("删除", key='-DELETE-'),
sg.Button("保存任务", key='-SAVE_TASK-')]
]
return sg.Window("自动脚本任务管理", task_layout, finalize=True, size=(800, 600))
# 主程序
def main():
task_manager = TaskManager()
input_window = create_input_window()
task_window = create_task_window()
current_window = input_window
# 初始化任务表格
def update_task_table():
task_data = []
for task in task_manager.tasks:
task_data.append([
task.id,
task.name,
task.resource_type,
task.amount,
task.status,
f"{task.progress}%",
task.start_time.strftime("%Y-%m-%d %H:%M:%S")
])
task_window['-TASK_TABLE-'].update(values=task_data)
# 保存任务到文件
def save_tasks():
try:
task_data = []
for task in task_manager.tasks:
task_data.append({
'id': task.id,
'name': task.name,
'resource_type': task.resource_type,
'amount': task.amount,
'start_time': task.start_time.strftime("%Y-%m-%d %H:%M:%S")
})
with open('tasks.json', 'w') as f:
json.dump(task_data, f, indent=4)
sg.popup("任务保存成功!", title="保存成功")
return True
except Exception as e:
sg.popup(f"保存失败: {str(e)}", title="错误")
return False
# 从文件导入任务
def load_tasks():
try:
if os.path.exists('tasks.json'):
with open('tasks.json', 'r') as f:
task_data = json.load(f)
for task in task_data:
new_task = ResourceTask(
name=task['name'],
resource_type=task['resource_type'],
amount=task['amount'],
start_time=datetime.strptime(task['start_time'], "%Y-%m-%d %H:%M:%S")
)
new_task.id = task['id']
task_manager.add_task(new_task)
sg.popup(f"成功导入 {len(task_data)} 个任务", title="导入成功")
update_task_table()
return True
except Exception as e:
sg.popup(f"导入失败: {str(e)}", title="错误")
return False
# 事件循环
while True:
window, event, values = sg.read_all_windows(timeout=100)
if event == sg.WINDOW_CLOSED:
if window == input_window:
input_window.hide()
task_window.un_hide()
elif window == task_window:
break
continue
# 信息输入窗口事件
if window == input_window:
if event == '-ADD_TASK-':
try:
start_time = datetime.strptime(
f"{values['-START_DATE-']} {values['-START_TIME-']}",
"%Y-%m-%d %H:%M:%S"
)
new_task = ResourceTask(
name=values['-TASK_NAME-'],
resource_type=values['-RESOURCE_TYPE-'],
amount=int(values['-AMOUNT-']),
start_time=start_time
)
task_manager.add_task(new_task)
sg.popup("任务添加成功!", title="成功")
update_task_table()
input_window.hide()
task_window.un_hide()
except Exception as e:
sg.popup(f"任务添加失败: {str(e)}", title="错误")
elif event == '-IMPORT_TASK-':
load_tasks()
# 任务管理窗口事件
elif window == task_window:
if event == '-TASK_TABLE-':
if values['-TASK_TABLE-']:
selected_index = values['-TASK_TABLE-'][0]
selected_task = task_manager.tasks[selected_index]
task_window['-PROGRESS_BAR-'].update(selected_task.progress)
elif event == '-START-':
if values['-TASK_TABLE-']:
selected_index = values['-TASK_TABLE-'][0]
task_id = task_manager.tasks[selected_index].id
if task_manager.start_task(task_id):
update_task_table()
elif event == '-PAUSE-':
if values['-TASK_TABLE-']:
selected_index = values['-TASK_TABLE-'][0]
task_id = task_manager.tasks[selected_index].id
if task_manager.pause_task(task_id):
update_task_table()
elif event == '-RESUME-':
if values['-TASK_TABLE-']:
selected_index = values['-TASK_TABLE-'][0]
task_id = task_manager.tasks[selected_index].id
if task_manager.resume_task(task_id):
update_task_table()
elif event == '-DELETE-':
if values['-TASK_TABLE-']:
selected_index = values['-TASK_TABLE-'][0]
task_id = task_manager.tasks[selected_index].id
if task_manager.delete_task(task_id):
update_task_table()
elif event == '-SAVE_TASK-':
save_tasks()
# 定期更新进度
update_task_table()
input_window.close()
task_window.close()
if __name__ == "__main__":
sg.theme('DarkTeal9')
main()
```
### 功能说明
1. **信息输入模块**:
- 创建新任务:输入任务名称、资源类型、采集数量和开始时间
- 导入任务:从JSON文件加载保存的任务
- 数据验证:确保所有字段正确填写
2. **任务管理界面**:
- 任务表格:显示所有任务的详细信息(ID、名称、类型、数量、状态、进度、开始时间)
- 进度条:可视化显示当前任务的执行进度
- 操作按钮:
- 开始:启动选中的任务
- 暂停:暂停运行中的任务
- 继续:恢复已暂停的任务
- 删除:移除选中的任务
- 保存:将所有任务保存到文件
3. **核心功能实现**:
- 任务队列管理:使用Queue实现任务排队
- 多线程执行:每个任务在独立线程中运行
- 状态管理:等待中/运行中/已暂停/已完成/已取消
- 时间处理:支持定时启动任务
- 异常处理:任务执行错误处理
- 数据持久化:JSON格式保存/加载任务
4. **使用说明**:
- 运行程序后首先打开任务输入窗口
- 添加任务后自动切换到任务管理界面
- 支持任务排序(按开始时间排序)
- 使用日历控件选择开始时间
- 任务执行进度实时更新
### 技术特点
1. **多线程处理**:
```python
task.thread = threading.Thread(target=self._execute_task, args=(task,), daemon=True)
```
每个任务在独立线程中执行,避免阻塞主界面
2. **任务调度算法**:
```python
def _process_queue(self):
while True:
if not self.task_queue.empty() and self.current_task is None:
task = self.task_queue.get()
if task.status == "等待中":
self.start_task(task.id)
time.sleep(1)
```
3. **异常处理机制**:
```python
try:
# 任务执行代码
except Exception as e:
task.status = f"错误: {str(e)}"
```
4. **数据持久化**:
```python
with open('tasks.json', 'w') as f:
json.dump(task_data, f, indent=4)
```
5. **实时进度更新**:
```python
def _execute_task(self, task):
for i in range(1, 101):
task.progress = i
time.sleep(0.1) # 模拟采集过程
```
### 扩展建议
1. 添加资源类型配置系统
2. 实现任务优先级设置
3. 增加任务执行日志记录
4. 添加邮件/通知功能
5. 集成游戏API实现真实采集