【Godot4.3】题目与答案解析合并器

免责申明
本文和工具截图中涉及题库和题目,均为本人自学使用,并未有商业和传播企图。如有侵害,联系删改。

概述

笔者本人医学专业从业人员,编程只是业余爱好。在自己的专业应考学习过程当中:

  • 有时候不太喜欢纸质题库的排版,比如题目与答案分开,甚至是分开在两本书上。
  • 想要翻录下来,转存到笔记软件中,作为自己积累的一个题库,而且方便随时查看、修改和扩充笔记。

这项工作,我从2024年头就开始实践,并且已经积累了一定的题目量。作为专业学习和考试的专用复习资料。

翻录操作

我的具体做法是:

  • 摊开书,翻页,手机拍照后转存PDF
  • 在WPS中进行扫描件识别,获取文本
  • 在VSCode中进行批量替换和初步处理
  • 复制到笔记软件(我用的是语雀),进行更详细的优化排版
  • 学习和补充笔记,手机随时查看记忆
  • 选择性打印为册子,方便查看

翻录环节一直是很顺畅和熟练的。但是,后续处理有时候比较麻烦。尤其是很多纸质题库是将题目和参考答案以及解析分开的。手动排版是个海量的工作。因此,需要编程实现题目与答案的合并。

之前的Python版本

2024年11月11日创建的Python版本,其原理是:

  • 把题目和答案都预处理为---分隔的形式
  • 各自拆分为字符串数组,检测题目总数和答案总数一致后,进行题目和答案解析的合并
# 读取文件内容
def read_file(path):
    with open(path, 'r',encoding="utf-8") as file:
        content = file.read()
    return content

# 读取文件内容
def save_file(path,content):
    with open(path, 'w',encoding="utf-8") as file:
        content = file.write(content)

# 获取题目和答案合并的结果
def get_result(qus_path,ans_path):
    result = ""

    qus = read_file(qus_path) # 问题
    ans = read_file(ans_path) # 答案

    # 用 --- 划分
    qus_arr = qus.split("---")
    ans_arr = ans.split("---")

    if len(qus_arr) == len(ans_arr): # 答案和题目数量一致
        # 遍历生成合并字符串
        for i in range(len(qus_arr)):
            q = qus_arr[i]
            a = ans_arr[i]
            result += f"""{q}
:::tips
{a}
:::"""
    return result


save_file("result.md",get_result("qus.txt","ans.txt"))

这个版本的坏处的,必须依靠VSCode和工作目录,并且需要将题目和答案分别存储到固定名称的文件中。

而字符串处理的大部分操作还是需要手动在VSCode中进行。

创建Godot版本

为了更方便快捷的合并,编写了一个简单的Godot应用。

  • 用Godot创建合并器,以复制粘贴文本形式,而不是文件形式,处理起来会更灵活。
  • 而且代码和处理的灵活性也会进一步上升

初步界面设计

初期界面规划

初步实现

extends HSplitContainer
@onready var qus_txt: TextEdit = %qusTxt
@onready var ans_txt: TextEdit = %ansTxt
@onready var result_txt: TextEdit = %resultTxt

# ================================= 按钮点击 =================================
# 整体添加分隔线
func _on_splits_btn_pressed() -> void:
	qus_txt.text = add_splits(qus_txt.text)
	ans_txt.text = add_splits(ans_txt.text)

# 执行合并
func _on_join_btn_pressed() -> void:
	result_txt.text = do_joins_result(qus_txt.text,ans_txt.text)
	pass

# ================================= 自定义方法 =================================
# 查找题目或答案序号,并添加 --- 分隔符
func add_splits(old_str:String) -> String:
	var regex = RegEx.new()
	regex.compile(r"\n\d\.")
	for result in regex.search_all(old_str):
		var res = result.get_string()
		old_str = old_str.replace(res,"\n --- %s" % res)
	return old_str

# 执行合并
func do_joins_result(qus_str:String,ans_str:String) -> String:
	var result:PackedStringArray = []
	var qus_arr = qus_str.split("---",false)
	var ans_arr = ans_str.split("---",false)
	if qus_arr.size() == ans_arr.size(): # 题目数量与答案解析数量一致
		# 遍历生成合并字符串
		for i in range(qus_arr.size()):
			var qus = qus_arr[i]
			var ans = ans_arr[i]
			result.append(qus_ans_str(qus,ans))
	return "\n".join(result)
	
# 根据模版字符串生成题目和答案解析的合并字符串
func qus_ans_str(qus:String,ans:String) -> String:
	var tmp = """%s
:::tips
%s
:::""" % [qus,ans]
	return tmp

已经实现基础的题目与答案解析合并功能,复制到语雀自动识别后,也是可以正常显示:

编写处理类

上面将处理逻辑编写为三个函数,混杂在主场景中,显得并不优秀。

好的办法是进一步编写类,来固定处理相似的逻辑。这样也就让逻辑和UI部分分离了。

新类设想

  • 新的类将自动处理输入的普通的题目和答案解析字符串,并维护题目和答案两个数组
  • 将可以编写简单的题库系统,顺序查看题目和答案

新类实现

# =========================================================
# 名称:QusAnsList
# 类型:GDSCript类
# 描述:解析和处理普通的题目和答案解析字符串,生成合并字符串的处理类
# Godot版本:v4.3.stable.steam [77dcf97d8]
# 创建时间:202521213:08:13
# 最后修改时间:202521221:38:46
# =========================================================
class_name QusAnsList
# 切分后的题目与答案集合
var qus_arr:PackedStringArray
var ans_arr:PackedStringArray

# 原始字符串
var qus_str:String
var ans_str:String
# 添加分隔符后的字符串
var split_qus_str:String
var split_ans_str:String

