441 lines
12 KiB
C#
441 lines
12 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Xml;
|
||
using TMPro;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using static System.Collections.Specialized.BitVector32;
|
||
|
||
public class QuizSystem : MonoBehaviour
|
||
{
|
||
public static QuizSystem Instance;
|
||
|
||
public string FileName;
|
||
// UI元素
|
||
public TextMeshProUGUI questionText; // 显示问题文本
|
||
public ToggleGroup optionsGroup; // 选项组,用于单选题
|
||
public List<Toggle> optionToggles; // 选项切换按钮列表
|
||
public TextMeshProUGUI progressText; // 显示答题进度
|
||
public Button submitButton; // 提交按钮
|
||
//上一题下一题页面
|
||
public GameObject CtrlPage;
|
||
public Button nextButton; // 下一题按钮
|
||
//public Button previousButton; // 上一题按钮
|
||
public TextMeshProUGUI hintText; // 显示提示文本
|
||
//结果页面
|
||
public GameObject ResultPage;//结果页面
|
||
public TextMeshProUGUI resultText; // 显示结果文本
|
||
public Button MoreOnceBtn;//再来一次
|
||
|
||
//public Button hintButton; // 提示按钮
|
||
//public Slider timeSlider; // 时间滑动条
|
||
//public TextMeshProUGUI timerText; // 倒计时文本
|
||
|
||
// 问题列表和当前状态
|
||
private List<Question> questions = new List<Question>(); // 问题列表
|
||
private int currentQuestionIndex = 0; // 当前问题索引
|
||
private List<HashSet<int>> userAnswers = new List<HashSet<int>>(); // 用户答案列表
|
||
//private bool isSubmitted = false; // 是否已提交
|
||
|
||
// 总体倒计时
|
||
//private float totalTime = 1800f; // 总时间(秒)
|
||
//private float remainingTime; // 剩余时间
|
||
|
||
private void Awake()
|
||
{
|
||
Instance = this;
|
||
}
|
||
|
||
void Start()
|
||
{
|
||
// 加载问题
|
||
LoadQuestions();
|
||
|
||
ToStartQuestion();
|
||
|
||
// 初始化倒计时
|
||
//remainingTime = totalTime;
|
||
//UpdateTimerUI();
|
||
}
|
||
|
||
public void ToStartQuestion()
|
||
{
|
||
//结果和控制页面消失
|
||
ResultPage.SetActive(false);
|
||
CtrlPage.SetActive(false);
|
||
|
||
userAnswers.Clear();
|
||
|
||
// 初始化用户答案列表
|
||
for (int i = 0; i < questions.Count; i++)
|
||
{
|
||
userAnswers.Add(new HashSet<int>());
|
||
}
|
||
|
||
currentQuestionIndex = 0;
|
||
// 显示第一个问题
|
||
DisplayQuestion();
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
//// 更新倒计时
|
||
//if (remainingTime > 0)
|
||
//{
|
||
// remainingTime -= Time.deltaTime;
|
||
// UpdateTimerUI();
|
||
//}
|
||
//else if (!isSubmitted)
|
||
//{
|
||
// // 时间到,自动提交
|
||
// OnSubmitButtonClicked();
|
||
//}
|
||
}
|
||
|
||
void LoadQuestions()
|
||
{
|
||
questions = XmlTextReaderTest();
|
||
}
|
||
|
||
void DisplayQuestion()
|
||
{
|
||
// 检查是否超出问题列表
|
||
if (currentQuestionIndex >= questions.Count)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 获取当前问题
|
||
Question question = questions[currentQuestionIndex];
|
||
questionText.text = question.Text;
|
||
progressText.text = $"答题进度: {GetAnsweredCount()+1} / {questions.Count}";
|
||
|
||
// 显示选项
|
||
for (int i = 0; i < optionToggles.Count; i++)
|
||
{
|
||
if (i < question.Options.Length && (!question.IsJudgment || i < 2))
|
||
{
|
||
optionToggles[i].gameObject.SetActive(true);
|
||
var label = optionToggles[i].GetComponentInChildren<Text>();
|
||
if (label != null)
|
||
{
|
||
string sign = "A:";
|
||
switch (i)
|
||
{
|
||
case 0: sign = "A:"; break;
|
||
case 1: sign = "B:"; break;
|
||
case 2: sign = "C:"; break;
|
||
case 3: sign = "D:"; break;
|
||
}
|
||
label.text = sign+question.Options[i];
|
||
}
|
||
optionToggles[i].isOn = userAnswers[currentQuestionIndex].Contains(i + 1);
|
||
|
||
// 判断题或单选题使用 ToggleGroup
|
||
optionToggles[i].group = (question.IsSingleChoice || question.IsJudgment) ? optionsGroup : null;
|
||
}
|
||
else
|
||
{
|
||
optionToggles[i].gameObject.SetActive(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
//选择结束后确认该题结果
|
||
public void OnSubmitButtonClicked()
|
||
{
|
||
// 提交答案
|
||
SaveUserAnswers();
|
||
//检查结果并显示
|
||
CheckCurrentAnswer();
|
||
}
|
||
|
||
//保存用户答案
|
||
void SaveUserAnswers()
|
||
{
|
||
// 保存用户选择的答案
|
||
userAnswers[currentQuestionIndex].Clear();
|
||
int index = 0;
|
||
foreach (Toggle toggle in optionToggles)
|
||
{
|
||
if (toggle.isOn)
|
||
{
|
||
userAnswers[currentQuestionIndex].Add(index);
|
||
}
|
||
index++;
|
||
}
|
||
}
|
||
|
||
//检查是否正确
|
||
void CheckCurrentAnswer()
|
||
{
|
||
// 检查用户答案是否为正确答案的子集
|
||
if (questions[currentQuestionIndex].CorrectAnswers.IsSubsetOf(userAnswers[currentQuestionIndex]))
|
||
{
|
||
OnHintViewShow("正确");
|
||
}
|
||
else
|
||
{
|
||
OnHintViewShow("错误");
|
||
}
|
||
}
|
||
|
||
//显示正确还是错误
|
||
public void OnHintViewShow(string result)
|
||
{
|
||
// 显示正确答案提示
|
||
Question question = questions[currentQuestionIndex];
|
||
string correctAnswerLetters = ConvertNumbersToLetters(question.CorrectAnswers);
|
||
|
||
hintText.text = $"回答{result},答案为: {correctAnswerLetters}";
|
||
|
||
CtrlPage.SetActive(true);
|
||
}
|
||
|
||
//下一题
|
||
public void OnNextButtonClicked()
|
||
{
|
||
// 切换到下一题
|
||
if (currentQuestionIndex < questions.Count - 1)
|
||
{
|
||
//SaveUserAnswers();
|
||
currentQuestionIndex++;
|
||
DisplayQuestion();
|
||
//hintText.gameObject.SetActive(false);
|
||
}
|
||
//计算结果并进入结果页面
|
||
else
|
||
{
|
||
CheckAllAnswers();
|
||
}
|
||
|
||
//控制页面消失
|
||
CtrlPage.SetActive(false);
|
||
}
|
||
|
||
//检查所有答案结果并显示结果页面
|
||
void CheckAllAnswers()
|
||
{
|
||
// 检查所有答案并计算正确率
|
||
int correctCount = 0;
|
||
for (int i = 0; i < questions.Count; i++)
|
||
{
|
||
// 检查用户答案是否为正确答案的子集
|
||
if (questions[i].CorrectAnswers.IsSubsetOf(userAnswers[i]))
|
||
{
|
||
correctCount++;
|
||
}
|
||
}
|
||
resultText.text = $"正确率: {(float)correctCount / questions.Count * 100:F2}%";
|
||
ResultPage.SetActive(true);
|
||
}
|
||
|
||
//再来一遍
|
||
public void OnMoreDoOnce()
|
||
{
|
||
ToStartQuestion();
|
||
}
|
||
|
||
//上一题
|
||
public void OnPreviousButtonClicked()
|
||
{
|
||
// 切换到上一题
|
||
if (currentQuestionIndex > 0)
|
||
{
|
||
SaveUserAnswers();
|
||
currentQuestionIndex--;
|
||
DisplayQuestion();
|
||
//isSubmitted = false; // 重置提交状态
|
||
hintText.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
void UpdateTimerUI()
|
||
{
|
||
//// 更新滑动条和倒计时文本
|
||
//if (!isSubmitted) // 仅在未提交时更新
|
||
//{
|
||
// timeSlider.value = remainingTime / totalTime;
|
||
// timerText.text = $"剩余时间: {Mathf.Ceil(remainingTime)}秒";
|
||
//}
|
||
}
|
||
|
||
//void LockSliderPosition()
|
||
//{
|
||
// // 禁用滑块交互
|
||
// timeSlider.interactable = false;
|
||
//}
|
||
|
||
//void StopTimer()
|
||
//{
|
||
// // 停止倒计时并更新文本
|
||
// timerText.text = $"本次用时:{totalTime - remainingTime:F0}s";
|
||
// timeSlider.value = 0; // 停止滑动条
|
||
//}
|
||
|
||
|
||
int GetAnsweredCount()
|
||
{
|
||
// 获取已回答的问题数量
|
||
int count = 0;
|
||
foreach (var answer in userAnswers)
|
||
{
|
||
if (answer.Count > 0)
|
||
{
|
||
count++;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
string ConvertNumbersToLetters(HashSet<int> numbers)
|
||
{
|
||
// 将数字转换为字母
|
||
StringBuilder letters = new StringBuilder();
|
||
foreach (int number in numbers)
|
||
{
|
||
if (number >= 0 && number <= 3)
|
||
{
|
||
char letter = (char)('A' + number);
|
||
letters.Append(letter);
|
||
}
|
||
}
|
||
return letters.ToString();
|
||
}
|
||
|
||
private List<Question> XmlTextReaderTest()
|
||
{
|
||
List<Question> questions = new List<Question>();
|
||
|
||
// 构建文件路径
|
||
string filePath = Path.Combine(Application.streamingAssetsPath, FileName+".xml");
|
||
|
||
if (!File.Exists(filePath))
|
||
{
|
||
Debug.LogError("Questions file not found at: " + filePath);
|
||
return questions;
|
||
}
|
||
|
||
string textmsg = File.ReadAllText(filePath);
|
||
|
||
XmlDocument xmlDocument = new XmlDocument();
|
||
try
|
||
{
|
||
xmlDocument.LoadXml(textmsg);
|
||
}
|
||
catch (XmlException e) {
|
||
Debug.LogError($"XML格式错误:{e.Message}");
|
||
return questions;
|
||
}
|
||
|
||
XmlNode rootNode = xmlDocument.SelectSingleNode("root");
|
||
if (rootNode == null)
|
||
{
|
||
Debug.LogError("XML中未找到<root>节点");
|
||
return questions;
|
||
}
|
||
|
||
//遍历所有<question>子节点
|
||
XmlNodeList questionNodes = rootNode.SelectNodes("question");
|
||
if (questionNodes == null || questionNodes.Count == 0)
|
||
{
|
||
Debug.LogWarning("未找到任何<question>节点");
|
||
return questions;
|
||
}
|
||
|
||
// 提取每个question的属性值
|
||
foreach (XmlNode node in questionNodes)
|
||
{
|
||
Question question = new Question();
|
||
|
||
// 读取id属性(转换为int)
|
||
if (!TryGetIntAttribute(node, "id", out int id))
|
||
{
|
||
Debug.LogWarning("跳过无效的question节点(缺少id或格式错误)");
|
||
continue;
|
||
}
|
||
|
||
// 读取quesTitle属性(字符串)
|
||
if (!TryGetStringAttribute(node, "quesTitle", out string title))
|
||
{
|
||
Debug.LogWarning($"id为{id}的节点缺少quesTitle属性");
|
||
continue;
|
||
}
|
||
|
||
string[] allMsg = title.Split('|');
|
||
question.Text = allMsg[0];
|
||
if (allMsg.Length > 1) {
|
||
question.Options=new string[allMsg.Length-1];
|
||
for (int i = 1;i<allMsg.Length;i++)
|
||
{
|
||
question.Options[i-1]=allMsg[i];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
question.Options=null;
|
||
}
|
||
|
||
// 读取answer属性(转换为string)
|
||
if (!TryGetStringAttribute(node, "answer", out string answer))
|
||
{
|
||
Debug.LogWarning($"id为{id}的节点answer格式错误");
|
||
continue;
|
||
}
|
||
question.CorrectAnswers = new HashSet<int>(Array.ConvertAll(answer.ToCharArray(), c => int.Parse(c.ToString())));
|
||
|
||
// 读取type属性(转换为int)
|
||
if (!TryGetIntAttribute(node, "type", out int type))
|
||
{
|
||
Debug.LogWarning($"id为{id}的节点type格式错误");
|
||
continue;
|
||
}
|
||
question.IsSingleChoice = type == 1;
|
||
question.IsJudgment = type == 0;// 判断题标识
|
||
|
||
// 添加到列表
|
||
questions.Add(question);
|
||
}
|
||
|
||
return questions;
|
||
}
|
||
|
||
// 辅助方法:获取节点的字符串属性
|
||
private bool TryGetStringAttribute(XmlNode node, string attrName, out string value)
|
||
{
|
||
XmlAttribute attr = node.Attributes[attrName];
|
||
if (attr != null)
|
||
{
|
||
value = attr.Value;
|
||
return true;
|
||
}
|
||
value = null;
|
||
return false;
|
||
}
|
||
|
||
// 辅助方法:获取节点的int属性(并验证格式)
|
||
private bool TryGetIntAttribute(XmlNode node, string attrName, out int value)
|
||
{
|
||
if (TryGetStringAttribute(node, attrName, out string strValue))
|
||
{
|
||
return int.TryParse(strValue, out value);
|
||
}
|
||
value = 0;
|
||
return false;
|
||
}
|
||
|
||
}
|
||
|
||
public class Question
|
||
{
|
||
public string Text; // 问题文本
|
||
public string[] Options; // 选项数组
|
||
public bool IsSingleChoice; // 是否为单选题
|
||
public HashSet<int> CorrectAnswers; // 正确答案集合
|
||
public bool IsJudgment; // 是否为判断题
|
||
} |