软件构造大作业之计算题生成器
本次GUI界面采有java的swing包,相比于awt,swing为重量级GUI插件包,是目前大多数Java编写GUI界面普遍使用的包,后台连接了mysql数据库可以实现计算题的保存
计算题生成类
package com.std.www.compute;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
public class TopicProduce {
private static Set<String> flag=new HashSet<>();
private static List<Topic> topicList=new ArrayList<>();
private static int numbering=0;
public static Set<String> getFlag() {
return flag;
}
public static void setFlag(Set<String> flag) {
TopicProduce.flag = flag;
}
public static List<Topic> getTopicList() {
return topicList;
}
public static void setTopicList(List<Topic> topicList) {
TopicProduce.topicList = topicList;
}
public static int getNumbering() {
return numbering;
}
public static void setNumbering(int numbering) {
TopicProduce.numbering = numbering;
}
private static String makeOp(List<String> op){
Random random=new Random();
return op.get(random.nextInt(10000)% op.size());
}
private static int makeNum(int minNum,int maxNum){
Random random=new Random();
return random.nextInt(maxNum-minNum+1)+minNum;
}
public static void makeTopic(int topicNum,List<String> topicLevel,int topicMinNum,int topicMaxNum,int opNum){
numbering=1;
topicList.clear();
if(opNum<2) opNum=2;
LocalDateTime localDateTime=LocalDateTime.now();
System.out.println("题目难度等级:"+topicLevel);
System.out.println("题目数量:"+topicNum);
System.out.println("操作符个数:"+opNum);
System.out.println("数值范围:"+topicMinNum+"~"+topicMaxNum);
while (topicNum-->0) {
List<String> symbols = new ArrayList<>();
List<Integer> figures = new ArrayList<>();
StringBuilder formula=new StringBuilder();
for(int i=0;i<opNum;i++){
if(i==0) {
int figure= makeNum(topicMinNum,topicMaxNum);
figures.add(figure);
formula.append(String.valueOf(figure));
}
String symbol=makeOp(topicLevel);
int figure= makeNum(topicMinNum,topicMaxNum);
if(figure==0&&symbol.equals("/")){
i--;
continue;
}
symbols.add(symbol);
figures.add(figure);
formula.append(" "+symbol+" ");
formula.append(String.valueOf(figure));
}
formula.append("=");
if(flag.contains(String.valueOf(formula))){
topicNum++;
}
else {
flag.add(String.valueOf(formula));
Topic topic=new Topic(symbols,figures,String.valueOf(formula),numbering++,0);
topic.setGroup_id(localDateTime);
if(!topic.makeAnswer()){
topicNum++;
continue;
}
int ans=topic.getTopicAnswer();
topic.setTopicAnswer(ans);
topicList.add(topic);
}
}
}
}
class Topic{
private List<String> symbols;
private List<Integer> figures;
private String formula;
private int topicId;
private int topicAnswer;
private LocalDateTime group_id;
public Topic() {
}
public Topic(List<String> symbols, List<Integer> figures, String formula, int topicId, int topicAnswer) {
this.symbols = symbols;
this.figures = figures;
this.formula = formula;
this.topicId = topicId;
this.topicAnswer = topicAnswer;
}
public Topic(String formula, int topicId, int topicAnswer,LocalDateTime group_id) {
this.formula = formula;
this.topicId = topicId;
this.topicAnswer = topicAnswer;
this.group_id = group_id;
}
public LocalDateTime getGroup_id() {
return group_id;
}
public void setGroup_id(LocalDateTime group_id) {
this.group_id = group_id;
}
public List<String> getSymbols() {
return symbols;
}
public void setSymbols(List<String> symbols) {
this.symbols = symbols;
}
public List<Integer> getFigures() {
return figures;
}
public void setFigures(List<Integer> figures) {
this.figures = figures;
}
public String getFormula() {
return formula;
}
public void setFormula(String formula) {
this.formula = formula;
}
public int getTopicId() {
return topicId;
}
public void setTopicId(int topicId) {
this.topicId = topicId;
}
public int getTopicAnswer() {
return topicAnswer;
}
public void setTopicAnswer(int topicAnswer) {
this.topicAnswer = topicAnswer;
}
private boolean check(String a,String b){
if(a.equals("+")&&!b.equals("+")) return true;
else if(a.equals("-")&&(b.equals("/")||b.equals("*"))) return true;
else if(a.equals("*")&&b.equals("/")) return true;
else return false;
}
private int computeAnswer(int x,int y,String op){
switch (op){
case "+":
return x+y;
case "-":
return x-y;
case "*":
return x*y;
case "/":
return x/y;
default:
throw new RuntimeException("非法运算符");
}
}
public boolean makeAnswer(){
Stack<String> sym_s=new Stack<>();
Stack<Integer> fig_s=new Stack<>();
fig_s.push(figures.get(0));
for(int i=1;i<figures.size();i++){
int fi=figures.get(i);
String sy=symbols.get(i-1);
if(sym_s.isEmpty()||check(sym_s.peek(),sy)){
sym_s.push(sy);
fig_s.push(fi);
}
else{
while (!sym_s.isEmpty()&&!check(sym_s.peek(),sy)) {
int y = fig_s.pop();
int x = fig_s.pop();
String op=sym_s.pop();
if(op.equals("/")&&y==0){
System.out.println("除0异常");
return false;
}
int ans=computeAnswer(x,y,op);
fig_s.push(ans);
}
sym_s.push(sy);
fig_s.push(fi);
}
}
while (!sym_s.isEmpty()) {
int y = fig_s.pop();
int x = fig_s.pop();
String op=sym_s.pop();
if(op.equals("/")&&y==0){
System.out.println("除0异常");
return false;
}
int ans=computeAnswer(x,y,op);
fig_s.push(ans);
}
this.setTopicAnswer(fig_s.pop());
return true;
}
}
GUI界面类
package com.std.www.compute;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TextFieldCellEditor extends AbstractCellEditor implements TableCellEditor {
private JTextField textField;
public TextFieldCellEditor() {
textField = new JTextField();
textField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
stopCellEditing();
}
});
}
@Override
public Object getCellEditorValue() {
return textField.getText();
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
textField.setText((value != null) ? value.toString() : "");
return textField;
}
}
package com.std.www.compute;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
public class TextFieldCellRenderer extends JTextField implements TableCellRenderer {
public TextFieldCellRenderer() {
setBorder(BorderFactory.createEmptyBorder());
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
setText((value != null) ? value.toString() : "");
return this;
}
}
package com.std.www.compute;
import javafx.scene.web.WebEngine;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.AbstractDocument;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class TopicGUI {
private static JScrollPane scrollPane;
private static JTable topicTable;
private static JTextField topicNum;
private static JTextField minNum;
private static JTextField maxNum;
private static JTextField opNum;
private static List<Topic> topics = new ArrayList<>();
private static List<JTextField> answer = new ArrayList<>();
private static DefaultTableModel model;
private static JCheckBox checkBox1;
private static JCheckBox checkBox2;
private static JCheckBox checkBox3;
private static JCheckBox checkBox4;
private static void loadTopicTable(){
String[] columnNames = {"题目序号","题目","你的答案"};
model = new DefaultTableModel(null, columnNames) {
@Override
public boolean isCellEditable(int row, int column) {
return column == getColumnCount() - 1;
}
};
topicTable.setModel(model);
topicTable.getColumnModel().getColumn(columnNames.length - 1).setCellRenderer(new TextFieldCellRenderer());
topicTable.getColumnModel().getColumn(columnNames.length - 1).setCellEditor(new TextFieldCellEditor());
for (Topic i:topics){
model.addRow(new Object[]{i.getTopicId(),i.getFormula(),""});
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("算术题生成器");
frame.setSize(1000, 800);
frame.setLayout(null);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Font font = new Font("宋体", Font.BOLD, 25);
//初始化头部
JPanel headPanel = new JPanel();
headPanel.setBackground(Color.WHITE);
headPanel.setBounds(200, 50, 600, 150);
headPanel.setLayout(new FlowLayout(FlowLayout.LEADING, 20, 10));
JLabel numTopicLabel = new JLabel("题目数量");
numTopicLabel.setFont(font);
topicNum = new JTextField();
topicNum.setPreferredSize(new Dimension(130, 30));
JLabel numOpLabel = new JLabel("运算符个数");
numOpLabel.setFont(font);
opNum = new JTextField();
opNum.setPreferredSize(new Dimension(130, 30));
JLabel oPLabel = new JLabel("操作符");
oPLabel.setFont(font);
checkBox1 = new JCheckBox("+");
checkBox2 = new JCheckBox("-");
checkBox3 = new JCheckBox("*");
checkBox4 = new JCheckBox("/");
checkBox1.setFont(font);
checkBox2.setFont(font);
checkBox3.setFont(font);
checkBox4.setFont(font);
JLabel range = new JLabel("数值范围");
range.setFont(font);
minNum = new JTextField();
minNum.setText("0");
minNum.setPreferredSize(new Dimension(130, 30));
maxNum = new JTextField();
maxNum.setPreferredSize(new Dimension(130, 30));
maxNum.setText("100");
headPanel.add(numTopicLabel);
headPanel.add(topicNum);
headPanel.add(numOpLabel);
headPanel.add(opNum);
headPanel.add(oPLabel);
headPanel.add(checkBox1);
headPanel.add(checkBox2);
headPanel.add(checkBox3);
headPanel.add(checkBox4);
JLabel jLabel = new JLabel();
jLabel.setPreferredSize(new Dimension(200, 50));
headPanel.add(jLabel);
headPanel.add(range);
headPanel.add(minNum);
headPanel.add(maxNum);
headPanel.setBorder(new LineBorder(Color.BLACK));
//初始化计算题
topicTable=new JTable();
scrollPane=new JScrollPane(topicTable);
scrollPane.setBounds(200,210,600,400);
scrollPane.setBorder(new LineBorder(Color.BLUE));
//初始化按钮
JPanel buttonPanel=new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel,BoxLayout.X_AXIS));
buttonPanel.setBorder(new LineBorder(Color.BLUE));
buttonPanel.setBounds(200,650,600,70);
JButton push=new JButton("提交习题");
push.setFont(font);
JButton makeTopicButton=new JButton("生成习题");
makeTopicButton.setFont(font);
JButton saveTopicButton=new JButton("保存习题");
saveTopicButton.setFont(font);
JButton oldTopicButton=new JButton("历史习题");
oldTopicButton.setFont(font);
push.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int rowCount = model.getRowCount();
int lastColumnIndex = model.getColumnCount() - 1;
StringBuilder stringBuilder=new StringBuilder("");
int right=0;
for (int row = 0; row < rowCount; row++) {
Object value = model.getValueAt(row, lastColumnIndex);
stringBuilder.append("答案: "+topics.get(row).getTopicAnswer()+" 你的答案: "+value+"\n");
if(String.valueOf(topics.get(row).getTopicAnswer()).equals(value)){
right++;
}
}
stringBuilder.append("正确率为: "+right*1.0/rowCount);
JOptionPane.showMessageDialog(frame, stringBuilder.toString());
}
});
makeTopicButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<String> op=new ArrayList<>();
if(checkBox1.isSelected()){
op.add("+");
}
if(checkBox2.isSelected()){
op.add("-");
}
if(checkBox3.isSelected()){
op.add("*");
}
if(checkBox4.isSelected()){
op.add("/");
}
TopicProduce.makeTopic(Integer.parseInt(topicNum.getText()), op,Integer.parseInt(minNum.getText()),Integer.parseInt(maxNum.getText()),Integer.parseInt(opNum.getText()));
topics=TopicProduce.getTopicList();
loadTopicTable();
}
});
saveTopicButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TopicMysql topicMysql=new TopicMysql();
if(topicMysql.saveTopics(topics)){
JOptionPane.showMessageDialog(frame, "保存成功");
}
else {
JOptionPane.showMessageDialog(frame, "保存失败");
}
}
});
oldTopicButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TopicMysql topicMysql=new TopicMysql();
topics.clear();
topics=topicMysql.oldTopics();
loadTopicTable();
}
});
buttonPanel.add(push);
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(makeTopicButton);
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(saveTopicButton);
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(oldTopicButton);
frame.add(headPanel);
frame.add(scrollPane);
frame.add(buttonPanel);
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
数据库连接
package com.std.www.compute;
import lombok.SneakyThrows;
import java.sql.*;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class TopicMysql {
private Connection connection;
@SneakyThrows
public Connection getConnection() {
Class.forName("com.mysql.jdbc.Driver");
connection=DriverManager.getConnection("jdbc:mysql://localhost:3307/lab?useSSL=false","root","123456");
return connection;
}
@SneakyThrows
public boolean saveTopics(List<Topic> topics){
getConnection();
PreparedStatement p = connection.prepareStatement("insert into topic values (?,?,?,?)");
boolean flag=true;
for (Topic i:topics){
p.setInt(1,i.getTopicId());
p.setString(2,i.getFormula());
p.setInt(3,i.getTopicAnswer());
p.setTimestamp(4, Timestamp.valueOf(i.getGroup_id()));
flag&=p.executeUpdate()>0;
}
p.close();
connection.close();
return flag;
}
@SneakyThrows
public List<Topic> oldTopics(){
getConnection();
PreparedStatement p = connection.prepareStatement("select * from topic");
ResultSet res = p.executeQuery();
List<Topic> topics=new ArrayList<>();
while (res.next()){
topics.add(new Topic(res.getString(2),res.getInt(1),res.getInt(3), res.getTimestamp(4).toLocalDateTime()));
}
p.close();
connection.close();
return topics;
}
}
从程序到软件
从用户的需求来讲,本程序缺乏许多功能,例如实现小数或者更高级的运算题,对于历史错题的记录,对于历史生成题目进行分组展示可以让用户自由选择某一套习题,也没有实现题目去重,还有对每一道错题的错误次数统计,如果更加深入的话,还可以增加一种算法,根据以往用户的答题记录,优化生成,尽可能生成用户错误次数的题目的相似的题目类型,还有等等
从测试的角度来讲的话,本程序也缺乏健壮性,对于错误的输入没有判定,没有判定用户空选的情况,还有等等
软件构造课让我明白,软件不单单是实现功能,更关键的是用户体验,永远是为人服务的,最需要抓住的是用户的直观感受和必要需求,这样才能让程序变为一个软件。