# 创建实例
func _init(qus_str:String,ans_str:String) -> void:
	self.qus_str = inline_opts(qus_str)
	self.ans_str = ans_str
	# 添加分隔线
	self.split_qus_str = add_lines(self.qus_str,true)
	self.split_ans_str = add_lines(ans_str)
	# 分割添加了分隔线的字符串,获得题目与答案集合
	splits()

# ================================ 方法 ================================

# 将ABCDE选项放置到一行
func inline_opts(old_str:String) -> String:
	var new_str = old_str
	for opt in ["B","C","D","E"]:
		new_str = new_str.replace("\n%s." % opt,"%s%s." % ["\t".repeat(2),opt])
	return new_str

# 查找题目或答案序号,并添加 --- 分隔符
# old_str 要添加分隔符的原始字符串
# ignore_first 是否忽略第一项,用于题目序号不是在第一行的情况
func add_lines(old_str:String,ignore_first:=false) -> String:
	# 创建对序号的正则匹配
	var regex = RegEx.new()
	regex.compile(r"\n\d+\.")
	# 遍历所有搜索到的序号,替换为 --- 
	var results = regex.search_all(old_str)
	for i in range(results.size()):
		if ignore_first and i==0: # 忽略第一次
			continue
		var res = results[i].get_string()
		old_str = old_str.replace(res,"\n---%s" % res)
	return old_str

# 分割添加了分隔线的字符串获得题目与答案集合
func splits() -> void:
	qus_arr = split_qus_str.split("\n---\n",false)
	ans_arr = split_ans_str.split("\n---\n",false)

# 检测题目与答案集合的数目是否相等
func is_qus_equal_ans_count() -> bool:
	return qus_arr.size() == ans_arr.size()

# 获取执行合并的字符串
func get_joins_result() -> String:
	var result:PackedStringArray = []
	if qus_arr.size() == ans_arr.size(): # 题目数量与答案解析数量一致
		# 遍历生成合并字符串
		for i in range(qus_arr.size()):
			var qus = qus_arr[i]
			var ans = ans_arr[i]
			result.append(qus_ans_str(qus,ans))
	return "\n".join(result)
	
# 根据模版字符串生成题目和答案解析的合并字符串
func qus_ans_str(qus:String,ans:String) -> String:
	var tmp = """%s
:::tips
%s
:::""" % [qus,ans]
	return tmp

基于新类,主场景的代码简化如下:

extends HSplitContainer

var QAlist:QusAnsList
var current_index:int = 0

@onready var qus_txt: TextEdit = %qusTxt
@onready var ans_txt: TextEdit = %ansTxt
@onready var result_txt: TextEdit = %resultTxt
@onready var tk_qus_txt: CodeEdit = %tkQusTxt
@onready var tk_ans_txt: CodeEdit = %tkAnsTxt

# ================================= 按钮点击 =================================


# 执行合并
func _on_join_btn_pressed() -> void:
	QAlist = QusAnsList.new(qus_txt.text,ans_txt.text)
	result_txt.text = QAlist.get_joins_result()
	# 题库显示第一题
	show_tk_qus_ans(0)
	
# 题库显示指定的第几题
func show_tk_qus_ans(index:int) -> void:
	tk_qus_txt.text = QAlist.qus_arr[index]
	tk_ans_txt.text = QAlist.ans_arr[index]

# 显示上一题
func _on_last_btn_pressed() -> void:
	current_index = clamp(current_index-1,0,QAlist.qus_arr.size()-1)
	show_tk_qus_ans(current_index)
	

# 显示下一题
func _on_next_btn_pressed() -> void:
	current_index = clamp(current_index+1,0,QAlist.qus_arr.size()-1)
	show_tk_qus_ans(current_index)

程序效果

一键处理和合并题目与答案

  • 生成合并字符串并进行展示,可以复制到语雀中自动识别和排版

生成题库预览

  • 因为新类内部维护两个数组,存储题目和答案
  • 所以可以搭建简易的题库预览界面,来按顺序查看每一道题目

后续改进

界面分开

  • 将界面分为合并器和题库预览两部分,通过顶栏的按钮进行界面的切换

分开后的合并器主界面

分开后的题库预览界面,字号做了相应放大

题号列表

  • 只通过上一题,下一题查看题库并不方便,可以生成题号按钮,点击快速查看。

修改题库预览界面样式

  • 两边留白,让视野宽度略小于屏幕宽度,中心聚焦且看的不是太累。

刷题计时器

  • 设计一个简单的刷题计时器,可以重置、开始、暂停,用以记录刷题时间
  • 首先是编写一个函数获取累计时间的HH:MM:SS形式字符串
# 返回累计时间的时分秒的字符串形式 HH:MM:SS
func get_time_str(time:int) -> String:
	var dic = Time.get_time_dict_from_unix_time(time)
	return "%02d:%02d:%02d" % [dic["hour"],dic["minute"],dic["second"]]
# 返回累计时间的时分秒的字符串形式 HH小时MMSS秒
func get_time_str(time:int) -> String:
	var dic = Time.get_time_dict_from_unix_time(time)
	return "%02d小时%02d分%02d秒" % [dic["hour"],dic["minute"],dic["second"]]
  • 接着就是使用和控制Timer节点显示累计时间

UI美化

加载和保存题库

  • 题库文件是题目和答案用特殊解析字符串#================;所连接的纯文本文件

总结

  • 至此,意为着利用纯文本形式的题目和答案字符串,快速合并获得适用于语雀显示的格式将变得异常方便
  • 同时,可以以非常简便的方式创建刷题系统。只需要维护和保存一堆.txt文件即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

巽星石

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

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

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

打赏作者

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

抵扣说明:

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

余额充值