WhiteInterweaves的资源

  • 基于HTML和JS实现的西瓜皮斯拉小游戏

    一、作品设计1.1 作品主题《西瓜皮斯拉》是一款宣传保护海洋动物、保护环境的硬核小游戏。

    作品意义:希望能让玩家在游戏中学到保护海洋的重要性
    灵感来源:游戏角色来源于DOTA2英雄船长昆卡和潮汐猎人。 游戏背景借鉴于电影《哥斯拉》

    1.2 作品设计思路游戏背景讲述了在人类重度污染下产生变异的生物向人类发起复仇的故事,通过让玩家扮演英雄昆卡与之进行战斗,引发玩家对于现代人类为了发展而过度破坏自然环境的思考。
    1.3 实现思路及表现手法应用角色扮演的方式让玩家代入其中,通过高难度的过关流程让玩家在一次又一次的失败中深刻地感受到破坏环境所带来的严重后果,激发玩家的环境保护意识。
    1.4 功能模块1.4.1 主界面模块游戏的整个界面由1个包含2个同样大小的canvas元素(以下用canvas1和canvas2分别代指左边和右边的canvas)的html界面构成,用于显示游戏的画面并进行用户的交互。
    分为开始界面和结束界面两块。
    1.4.2 角色模块canvas中玩家可操控的人物本身采用键盘控制,实现角色在各自的canvas元素中自由移动。
    玩家可操控的人物具有攻击、生命值、法力值、经验值、等级和移动速度等属性。游戏中canvas的会显示角色的属性。
    1.4.3 游戏内容模块单人模式

    canvas1中每回合会以一定的频率在随机位置产生能力值在一定范围内随机的怪物,怪物会朝向人物移动,撞击后对其造成伤害。每一回合怪物出现的频率都会有所增加
    玩家通过射出子弹或施放技能击杀怪物可以获取与其能力值成比的分数,并显示在canvas的上部。击杀怪物还可以获得经验值,达到规定的经验值角色可以升级,其各项能力值得到提升,并且回复所有的生命值和魔法值
    在每一回合游戏结束之后,canvas中间会显示红蓝两个传送门,玩家在进入传送门区域后通过点击空格键确认,来增加生命值上限或者魔法值上限,并且开始下一回合的战斗
    canvas2中为boss挑战场景,玩家在canvas1中进行与多人模式中基本相同的游戏模式,同时canvas2中的boss随着时间不断地变强
    玩家击杀怪物可以获得挑战boss的资格,会用进度条显示在canvas的上部,届时游戏中间会出现另一个boss传送门,玩家可以随时选择进入该传送门点击空格键确定,进入canvas2中进行boss战
    玩家生命值为0即为失败,boss生命值为0即玩家胜利

    双人模式

    canvas1和canvas2均采用单人模式中canvas1中的战斗模式,但取消了boss战以及相关功能,取消了射出子弹的功能,增加了每秒回蓝。玩家只能使用技能来击杀怪物
    每回合结束后双方玩家可以根据自己的喜好选择生命值上限或者魔法值上限的提升
    双方玩家以生存更长的时间为目的进行竞技,任何一方的生命值归零时游戏结束,另一方获得游戏的胜利

    二、代码设计2.1 模块设计(冒号后为子模块)2.1.1 角色模块
    人物:人物移动、人物属性、人物攻击、人物技能、经验系统
    怪物:怪物移动、怪物属性、怪物攻击、怪物属性值随机、击杀给予经验和分数
    Boss:boss移动、boss属性、boss攻击、boss技能、boss能力值随时间增长

    2.1.2 主界面模块游戏开始结束界面、开始按钮、剧情背景文字、结束信息、人物属性面板。
    游戏内容模块,怪物随机生成位置、战斗的伤害判定。
    2.2 流程设计
    移动部分:人物、怪物移动和boss的移动均采用设置上下左右四个布尔型变量,其中人物由键盘输入激活相应方向的变量使其坐标根据移动速度产生变化。而boss和怪物则通过获取人物的坐标来判定需要移动的方向,自动决定四个变量的值,并根据移动速度进行移动
    属性值部分:通过对象的属性实现,并在需要的时候进行修改
    攻击部分:通过键盘输入指令,按照一定频率生成子弹对象,并将其存入队列中,在其击中敌人或者飞离canvas可视范围之外后将其销毁
    伤害判定部分:主要包括矩形与矩形、矩形与圆形是否相交的判定,若有相交则攻击/技能生效,修改相应的属性值
    经验系统:击杀怪物后人物经验值属性会得到增加,达到经验值上限后可以升级,并且通过一定系数提升各项能力值,回复所有生命值和法力值,并且提高下一级升级所需要的经验值上限
    技能系统:人物通过从键盘读入指令决定施放技能的种类,再调用函数施放技能。Boss通过预先设定不同技能的几率,再随机生成0到100的数来随机决定施放的技能,并调用相应的函数
    boss成长:设定一定的时间间隔,每隔一定的时间间隔,boss的各项属性包括大小都会有所提升

    所需要素材文件:

    图片:游戏开始界面图,游戏结束界面图
    音乐:游戏背景音乐

    2.3 流程图
    角色模块,包括人物主角,怪物和BOSS都通过定义对象,子模块在对象中实现。在游戏主场景调用对象。
    主界面模块作为游戏的整个流程线,设置各个界面模块的次序。
    游戏内容模块在主界面模块中调用实现效果。
    整个文件夹包括 代码文件夹、音乐文件夹和图片文件夹。
    在代码文件夹中,创建不同模块文件夹。
    调用逻辑:

    先调用游戏开始界面模块,进入游戏开始界面
    通过鼠标点击选择模式,调用游戏界面模块,进入游戏相对应模式中
    在相应模式中调用 角色模块 和 怪物模块 生成角色和怪物,产生战斗。其中包含游戏内容模块,怪物随机生成位置、战斗的伤害判定
    当目标达成进入Boss界面,调用Boss界面模块和Boss模块,使角色和Boss进行战斗
    当人物死亡或Boss死亡,调用结束界面模块

    Xia文件包:

    Cover.js:游戏开始界面 过场动画 开始按钮
    mainGame.js:游戏主界面 人物属性面板 提示文字
    over.js:游戏结束界面 过场动画 结束按钮
    Public_function.js:通用函数
    Statement.js:变量声明
    requestNextAnimationFrame.js:动画函数

    Pan文件包:

    object.js:游戏人物移动、属性、攻击、技能 小怪群 怪物站点
    buckets.min.js:框架:提供一些数据结构,用到了List
    victor.min.js框架: 提供向量以及向量的各种操作

    Fang文件包:
    bossobject boss移动、boss属性、boss攻击、boss技能、boss能力值随时间增长
    三、游戏展示初始界面

    游戏教程

    游戏画面

    游戏结束
    0  留言 2021-04-16 08:55:31
  • 基于Qt实现的简易记事本

    一、实验内容使用 Qt 设计并实现简易记事本。记事本包括一个 Menu,三个 Toolbar 和一 个文本编辑区。

    Menu 主要包含四个下拉列表:File, Edit, Format 和 Help
    File 下拉列表包含: New(新建), Open(打开), Save(保存), Save As(另存), Print(打印), Print Preview(打印预览)和 Exit(退出)
    Edit 下拉列表包含:Undo(撤销), Redo (重做), Cut(剪切), Copy(复制), Paste(粘贴)
    Format 下拉列表包含: Font(字体)子列表和 Color(颜色),Font(字体)子列表里包含 Bold(加粗), Italic(斜体)和 Underline(下划线)
    Help 下拉列表包含:About(关于)和 About Qt(关于 Qt)。Toolbar 包含 fileToolBar, editToolBar 和 formatToolBar

    二、代码结构实现过程中没有添加新的类,所有功能都在 MainWindow 主类中实现。添加的属性和方法如表 1 所示:

    三、算法介绍本次实验没有设计到较为复杂的算法,都是使用 Qt 自带的一些类,以及相关类对应的方法来实现对应的功能。主要用到以下几个类及其方法:
    QPlainTextEdit 类:本实验文本编辑域类,主要用来实现文本编辑中用户需要的各种功能需求。

    QMenuBar 类:主要用来实现菜单栏功能。

    QMenu 类:主要用来实现菜单栏每个菜单的功能,包括 File,Edit,Format 和 Help,以 Format 菜单为例,实现代码如下:
    menuFormat = menuBar->addMenu(tr("&Format"));menuFormat->addSeparator();menuFont = menuFormat->addMenu(tr("&Font")); menuFont->addAction(boldAct);menuFont->addAction(italicAct); menuFont->addAction(underlineAct); menuFormat->addAction(colorAct);
    用到的方法及其功能见表 3-3:

    QAction 类:主要用来实现每个菜单中的 Action,以 File 菜单下的 New(新建)为例,代码实现如下:
    newAct = new QAction(QIcon("icons/new.png"),tr("&New"), this);newAct->setShortcut(QKeySequence(tr("Ctrl+N"))); newAct->setToolTip("New"); newAct->setStatusTip(tr("Create a new file")); connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
    用到的方法及其功能见表 3-4:

    QTextcharFormat 类,主要用来设置 textEdit 中的字体的样式,以初始默认样式为例,代码实现如下:
    void TextEdit::initialize() { QTextCharFormat fmt; fmt.setFontPointSize(15); fmt.setFontFamily("Times New Roman"); textEdit->mergeCurrentCharFormat(fmt);}
    用到的方法及其功能见表 3-5。

    四、UML 图用例图如图 4-1 所示,为用户提供的功能主要是菜单栏中的几个菜单。包括文本编辑,File 菜单中的新建,打开,保存,另存为,打印,预览和退出。Edit 菜单中的撤销,重做,剪切,复制,粘贴。Format 菜单中的加粗,斜体,下划线和颜色设置。Help 菜单中的关于和关于 Qt。

    类图如图 4-2 所示。

    五、信号和槽5.1 信号与槽函数的连接File Menu 中的 connect
    connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); connect(openAct, SIGNAL(triggered()), this, SLOT(openFile())); connect(saveAct, SIGNAL(triggered()), this, SLOT(saveFile())); connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAsFile())); connect(printAct, SIGNAL(triggered()), this, SLOT(printFile())); connect(printPreviewAct, SIGNAL(triggered()), this, SLOT(printPreview())); connect(exitAct, SIGNAL(triggered()), this, SLOT(exit()));
    Eidt Menu 中的 connect:
    connect(undoAct, SIGNAL(triggered()), this, SLOT(undo())); connect(redoAct, SIGNAL(triggered()), this, SLOT(redo())); connect(cutAct, SIGNAL(triggered()), this, SLOT(cut())); connect(copyAct, SIGNAL(triggered()), this, SLOT(copy())); connect(pasteAct, SIGNAL(triggered()), this, SLOT(paste()));
    Format Menu 中的 connect:
    connect(boldAct, SIGNAL(triggered()), this, SLOT(bold())); connect(italicAct, SIGNAL(triggered()), this, SLOT(italic())); connect(underlineAct, SIGNAL(triggered()), this, SLOT(underline())); connect(colorAct, SIGNAL(triggered()), this, SLOT(color()));
    Help Menu 中的 connect:
    connect(aboutAct, SIGNAL(triggered()), this, SLOT(about())); connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
    textEdit 中的 connect:
    connect(textEdit, SIGNAL(textChanged()), this, SLOT(textChange())); connect(textEdit, SIGNAL(selectionChanged()), this, SLOT(selectChange()));
    打印预览 connect:
    connect(&preview, SIGNAL(paintRequested(QPrinter*)), this, SLOT(printPreviewSlot(QPrinter*)));
    5.2 槽函数执行功能
    六、实验亮点6.1 点击窗口关闭按钮或者 exit点击窗口关闭按钮或者 exit 时,若文档未保存,则跳出提示框,询问用户 是否保存。若文档已经保存,或者文档未进行修改则不会跳出提示框,直接关闭。
    效果如图 6-1 所示。 具体实现方法如下:
    int TextEdit::exit() { if (!isSaved) { int signal = QMessageBox::warning(this, tr("Warning"), tr("Do you want to save the changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save); switch (signal) { case QMessageBox::Save: saveFile(); break; case QMessageBox::Discard: return 0; QCoreApplication::exit(0); case QMessageBox::Cancel: break; } return 1; } return 0;} void TextEdit::closeEvent(QCloseEvent *event) { int res = exit(); if (res) event->ignore(); }
    当点击了 exit action 之后,调用 exit()槽函数。判断当前文件是否已保存。若文件未保存,则弹出警告 QMessageBox 询问用户是否要保存当前文件,共有三个选择按钮:Save(保存当前文件),Discard(不保存,退出),Cancle(取消,返回当前文件)。
    当点击了窗口的关闭按钮后,调用 closeEvent 函数,函数中调用 exit()函数,判断如果用户选择的是 Cancle,则取消关闭当前窗口。当文档进行修改后,如上文第五点中 textEdit 中的 connect,textEdit 文档发生修改会调用 textChange()槽函数,修改文件状态为保存即可。(修改 isSaved 值为 false)

    6.2 加粗,斜体,下划线功能加粗,斜体,下划线功能的实现效果完全仿照 Ofiice Word。ToolBar 中的加粗,斜体,下划线图标的状态(checked 状态或者非 checked 状态)完全取决于鼠标光标所处位置处的字体。如果是选中状态的话,取决于选中文本结尾处字体的格式。



    光标所处位置处前一个字“国”为加粗状态。所以当前 B 图标为 checked 状态。I 图标和 U 图标为非 checked 状态。

    光标所处位置处前一个字为“津”,该字此时为 normal 状态,所以三个图 标均为非 checked 状态。


    具体实现方法代码如下:
    connect(textEdit, SIGNAL(selectionChanged()), this, SLOT(selectChange())); void TextEdit::selectChange() { QTextCharFormat fmt = textEdit->currentCharFormat(); int weight = fmt.fontWeight(); bool italic = fmt.fontItalic(); bool underline = fmt.fontUnderline(); if (weight == 75) boldAct->setChecked(true); else boldAct->setChecked(false); if (italic) italicAct->setChecked(true); else italicAct->setChecked(false); if (underline) underlineAct->setChecked(true); else underlineAct->setChecked(false); }
    首先,当光标所处位置发生变化时,textEdit 会发出 selectionChanged() 信号,然后与之对应的槽函数 selectChange()进行接收。在该函数中,首先获取光标所处位置处文字的 format。然后根据这个 format 对“加粗”,“斜 体”,“下划线”的 checked 状态进行设置。只需要设置成对立状态就可以了。
    七、总结讨论经过本次实验,我对 Qt 的使用更为熟练。此次实验的所有控件都是通过代码实现的,所以是我使用 Qt 以来的一次新的尝试,之前使用 Qt 控件都是通过 Qt designer 完成的。较实验一而言,本次 实验更偏重的是对 Qt 一些新的类的了解与应用。比如 QMenu 类,QAction 类,QPrinter 类等。让我认识了很多 Qt 自带的新的类,更加体会到 Qt 的强大之处。更重要的是,对 C++语言更为熟悉,编程能力进一步加强。
    1  留言 2021-03-08 10:40:23
  • 基于Python Web框架和MySQL的图书借阅系统

    1.需求分析1.1 系统目标利用Python web框架和MySQL开发一个仿真模拟的图书借阅系统。分为管理员和读者两个方面的功能实现。
    1.1.1 管理员方面
    增、删、改、挂失图书信息和读者信息
    查看用户信息和图书信息
    登录管理员界面
    为读者办理借书还书

    1.1.2 读者方面
    登录读者界面
    查询借书记录和个人信息

    1.2 数据流图1.2.1 读者注册
    1.2.2 图书上架
    1.2.3 图书搜索
    1.2.4 图书借阅
    1.2.5 借书记录搜索
    1.2.6 登陆
    1.2.7 图书、读者证挂失
    1.2.8 图书、读者删除
    1.3 数据字典1.3.1 数据项


    数据项名
    别名
    数据类型
    说明




    读者编号
    reader_id
    varchar(5) PK
    读者证的编号按顺序系统分配


    姓名
    reader_name
    varchar(20)
    读者姓名


    性别
    sex
    char(2)
    读者性别


    出生日期
    birthday
    date
    读者出生日期


    电话
    phone
    varchar(20)
    读者电话


    手机
    mobile
    varchar(20)
    读者手机


    证件名称
    card_name
    varchar(8)
    读者的证件可以是身份证学生证等


    证件编号
    card_id
    varchar(18)
    读者的证件号


    会员级别
    level
    varchar(6)
    有三个级别,普通银卡金卡


    办证日期
    day
    date
    即注册日期


    读者登录密码
    Password(Readers表)
    varchar(45)
    读者登录系统中时使用的密码


    图书编号
    book_id
    varchar(5)
    图书的编号系统分配


    书名
    book_name
    varchar(50)
    书的名字


    作者
    author
    varchar(20)
    书的作者


    出版社
    publishing
    varchar(20)
    书的出版社


    类别编号
    category_id
    varchar(5)
    书的类别编号


    单价
    price
    double
    书的价格


    入库日期
    date_in
    datetime
    入库的时间即图书上架时间


    库存数量
    quantity_in
    int
    上架的书的数量


    借出数量
    quantity_out
    int
    书借出去的数量


    遗失数量
    quantity_loss
    int
    书的丢失的数量


    出借日期
    date_borrow
    date
    书借出去的日期在借阅中生成


    应还日期
    date_return
    date
    根据会员的级别确定的日期


    遗失
    loss
    char(2)
    有“否”“是”区分书是否丢失了


    类别名称
    category
    varchar(20)
    类别的名字如“计算机”


    最长出借天数
    days
    smallint
    根据会员级别相应天数


    最多借书册书
    numbers
    smallint
    根据会员级别能借最多的书


    会费
    fee
    smallint
    会员级别所要的费用(单位:元)


    管理员ID
    User_ID
    Varchar(20)
    管理员登录用ID


    管理员密码
    Password(admin表)
    Varchar(20)
    管理员登录用密码



    1.3.2 数据结构
    数据结构名:读者证
    说明:定义了读者的信息
    组成

    reader_id, reader_name,sex,birthday,phone,mobile,card_name,card_id,level,day
    数据结构名:图书信息
    说明:定义了一本书的信息
    组成:

    book_id,book_name,author,publishing,category_id,price,date_in,quantity_in,quantity_out
    数据结构名:借阅信息
    说明:用户借阅书的信息
    组成:

    reader_id,book_id,date_borrow,date_return,loss
    数据结构名:会员等级
    说明:定义了会员的等级
    组成:

    level,days,numbers,fee

    2.概念设计2.1 系统ER图
    3.详细设计3.1关系模型
    会员级别:{会员级别,最长出借天数,最多借书书册,会费}
    读者:{读者编号,姓名,电话,手机,性别,办证日期,出生日期,证件名称,证件编号,会员级别,密码}
    借阅:{图书编号,读者编号,借阅日期,归还日期}
    图书:{图书编号,书名,作者,出版社,类别编号,单价,入库日期,库存数量,出借数量,遗失数量}
    读者管理:{管理员编号,读者编号,遗失日期}
    类别:{类别编号,类别名称}
    管理员:{管理员编号,密码}

    3.2 物理结构设计3.2.1 Readers表
    3.2.2 Books表
    3.2.3 Borrow表
    3.2.4 Member_level表
    3.2.5 Lost_card表
    3.2.6 Admin表
    4.系统实现4.1 程序框图4.1.1 总框图
    4.1.2 登录操作框图
    4.1.3 管理员操作框图


    4.1.4 读者操作框图
    4.2 运行界面4.2.1 登陆界面登录界面可以输入用户名和密码登录,两种不同用户组的登陆界面可以通过menu上的标签切换,在程序里是直接读取两个html文件(根目录下的member.html 和 administer.html)然后通过浏览器提出get请求完成操作。登录是提交get请求,登录信息在超链接里,后端分析登录信息做出判断。登录成功后会跳转相关界面,然后在根目录下产生一个user.txt,里面记录了登录用户的用户组和用户名,如a:root就代表管理员“root”、m:r001就代表读者“r001”。
    会员登录界面

    管理员登陆界面

    4.2.2 管理员所有操作都是通过get请求将信息返回给后端程序的。所有管理员的操作都要通过判断根目录下的登录信息文件user.txt里面的首字母来判断当前操作的用户是否拥有权限。
    图书信息表展示界面(外观模版为admin/adminBook.html)

    添加书籍界面(外观模版为admin/adminBookNew.html)

    修改书籍界面(外观模版为admin/adminBookEdit.html)

    读者信息表显示界面(外观模版为admin/adminReader.html)


    注册新读者界面(外观模版为admin/adminReaderNew.html)

    修改读者信息界面(外观模版为admin/adminReaderEdit.html)

    借书记录管理界面(外观模版为admin/adminRecord.html)

    4.2.3读者个人资料界面(外观模版为member/memberInfo.html)

    借书纪录界面(外观模版为member/memberHistory.html)

    搜索图书界面(外观模版为member/memberBorrow.html)

    4.2.4控制台开始界面

    Get请求path详情

    出错信息

    操作

    输出信息打印

    5.用户使用说明5.1 编程语言
    服务器: Python 2.7
    前端: HTML + JavaScript + CSS

    5.2 依赖库
    服务器库:BaseHTTPServer
    连接MySQL库:MySQLdb
    控制台设置库:Sys
    URL解析库:Urllib
    时间库:Time
    时间库:Datetime
    操作系统库:os

    5.3 编码方式
    前端网页:GBK
    服务器端:GBK
    数据库:UTF-8

    5.4 运行环境
    服务器:Windows7 SP1旗舰版 + Python2.7
    前端:Opera浏览器 34.0(Chrome内核)
    数据库:MySQL Server 5.5(默认端口3306)

    6.实验总结问题与解决6.1 实验总结在这个大作业实验中,我学会了很多东西,不仅更了解了数据库连接的方式和数据库的操作,还学会了使用Python搭建一个服务器和javascript语言。我也通过这次实验,深刻的考虑到了用户体验的需求,我们开发软件不仅要注重程序的完整性,更要关注界面以及操作对于用户的体验。一个好的程序必须是良好的用户体验加上完整的系统,缺一不可。我也通过这次实验了解到了web开发中前端和后端是如何交互的,比如说get请求的处理、post请求的处理,还有表单的递交、url的解析、编码的统一等等。我还学会了使用触发器来控制数据库的操作,以防非法操作,这比在程序中来判断更具有完整性,因为在程序中很有可能没有想到,而建立了触发器就可以在数据库端就把非法信息隔绝。
    6.2 遇到的问题与解决6.2.1 服务器、前端、url、数据库的编码不统一怎么办?服务器使用的是Python 2.7,这个版本的Python使用的是全局Unicode编码,前端网页使用的是GB2312的中文编码,url使用的是UTF-8编码,数据库使用的是UTF-8编码。
    解决方法
    服务器在通过转换设置为全局GBK编码,因为GBK编码包括了GB2312编码的部分,然后对于所有接收到的信息都解码然后转换为GBK编码,在服务器端统一编码。
    6.2.2 自动分配ID的机制如何实现?我们在读者和图书的添加操作的时候,如果需要用户输入编号就不太人性化,但是自动生成有可能会在删除书籍的时候重复。
    解决方法
    计算最大的编号,在最大的编号加一即可。
    6.2.3 读者证挂失怎么实现?图书证挂失的过程中,因为设计的时候没有在readers表添加挂失标记,所以挂失操作的时候不能标记为挂失,也不能直接删除这条纪录。
    解决方法
    新建一张挂失表lost_card表,凡是挂失图书证的读者就将信息复制到这个表,然后删除readers表中的相关记录,在解除挂失的时候只要反过程操作即可实现。
    3  留言 2019-08-29 11:07:21
  • 基于JAVA WEB的计算机网络课程在线考试系统的设计与实现

    摘 要本系统基于B/S结构的模式开发,通过网络给广大用户提供了比较可靠、方便、快捷的在线测评平台,系统主要实现了自动抽取试题、人工出题、套题选择、自动阅卷计分、在线测试、用户信息管理、成绩存档、错题管理等功能。用户利用浏览器可以直接访问本平台,通过选择要考的套题进行测试,测试结束后系统自动为用户判卷得出测试成绩,并显示出测试中错题和相应的答案解析,使得用户非常方便完成一次计算机网络课程的在线测评。
    关键词:B/S模式;在线测试;计算机网络;
    AbstractThe system uses B/S structure of the model development, through the network to provide customers with a more reliable, convenient and fast online evaluation platform, system is mainly to achieve the automatic extraction questions, artificial out of question, sets of questions selected, automatic scoring line in mind when testing, user information management, performance archiving, error management and other functions. users can use the browser to directly access the platform, by selecting a set of questions to test, after testing the system automatically for the user graders test results obtained, and shows the test wrong questions and corresponding answers to resolve, allows users to easily complete a computer network courses online evaluation.
    Key words: B/S mode; Online Testing; Computer Network;
    1 引言1.1 研究背景计算机网络技术的飞速发展以及计算机技术的发展及计算机的日益普及,现在很多国内外的大学和社会其他部门都已经开设了远程教育,通过计算机网络实现异地教育和培训,从而为在线测评系统发展提供了坚实的基础。相对于传统的笔试,网络在线测评系统集测试、评卷、成绩统计、查看错题等为一体,突破了时间与空间的限制,使得用户在任意时刻、任意地点,只要在有网的情况,均可以完成一次测评,这不仅节省了资源,而且提高了评分的客观性、公正性和准确度,大大改善了平时考试和测试的效率。
    1.2 国内外研究现状1977年Lord经过大量的研究,提出了现代测试理论,他发现由多选题组成的计算机辅助考试,与传统的纸质考试两种不同的考试形式对测试的人来说没有特别显著的差异。而BiNET做了一个有关于自适应考试的研究,研究的内容是智力测试。到了1960年,一些学者认为如果对课间的考试采用CAT可能会更加的好。
    随着时间的发展,1996年底,国外渐渐的出现网络教学的系统和平台,像英国的OPEN COOLEGE、美国的NTU这些都是十分典型的例子。在英国的Derby大学运用网络教育的力量成功完成了以色列的本科教育;上海电视大学与澳大利亚昆士兰大学合作完成了在上海开设本科学历的教育课程。
    国内的的网上测评技术的研究与西方国家相比相对要晚一些,我国在1995年首条连通所有的计算机互联网CERNET才正式投入使用。虽然起步比较晚,但在网络远程教育方面发展的相当迅猛。北京大学、清华大学、华南理工大学、上海复旦大学、河北大学、北京医科大学、湖南大学这些高等学校都已经在自己的网站上设立了自己的测评系统。很多公司都非常热情的和高校合作开办网络教育的课程。在中国知名度最高的清华大学也在网上举办了计算机网上硕士研究生的教育,全国各地纷纷上网咨询,报名人数更是达到了上万。
    2 设计方法研究概述2.1 可行性研究2.1.1 经济可行性分析主要是对项目的经济效益进行评价,利用计算机来实现网上测试以成为适应当今教学管理的方式。开发一套能满足网上测评系统的软件是十分必要的,实现试卷管理和试卷生成自动化,在减少由于认为失误而造成损失的同时,也可以使参加测试的人快速的找出自己的薄弱知识点。本系统在经济上是可以接受的,并且本系统实施后可以显著提高测试效率,有助于提高个人的知识的学习。所以本系统在经济上是可行的。
    2.2.2 技术可行性分析软件方面:网络化测试只需要一个web浏览器即可,用户便能通过浏览器访问到测试平台,在数据库方面有Oracle,它能存储海量数据,并且对数据能够进行优化,其易用性、灵活性、安全性为数据库的开发和存储为开发创造了比较好的条件,所以在软件方面完全具有可行性。硬件方面:随着科学技术的发展,硬件发展速度突飞猛进,如今的硬件设备完全能满足系统在硬件方面的需求。
    2.2 系统开发技术系统设计基于B/S结构体系,前台采用JSP和JavaScript技术,后台运用目前主流的三层架构,数据库为ORACLE,开发工具是My Eclipse 8.5,服务器是Tomcat。
    2.2.1 B/S模式B/S(Browser/Server,浏览器/服务器)模式又称B/S结构。它是随着Internet技术的兴起,对C/S模式应用的扩展。B/S模式维护运行都比较简便,能实现从不同的地点,不同的人员,以不同的接入方式访问和操作共同的数据;但它最大的缺点是对网络的依赖性太强,这导致在没有网络的情况下是没有办法访问和操作数据。
    随着www和Internet的流行,以往的C/S模式完全无法满足全球网络互连、开放、信心共享的要求,就在此时B/S模式(浏览器/服务器)渐渐出现,它最大的特点是用户可以通过自己本机的浏览器去访问Internet上的数据、图像、动画、文本、视频点播和声音信息,这些信息都是从许多个web服务器应运而生,然而Web服务器可以通过各种各样的方式与数据服务器相连,一般数据库中都存着海量的数据。客户端除了WWW浏览器,一般无须任何用户程序,只需从Web服务器上下载程序到本地来执行,在下载过程中若遇到与数据库有关的指令,由Web服务器交给数据库服务器来解释执行,并返回给Web服务器,Web服务器又返回给用户。在这种结构中,将许许多多的网连接到一块,形成一个巨大的网,即全球网。而各个企业可以在此结构的基础上建立自己的Intranet。
    2.2.2 JSP技术JSP网页是由传统网页HTML文件中加入JSP标记和Java程序段构成。JSP(Java Server Page)是由Sun公司提出、多个互联网公司一起参与合作而建立的一种动态网页开发技术的标准。JSP规范是中间件应用服务器、Web服务器、交易系统以及软件开发工具厂商间广泛合作的成果,这种技术为创建一个动态的Web网页提供了非常方便的方法。JSP的设计目的是Web应用系统的构造变得更加方便、容易、快捷,而这些应用程序可以和各种中间件应用服务器、Web服务器、浏览器和各种开发工具协同工作。
    JSP继承了Java很多优点,用JSP开发动态网站十分方便,开发效率高。此外,JSP还具有强大的组件(Java Bean)支持此功能,可以方便地实现组件复用,进一步提高了开发效率。
    2.2.3 Oracle数据库Oracle是由美国甲骨文公司开发的一款数据库产品,它具有很多的优点,功能也非常强大,这导致Oracle受到了很多企业家的青睐,在系统开发中应用非常广泛。Oracle数据库的存储结构分为逻辑存储结构和物理存储结构,逻辑存储结构是用于描述Oracle内部组织和管理数据的方式;物理存储结构是用于描述Oracle外部即操作系统中组织和管理数据的方式。
    在启动Oracle数据库服务器时,实际上是在服务器的内存中创建一个Oracle实例(即在服务器内存中分配共享内存并创建相关的后台内存),然后由这个实例来访问和控制磁盘中的数据文件。Oracle有一个很大的内存快,成为全局区(SGA)。
    Oracle数据库始终保持一定数量的服务器进程,用户的请求首先被连接到一个称为“调度程序”的特殊服务进程,然后由调度程序为用户分配一个服务器进程为其提供服务。这意味着只需要使用很少的服务器进程,便可以为多个用户进程提供服务。
    2.2.4 自动生成试卷技术自动生成试卷的关键在于随机抽取试题,并保证抽取的试题不能重复,但是在数据库中如果不断频繁的抽取试题又显得效率比较低,所以问题的核心在于随机、不重复、高效,实现此技术主要有两种方法。
    第一种是利用java中随机函数,抽取出符合要求的试题,但为了保证抽取试题不重复,可以给每一道试题在数据库中加入一个mark字段,在抽取的过程中,如果该试题被抽取到,则将该试题的mark字段置为1,这样我们每抽取一次都会先判断这道试题的mark字段是否为1,如果为1,那么本次抽取无效进入下一次随机,否则抽取有效。这种方式需要在数据库中加入一个mark字段,并且每次随机时都需要判断mark字段的值,显得比较笨重,因此系统采用的是第二种方法。
    第二种方法更为可靠、高效、复杂,该算法主要分为三个步骤:第一步是得到随机抽取试题总数和题库中试题总数,然后生成一个1到题库试题总数的阵列,阵列模型可以由程序中数组构造;第二步是生成随机数将阵列打乱;第三步是根据要求抽取试题数,如果题库中试题总数为10题,需要抽取5题,那么最后结果为取打乱后阵列的前5个即可满足要求。
    第一步:生成1到题库总数阵列(假如题库中有十道试题)如表2-1所示。



    A1
    A2
    A3
    A4
    A5
    A6
    A7
    A8
    A9
    A10




    1
    2
    3
    4
    5
    6
    7
    8
    9
    10



    第二步:生成两个随机数,如3和6,此时交换索引为3和6中的内容,交换后阵列如表2-2所示。



    A1
    A2
    A3
    A4
    A5
    A6
    A7
    A8
    A9
    A10




    1
    2
    6
    4
    5
    3
    7
    8
    9
    10



    第三步:不断重复第二步就可以将阵列打乱,如果需要抽取的试题数为5,那么此时可以取(1,2,6,4,5),最后根据这些题号在数据库中进行相应查询即可。
    2.2.5 简答题批阅算法网络在线测试是很难实现主观题精准的批阅,因为在传统的人工阅卷过程中,不同的人批阅的结果也会不一致,用系统就更难实现了,这也是技术上的一大瓶颈。
    本系统实现了简答题批阅算法,算法的思想主要是计算两个字符串之间的Levenshtein 距离,Levenshtein 距离又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数,编辑操作包括插入、删除、替换操作,编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。举个简单的例子,现在比较字符串abc和abe之间的差异,算法的实现步骤如下:
    将字符串想象成下表2-3的结构。A处 是一个标记,为了方便讲解,不是这个表的内容。




    abc
    a
    b
    c




    abe
    0
    1
    2
    3


    a
    1
    A处




    b
    2





    e
    3





    计算A处的值。它的值取决于:左边的1、上边的1、左上角的0,按照Levenshtein distance的意思:上面的值和左面的值都要求加1,这样得到都是2。A处,由于是两个a相同,左上角的值加0.这样得到0+0=0,最后算出三个值,左边计算后为2,上边计算后为2,左上角计算为0,所以A处取他们里面最小的0,可用一个公式表示A处的值:min{左边值+1,上边值+1,(0,1)}。
    计算出A处值后,变成如下表2-4。B处的值根据第二步得到。




    abc
    a
    b
    c




    abe
    0
    1
    2
    3


    a
    1
    0




    b
    2
    B处




    e
    3





    依次类推得到填写完整表如下表2-5所示。




    abc
    a
    b
    c




    abe
    0
    1
    2
    3


    a
    1
    A处0
    D处1
    G处2


    b
    2
    B处1
    E处0
    H处1


    e
    3
    C处2
    F处1
    I处1



    计算相似度。先取两个字符串长度的最大值(maxlength),操作数记为A,那么两个字符串之间的相似度可表示为:1-(A/maxLength),最后得出abc和abe之间的相似度为0.666。
    现在要解决的问题是将这种思想如何用程序实现,那么需要将二维表格在程序中表示出来,解决方案是用二维数组表示上面所示二维表,通过双层for循环和一定的算法可以计算出两个字符串的距离,这样就可以比较两个字符串之间的相似度。
    3 系统的设计与实现3.1 系统功能需求分析3.1.1 功能描述
    系统角色分为:管理员、用户
    管理员输入用户名和密码登入系统
    管理员登入系统后,可以对用户基本信息进行增删改查操作
    管理员登入系统后,可以对题库进行增删改查
    管理员登入系统后,按照一定的步骤手动生成试卷,并且可以对试卷进行增删改查
    管理员登入系统后,可以对某个试卷进行授权操作
    用户注册个人信息
    输入用户名和密码登入系统
    用户登入系统后,可以对个人信息进行修改
    用户登入系统后,可以选择某一套试题进行测试
    用户登入系统后,测试完成一套试题,可以查看此套试题成绩
    用户登入系统后,测试完成一套试题,可以查看此套试题成绩排名
    用户登入系统后,测试完成一套试题,可以查看此套试题错题

    3.1.2 参与者用例图管理员用例图如图3-1所示。

    用户用例主要包括注册、登录、修改个人信息、在线测试、查看成绩、查看错题、查看排名等。用户用例图如图3-2所示。

    3.2 系统流程图本系统拥有管理员和用户两种角色,管理员登录系统后能够进行用户信息管理、题库管理、试卷管理、考试管理;用户登录后可以进行测试信息管理、成绩管理、我的测试管理、个人信息管理。系统整体结构图如图3-3所示。

    管理员登录系统后,点击用户管理系统会查出所有的用户信息,管理员可以添加用户,也可以选中一条用户信息后,对该信息进行查看详情、删除、修改等操作。用户信息管理流程图如图3-4所示。

    管理员可以对单选题、多选题、填空题、判断题、简答题进行添加,添加之前需进行题目重复验证避免相同的题目产生。管理员添加试题流程图如图3-5所示。

    管理员手动生成试卷包括很多个步骤,首先需要填写试卷的基本信息,然后点击下一步添加各个题型的题目,题目需要管理员手动添加。管理员生成试卷流程图如图3-6所示。

    添加完一套试卷后,用户是不能直接在网上进行测评的,需要管理员选中试卷点击授权,那么此套试卷才可以在网上进行测试,点击查看详情可以查看试卷的一些基本信息。管理员试卷授权流程图如图3-7所示。

    用户登录系统后,可以选中某一试卷进行测试,测试完成后点击提交,系统会根据用户答题情况来给定分数,系统实现了简答题批阅算法,所以用户测试完一套试卷后是全自动化批阅试卷的过程。用户在线测试流程图如图3-8所示。

    当用户测试完一套试题后,点击最新成绩查询,界面上会显示出用户最近一次测试的成绩,点击历史成绩查询会将用户以前参加的所有试题都罗列出来,用户也可以选择某套试卷点击查看错题,系统会显示出这套试卷的错题信息给用户参考。用户查看成绩以及错题流程图如图3-9所示。

    3.3 系统数据分析3.3.1 数据实体图用户实体包括用户Id、用户名、密码信息。用户登陆实体如图3-10所示。

    单选题实体包括单选题主键、单选题编号、单选题题目、A选项、B选项、C选项、D选项、答案、答案解析。单选题实体如图3-11所示。

    多选题试题包括多选题主键、多选题编号、多选题题目、A选项、B选项、C选项、D选项、E选项、答案、答案解析。多选题实体图如图3-12所示。

    判断题实体包括判断题主键、判断题编号、判断题题目、判断题答案、答案解析。判断题实体图如图3-13所示。

    填空题实体包括填空题主键、填空题题目、填空题编号、填空题答案、填空题答案解析。填空题实体图如图3-14所示。

    简答题实体包括主键、简答题题目、简答题编号、简答题答案、简答题答案解析。简答题实体图如图3-15所示。

    试卷实体包括试卷主键、试卷编号、试卷题目、单择题编号集、多选题编号集、填空题编号集、判断题编号集、简答题编号集。试卷实体图如图3-16所示。

    3.3.2 数据表数据库表设计在整个软件开发过程中起着至关重要的作用,一个优秀的数据库设计可以大大简化开发,提高开发效率。本系统数据库设计共13张表,下面用二维表格一一进行描述。
    用户表t_user



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [username]
    用户名
    Varchar2(30)
    N
    唯一


    [password]
    密码
    Varchar2(30)
    N



    用户信息表t_student



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [stu_id]
    用户登录名
    Varchar2(30)
    N
    外键


    [stu_name]
    用户姓名
    Varchar2(50)
    Y



    [stu_sex]
    用户性别
    Varchar2(20)
    Y



    [stu_qq]
    用户QQ
    Varchar2(20)
    Y



    单选题表t_choice



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [choice_num]
    单选题编号
    Number
    N
    唯一


    [choice_ques]
    单选题题目
    Varchar2(50)
    N



    [a]
    A选项
    Varchar2(50)
    N



    [b]
    B选项
    Varchar2(50)
    N



    [c]
    C选项
    Varchar2(50)
    N



    [d]
    D选项
    Varchar2(50)
    N



    [choice_ans]
    单选题答案
    Varchar2(5)
    N



    [answer_anlysis]
    答案解析
    Varchar2(50)
    N



    多选题表t_multi_choice



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [mc_num]
    多选题编号
    Number
    N
    唯一


    [mc_ques]
    多选题题目
    Varchar2(50)
    N



    [a]
    A选项
    Varchar2(50)
    N



    [b]
    B选项
    Varchar2(50)
    N



    [c]
    C选项
    Varchar2(50)
    N



    [d]
    D选项
    Varchar2(50)
    N



    [e]
    E选项
    Varchar2(50)
    N



    [mc_ans]
    多选题答案
    Varchar2(5)
    N



    [answer_anlysis]
    答案解析
    Varchar2(50)
    N



    判断题表t_tfng



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [tfng_num]
    判断题编号
    Number
    N
    唯一


    [tfng_ques]
    判断题题目
    Varchar2(50)
    N



    [tfng_ans]
    判断题答案
    Varchar2(50)
    N



    [answer_anlysis]
    判断题答案解析
    Varchar2(50)
    N



    填空题表t_completion



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [completion_num]
    填空题编号
    Number
    N
    唯一


    [completion _ques]
    填空题题目
    Varchar2(50)
    N



    [completion _ans]
    填空题答案
    Varchar2(50)
    N



    [answer_anlysis]
    填空题答案解析
    Varchar2(50)
    N



    简答题表t_short_answer



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [sa_num]
    简答题编号
    Number
    N
    唯一


    [sa _ques]
    简答题题目
    Varchar2(50)
    N



    [sa _ans]
    简答题答案
    Varchar2(50)
    N



    [answer_anlysis]
    简答题答案解析
    Varchar2(50)
    N



    试卷表t_paper



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [paper_num]
    试卷编号
    Number
    N
    唯一


    [paper_name]
    试卷名称
    Varchar2(50)
    N



    [dx_num]
    单选题集
    Varchar2(50)
    N



    [mul_num]
    多选题集
    Varchar2(50)
    N



    [pd_num]
    判断题集
    Varchar2(50)
    N



    [tk_num]
    填空题集
    Varchar2(50)
    N



    [sa_num]
    简答题集
    Varchar2(50)
    N



    试卷信息表t_paper_info



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [paper_num]
    试卷编号
    Number
    N
    外键


    [dx_num]
    单选题个数
    Number
    N



    [dx_value]
    单选题分值
    Number
    N



    [mul_num]
    多选题个数
    Number
    N



    [mul_value]
    多选题分值
    Number
    N



    [pd_num]
    判断题个数
    Number
    N



    [pd_value]
    判断题分值
    Number
    N



    [tk_num]
    填空题个数
    Number
    N



    [tk_value]
    填空题分值
    Number
    N



    [sa_num]
    简答题个数
    Number
    N



    [sa_value]
    简答题分值
    Number
    N



    [total_time]
    总时间
    Number
    N



    [total_value]
    总分
    Number
    N



    [user_times]
    使用次数
    Number
    N



    [status]
    是否授权
    Number
    N



    标准答案表t_stanswer



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [paper_num]
    试卷编号
    Number
    N
    外键


    [dx_answer]
    单选题答案
    Varchar2(50)
    N



    [mul_answer]
    多选题答案
    Varchar2(50)
    N



    [pd_answer]
    判断题答案
    Varchar2(50)
    N



    [tk_answer]
    填空题答案
    Varchar2(50)
    N



    [sa_ answer]
    简答题答案
    Varchar2(50)
    N



    答卷表t_answer



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [paper_num]
    试卷编号
    Number
    N
    外键


    [stu_id]
    用户id
    Varchar2(30)
    N
    外键


    [dx_answer]
    单选题答案
    Varchar2(50)
    N



    [mul_answer]
    多选题答案
    Varchar2(50)
    N



    [pd_answer]
    判断题答案
    Varchar2(50)
    N



    [tk_answer]
    填空题答案
    Varchar2(50)
    N



    [sa_ answer]
    简答题答案
    Varchar2(50)
    N



    成绩表t_grade



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [paper_num]
    试卷编号
    Number
    N
    外键


    [stu_id]
    用户id
    Varchar2(30)
    N
    外键


    [dx_value]
    单选题得分
    Number
    N



    [mul_value]
    多选题得分
    Number
    N



    [pd_value]
    判断题得分
    Number
    N



    [tk_value]
    填空题得分
    Number
    N



    [sa_ value]
    简答题得分
    Number
    N



    [total]
    总分
    Number
    N



    错题表t_error



    列名
    中文含义
    数据类型
    空值
    注释




    [id]
    Id
    Number
    N
    主键


    [paper_num]
    试卷编号
    Number
    N
    外键


    [stu_id]
    用户id
    Varchar2(30)
    N
    外键


    [dx_answer]
    单选题错题集
    Varchar2(50)
    N



    [mul_answer]
    多选题错题集
    Varchar2(50)
    N



    [pd_answer]
    判断题错题集
    Varchar2(50)
    N



    [tk_answer]
    填空题错题集
    Varchar2(50)
    N



    [sa_ answer]
    简答题错题集
    Varchar2(50)
    N



    3.4 系统模块实现3.4.1 注册、登录页面本系统对用户来说需要先注册,然后才能登录系统在线测试。而对于管理员来说可以直接登录系统进行一系列的管理和操作。注册页面如图3-17所示。
    用户注册页面

    用户登录页面

    管理员输入正确用户名和密码登录成功后,显示主页面如图3-19所示。

    用户输入正确用户名和密码登录成功后,显示主页面如图3-20所示。

    3.4.2 管理员用户管理模块管理员登录成功后,可以对用户进行管理,主要包括分页查看所有用户信息、对某一用户进行修改操作、删除选中用户、查看某一用户详细信息。分页查看所有用户信息,删除选中用户页面如图3-21所示。

    对某一用户进行修改操作页面如图3-22所示。

    查看选中用户详细信息如图3-23所示。

    3.4.3 管理员题库管理模块管理员登录系统后,可以往题库中添加不同题型的题目,题型包括选择题、多选题、判断题、填空题、简答题。系统将五种题型的添加页面集成到一个主页面,使得题型添加时更加清晰、直观,主页面如图3-24所示。

    在添加题目操作的过程中,有时难免会出现不合法的操作,例如:题目为空、单选题的某个选项为空、单选题的选项出现重复等,当出现这些操作时,系统会响应相应的触发条件通过ajax请求后台,后台会处理这些操作给管理员提示不合法的操作信息,使界面更加友好。不合法操作如图3-25所示。

    管理员将题目添加到题库后可以查看不同题型的题目列表,同样,系统将多种题型的题目查找集成在一个主页面,通过这个主页面,管理员可以对某一具体题目进行查看、删除操作。查看多种题型题目列表主页面如图3-26所示。

    查看某一具体题目详细信息如图3-27所示。

    3.4.4 管理员试卷管理模块管理员在系统中可以自动生成试卷也可以手动添加试卷,试卷添加分为六个步骤,第一个步骤是试卷的基本信息添加,主要包括试卷的名称、测试时间、题型分配情况等一些基本信息,剩下步骤为试卷各种题型题目选择性的添加,题目来源于系统题库中,试卷添加第一个步骤如图3-28所示。

    某种题型题目添加页面如图3-29所示。

    当试卷基本信息和各种题型题目添加完成后,会请求后台程序,此时后台需要将试卷的基本信息、试卷题型题目信息、试卷标准答案录入到数据库表中,这涉及到试卷表、试卷信息表、标准答案表这三张表,在试卷表中需要将各种题型的题目存入数据库中,这就需要将单个题型的题目编号集以某种规则存入数据库表中,当程序读取这些数据时,也需要按照同样的规则进行解码,将特殊字符串转换成某种题型题目的编号集。
    管理员可以对试卷进行管理,主要包括分页查询所有试卷、查看详细信息、删除等操作,试卷分页查询和删除页面如图3-30所示。

    查看页面详细信息如图3-31所示。

    3.4.5 管理员测试管理模块管理员添加完一套试卷后,用户是不能直接在浏览器上进行测试的,需要管理员对该套试题进行授权操作,用户才能进行测试,试卷授权页面如图3-32所示。

    3.4.6 用户我的测试管理模块当用户点击我的测试模块中在线测试,会展示所有可以进行测试的试题,当然同一个人只能测试某套试卷一遍,当再次操作时,会提示不能对试卷进行重复测试,在线测试试题列表如图3-33所示。

    当用户点击在线测试试题列表中开始考试,用户便可以开始一次测试,测试完成后,系统会自动的为这次测试给出理论上的评分。在线测试页面如图3-34所示。

    当用户测试完一套试卷后,点击提交试卷,如果存在没有作答的题目,系统会给出哪种题型第几题没有作答提示,全部作答完成后试卷便会提交到后台进行处理, 首先后台需要将用户的答案保存在答题表中;其次也是最重要的需要将用户答案与后台数据库中标准答案进行比对,然后得出用户每个题型的得分,并将结果存入到成绩表中;最后根据用户所提交的答案与后台标准答案比对后,将错题信息添加到错题表中,对于错题信息应包括错题题号、用户答案、标准答案、答案解析等基本信息。在开发本系统的过程中,存在着一大难点是客观题的匹配算法,一般主观题通过系统进行匹配很少出现误差的情况,而主观题不然,如今简答题的答案不是固定不变,用户答题可以从多个角度进行阐述,只要合理即可,所以给这方面的系统带来了很大的难度,主观题的批阅给出的分数只能是一个近似值,很难达到精确的地步,这类系统在开发过程中主观题一般设计为人工阅卷与系统阅卷相结合。
    本系统实现了简答题批阅的算法,但是也只能是一个近似值,该算法首先是由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance,算法的实现原理为:两个字符串之间,由一个字符串转换成另一个字符串所需要的操作数,对字符串的操作包括插入、删除、替换,最后两字符串相似度可以表示为1-操作数/两字符串中较长字符串字符数。算法实现如下:
    public static float levenshtein(String str1,String str2) { //计算两个字符串的长度。 int len1 = str1.length(); int len2 = str2.length(); //建立一个二维数组用于计算操作数 int[][] dif = new int[len1 + 1][len2 + 1]; //对数组赋初值 for (int a = 0; a <= len1; a++) { dif[a][0] = a; } for (int a = 0; a <= len2; a++) { dif[0][a] = a; } //计算两个字符是否一样,计算左上的值 int temp; for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++) { if (str1.charAt(i - 1) == str2.charAt(j - 1)) { temp = 0; } else { temp = 1; } //取三个值中最小的 dif[i][j] = min(dif[i - 1][j - 1] + temp, dif[i][j - 1] + 1, dif[i - 1][j] + 1); } } //计算相似度 float similarity = 1 - (float) dif[len1][len2] / Math.max(str1.length(), str2.length()); return similarity;} //该方法计算三个整数中最大数private static int min(int... is) { int min = Integer.MAX_VALUE; for (int i : is) { if (min > i) { min = i; } } return min; }
    3.4.7 用户成绩查询模块当用户点击成绩查询模块中最新成绩查询,系统会查出用户最近一次的测试信息,查看最新成绩页面如图3-35所示。

    同样当用户点击历史成绩查询时,会查出用户以前所有测试成绩列表。

    3.4.8 用户测试信息管理模块当用户点击测试信息中成绩排行时,用户可以很清楚的知道自己测试过的试题在所有测试过人中的排名,成绩排行页面如图3-37所示。

    当用户点击测试信息中我的错题时,可以查看测试试题中自己的错题情况,从而知道自己在哪些方面存在不足,需要加强学习。错题列表如图3-38所示。

    点击错题列表中查看详情,便可以查看选中试题的错题信息,错题详情页面如图3-39所示。

    3.5 测试测试在一个软件开发周期中起着重要的作用,一个好的软件是经过专业的测试人员无数次测试才会没那么多bug,测试是为了补充系统的不足,使系统更加完美,用户的体验更好,试想一下,如果你开发的软件没有经过严格的测试,而发布在互联网进行使用,那是多么可怕的一件事,用户随意点击页面便会出现很多错误,这给用户的感觉简直就是差劲!我认为这样的软件是失败的,因此测试与开发是同等重要的,并且测试人员与开发人员必须协调好才能开发出优秀的软件。
    测试方法主要包括黑盒测试和白盒测试,但软件测试有一个致命的缺陷,即测试的不完全、不彻底性,由于任何程序只能进行少量的有限的测试,在未发现错误时,是不能说明程序中没有错误。
    测试用例如下:

    管理员输入用户名:admin,密码:admin登入系统,点击用户管理系统,查看用户列表是否显示成功;在用户列表中点击修改、查询、删除操作看是否能够执行
    管理员输入用户名:admin,密码:admin登入系统,点击题库管理,查看题库列表是否显示成功;选择不同的题型后,查看题目列表是否显示成功;在题目列表中点击查看,是否能够显示题目的详情
    管理员输入用户名:admin,密码:admin登入系统,点击试卷管理,填写试卷基本信息后,看是否能够自动生成试卷,点击手动生成试卷看是否能添加成功
    管理员输入用户名:admin,密码:admin登入系统,点击试卷授权功能,当给某一试卷授权后,检测用户登入系统是否能够对该套试卷进行测试
    用户点击注册,填写用户名:123456,密码123456进行注册,注册完成后点击登录,看是否能够正常登录
    用户输入:123456,密码:123456登入系统,点击在线测试,选择试题后,看是否能够在线考试
    用户输入:123456,密码:123456登入系统,点击查看成绩看是否能够查看当前考试的成绩

    另外在系统设计和测试的过程中,发现系统存在以下几方面的不足:

    系统如果需要做的更完美,应该可以在线测试多个科目的试题
    在主观题的批阅过程中,始终无法达到精确的地步,所以算法需要做进一步改进优化
    计算机网络测试仍然存在着局限性,比如不能对计算机网络课程中画图题、连线题进行测试,所以这是需要改进的

    4 总结与展望在这次系统的设计与开发中我遇到了很多问题,像需求不是很明确、数据库设计出现问题、软件后台开发框架不能熟练的运用、简答题的批阅算法等,每一个问题都给我带来了巨大的困惑,起初需求不是很明确,当按照任务书上功能模块进行开发时,你会发现以前写的功能模块完全没有办法实现,原因是没有好好的理解需求和认真深入分析这个系统,不明确哪些功能模块可以实现,而哪些功能模块不能实现,所以我明白软件设计过程中需求分析、概要设计是相当重要的,可以直接导致一个软件设计的成败,终于理解前辈们通过自己亲身经历写下软件工程这本书的重要性,这是留给后人最宝贵的资料。然而在数据库的设计中也给我带来了不少麻烦,刚开始我便设计数据库,才发现一个不深入了解系统的人是很难设计出数据库的,于是我花了两三天的时间,认真专研系统,理清自己的逻辑思路,按照用户注册-登录-在线测试-查看成绩-查看错题这个流程渐渐的将数据库设计完成,当然在后期中也存在了一些改动,才会有今天这个成果。刚开始系统后台框架使用的是s2sh,但在一个简单的错误调试中花费了我大量的时间,让我无法专研到系统业务逻辑的实现,于是我选择了比较直接的jsp和mvc模式开发,这大大提高了我的开发效率,所以我认为系统实现的方法应该选择合理,不然会耽搁很多时间,不能在规定的时间完成任务。
    这个系统从需求分析、数据库设计、代码开发、测试都是自己独立完成,以前没有自己独立完成一个系统,当开发完成后,心里有特别大的成就感,在遇到问题-解决问题-遇到新问题这种重复模式中,自己的能力有了很大的提高,我想程序设计者应该要勇于面对问题,并且通过自己的方式寻找解决办法,在解决问题时,你往前又迈了一大步,这对自己是一种经验的积累,同时也会让自己走得更高更远。
    参考文献[1] 吕雯雯.网上报名与在线考试系统的设计与实现[J].电脑知识与技术,2012,8(7):43-47
    [2] 魏振,王婷,宫禹.基于JSP实验系统总体设计[J].黑龙江科学,2012,6(5):20-30
    [3] 朱静.基于WEB的在线考试系统的设计与实现[J].福建电脑,2007,4(23):14-25
    [4] 郑飞.基于J2EE的医药管理系统的设计与实现[J].信息技术,2012,6(5):12-14
    [5] 唐超礼,黄友锐.基于B/S模式的教师信息管理系统的设计与实现[J].中国科技信息,2006,3(8):19-36
    [6] 吴爽.教务管理的研究与设计[J].华东师范大学,2010:10-15
    [7] 冯彦.基于JSP和Struts框架动态Web开发技术研究以及实践[J].吉林大学,2005,6(7): 33-45
    [8] 赵高丽,冀红举,宋军平.基于SQL的高校考务管理系统的设计与实现[J].河南机电高等专科报, 2006,14(2):45-78
    [9] 靳新,李莹基.JSP的网络文件管理系统的设计与实现[J].沈阳理工大学,2007,5(7),20-21
    [10] 王妍.JSP的开发技术[J].科技创新与应用,2008,3(5):31-33
    [11] 蒋毅.WEB与数据库连接技术应用对比分析 [J].计算机光盘应用,2011,11(23):16-25
    [12] 李绍静,汤玉琛,王宝盼.基于JSP技术的就业信息网的架构与实现[J].计算机与现代化,2012,6(5):12-14
    [13] 张晓孪.基于SSH的团购网站的设计与实现 [J].电子设计工程,2009,3(8):19-23
    [14] 姜南.基于JSP技术的学生信息管理系统的设计与实现[J].计算机与现化,2010,3(8):10-15
    [15] 房明.基于JSP技术的教材管理信息系统设计分析[J].山西科学,2009,6(7):33-45
    [16] 陈月霞.浅谈在线考试系统的结构与实现[J].科技风,2007,14(2):22-28
    [17] Chen, Jing M., Josef Cihlar. Retrieving leaf area index of boreal conifer forest using landsat TM images [J]. Remote Sen. Environ, 1996, 55(2): 153-162
    [18] akehurst B C, Tobacco(Second edition). New York: Longman press, 1981: 51-58
    [19] Rafael C Gonzalez, Richard E Woods, Steven L Eddins. Digital Image Processing Using MATLAB.Beijing: Publishing House of Electronics Industry, 2004: 153-222
    [20] Mckee, G.W., A coefficient for computing leaf area in hybird corn. Agron. J. 1964, 56(2): 240-241
    [21] Debayle J, Pinoli J C. Multiscale image filtering and segmentation by means of adaptive neighborhood mathematical morphology[J]. Image Processing, 2005, 3(1 l-14): 537-540
    [22] Punt T. Entropic thresholding: A new approach[J]. CGIP, 1981, 16(21): 209-320
    [23] Meyer, Elsevier Trends Division. Computer & Security. Kidington, Oxford, UK, 1982
    [24] Bonanza Drive. Database. P.O.Box 70, Park City, UT 84060, USA, 1978
    [25] James A.O’Brien. Introduction to Information Systems[M]. McGraw-Hill, 2002
    5  留言 2019-05-19 10:31:25
  • 基于WPF实现的简单绘图工具

    1、系统功能设计
    开发、测试环境

    开发环境: Visual Studio 2012 Premium运行框架:.net framework 4.5测试环境:Windows 8.1、Windows 7
    开发语言

    C#XAML

    1.1 前言当初选择这么一个软件来编写纯粹是出于对玩弄文字游戏的xx管理系统的不喜,但我们没有料想到,经实践表明,涉及实时跟踪鼠标键盘事件和实时绘图的软件编写难度远大于主要通过文字实现信息交互的xx管理系统。仅仅实现一个屏幕上图形的框选功能就让我改了六七遍代码,我的队友更是间断地找出了五个bug。当终于能够把整体功能流畅地实现时,我们对软件开发者的了解与敬意又加深了一层。
    1.2 总体功能描述当今图像处理越发普及,人们对于图像处理的需求也各不相同。而一些绘图软件存在过于复杂(如PS)或是只具备基础功能(如windows自带画图)的问题,因此我们开发一个基于Windows Presentation Foundation(WPF)的简单绘图工具。
    以下为程序的工作界面

    1.3 功能点说明


    功能类型
    功能点名称
    按键
    实现方式
    功能点描述




    基本功能
    主流类型图片载入
    Ctrl+O
    自己编写C#代码
    支持.png.jpg.gif.bmp.eps等主流图片类型的读入和加载。使用C#标准OpenFileDialog对象获得文件路径并且使用Uri读入之后转为BitmapImage,用Image控件显示


    基本功能
    图片保存
    Ctrl+S
    自己编写C#代码
    支持bmp(位图)和eps(矢量图)两种格式。使用SaveFileDialog对象获得文件路径之后用filestream输出。


    基本功能
    鼠标手绘图形
    鼠标拖框
    自己编写C#代码
    包括矩形、圆角矩形、圆、椭圆、直线、贝塞尔曲线等


    基本功能
    单击选择图形
    鼠标单击
    自己编写C#代码
    调用类接口SelectPoint来实现


    复杂功能
    框选图形
    鼠标拖框
    自己编写C#代码
    递归调用类接口SelectRect来实现


    复杂功能
    全选图形
    Ctrl+A
    自己编写C#代码
    调用类接口SelectAll来实现


    复杂功能
    多次选中图形
    Shift+鼠标拖框
    自己编写C#代码
    调用类接口MergeComposite来实现


    复杂功能
    被选择图形的闪烁

    自己编写C#代码
    通过来回设置选择的CompositeGraphic的isVisible属性来达到闪烁的目的。


    复杂功能
    删除图形
    Delete
    自己编写C#代码
    调用类接口Clear来实现


    复杂功能
    图形状态变更

    自己编写C#代码
    设置类属性DrawMode来实现。可以设置图形的边框粗细、颜色,以及内部颜色


    复杂功能
    调色板

    使用第三方库
    支持通过ARGB属性或是在图形中直接选色的方式给图形的边框和填充分别选色


    复杂功能
    拖动图形
    鼠标按住拖动
    自己编写C#代码
    调用类接口Move来实现


    复杂功能
    键盘移动
    Up,Down, Left,Right
    自己编写C#代码
    通过长按可快速移动


    复杂功能
    剪切选中图形
    Ctrl+X
    自己编写C#代码
    将选中的图形加入一个List,并从画布上删除选中的图形


    复杂功能
    复制选中图形
    Ctrl+C
    自己编写C#代码
    将选中的图形加入一个List,但不从画布上删除选中的图形


    复杂功能
    粘贴图形
    Ctrl+V
    自己编写C#代码
    对上述List中的所有成员递归调用接口 ICloneable.Clone。长按Ctrl+V可以连续粘贴


    统计功能
    图形个数统计

    自己编写C#代码
    递归调用类属性Count并将它和Label绑定


    用户体验优化
    打开和新建图片、关闭程序时的友情提醒。

    自己编写C#代码
    防止误删未完成画布中的内容。


    用户体验优化
    显示系统时间

    自己编写C#代码
    使用DateTime.Now.ToString方法


    错误处理
    载入图像时对不支持图像及无法处理的图像的抛出。

    自己编写C#代码
    若捕获异常,会弹出对话框



    2、系统总体结构2.1 概要设计按照“面向接口编程,而不是面向实现编程”的面向对象基本原则,在建立解决方案的时候我就将解决方案分成了Ccao-big-homework(UI,WPF工程项目,由我的队友邵键准同学实现,调用者)和Ccao-big-homework-core(实现,C#类库,由我实现,被调用者)。双方互不干涉,独立调试,在整个大作业过程中不曾因为接口耦合问题出现bug。
    由于采用了多种科学合理的设计模式(见下文各模块介绍),本类库在维持良好的可复用性的同时,不曾进行过任何强制类型转换,不曾进行过任何运行时类型判定,所有的多态都靠重载函数来实现,充分体现了面向对象的思想。
    3、具体实现3.1 GUI的设计GUI的设计采用了较为简洁的风格,设计完成后曾请求周边同学进行体验并对细节进行改进,力求用户体验较好。主体采用XAML语言,实现设计和功能的分离,并配之以C#的事件处理函数。程序取消了不怎么美观的窗口边框,并采取点住程序窗口任何一个位置均可拖动的方法。
    3.1.1 程序启动和关闭动画设计程序的主要窗口在开始和结束时都是通过淡入和淡出来呈现和销毁窗口,实现此效果使用了一个计时器,并让窗口的透明度随计时器而改变。下图为启动过程截图,可看见窗体还是半透明状态(请无视背景的代码)。
    3.1.2 程序启动界面的设计界面包括版本号与开发人员,“新建绘图”按钮在鼠标移上后会有高亮,点击进入绘图页面,点击“离开”按钮则直接调用Application.Current.Shutdown()函数销毁窗口。
    3.1.3 程序主体绘图界面设计主体绘图界面如下。

    上部为菜单栏,具体按钮功能见使用手册,实现了鼠标移到某个按键上时该按钮闪烁一次并且放大,同时调整整个工作框的布局。效果如下。

    左部为绘图框,选择后可使用鼠标绘制不同的图形,包括直线、圆、椭圆、正方形、长方形、贝塞尔曲线。其中贝塞尔曲线限于WPF提供的贝塞尔曲线构造函数的局限性,其必定从画布的左上角开始绘制。
    左下角为一个图标,无实际作用。
    3.1.4 程序图标的添加我们的程序可是有图标的哦!

    下面是调色板的图标。

    3.2 逻辑层主要实现解决方案Ccao-big-homework。
    3.2.1 工作窗口WorkWindow部分3.2.1.1 概述主要实现人与程序的交互,包括鼠标事件和键盘事件等。由于绘图软件基本是在一个窗口进行操作,一般使用组合而非继承的方式,因此类的结构比较扁平。类图如下。


    由于没有继承,全部实现在一个窗口里代码显得非常臃肿,因此我根据功能的不同将同一个窗口类分成了如下几个文件。

    下面逐步介绍各个文件实现的方法。
    3.2.1.2 BtnEvent实现菜单栏所有按钮功能,包括新建、打开、保存、退出、全选、复制、剪切、粘贴、样式选择按钮。实现方法是调用其他文件里的私有函数。
    3.2.1.3 FileEvent实现图片读写的相关方法。我们的程序支持使用文件夹视图来把文件保存到计算机的任意位置或是从计算机任意位置载入图片。同时还在现有图片未保存时弹出对话框提示用户要保存,防止了图片的误删,优化用户体验。

    实现画布的新建与刷新,并实现程序的退出功能。
    3.2.1.4 FishEyePanel实现上部菜单栏中当鼠标移到某个按键上时该按钮闪烁一次并且放大,同时调整整个工作框的布局。此处采用了组合的形式,FishEyePanel是一个新的类,在WorkWindow类中创建该类的对象,并布局到主窗口上。
    3.2.1.5 GraphicsOperation实现图像整体操作,包括:

    全选:调用队友提供的SelectRect接口,并把范围设置成整个画布大小的矩形,从而得到一个类型为List<CompositeGraphic>的对象,添加到selectedGraphics里
    复制:调用队友提供的Clone()函数,往clonedGraphics这个对象里添加对象
    剪切:复制的同时清空selectedGraphics
    粘贴:将clonedGraphics里的所有成员添加到总画布compositeGraphic的Children()成员中,从而实现绘制,并向右下角移动(10,10),从而和复制的原图区分开,然后清空clonedGraphics(),并再次调用复制函数(),从而实现粘贴的连续性,即复制一次可连续粘贴。下图为按下Ctrl+C后连续按下Ctrl+V的效果


    3.2.1.6 KeyBoard实现键盘的按键监控,包括:

    Ctrl+A:全选图像
    Ctrl+C:复制选中图像
    Ctrl+V:粘贴选中图像
    Ctrl+X:剪切选中图像
    Ctrl+W:关闭程序
    Ctrl+O:打开图片
    Ctrl+N:新建画布
    Ctrl+S:保存图片
    Delete:删除选中图像
    Key Up、Key Down、Key Left、Key Right:选中图像的上下左右移动
    Shift:按住时可多次增加选择已选中的图形

    3.2.1.7 MouseEvent我的工作中最难的部分,而且肩负调试队友代码的使命。

    Window_MouseLeftButtonDown事件:实现鼠标不在画布上时窗口根据鼠标的移动而拖动
    OnMouseLeftButtonDown事件:鼠标左键按下处理事件。主要记录鼠标开始移动的点startPoint,让画布捕捉到鼠标,并标记左侧的ToolBar是否选中rbSelect选择按钮
    OnMouseMove事件:鼠标移动时的处理事件。当鼠标移动且画布捕捉到鼠标的时候,若是发现rbSelect选择按钮被选中且当前有图形被选中且Shift键未被按下,那么说明这个鼠标事件需要的是移动图形,因而用虚直线实时绘制鼠标指示的移动路径;若是其他绘图按钮被选中,则说明要绘制图形,则用虚长方形或是虚直线实时绘制图形
    OnMouseLeftButtonUp事件:鼠标左键抬起的处理事件。首先判断鼠标抬起时和按下时位置是否相同。若是相同,则说明用户只是按了一下,那么不管左侧ToolBar是选中的什么,说明用户都是想选中一个图像,因此将左侧的工具条调整到rbSelect按钮,并且调用总画布compositrGraphic的SelectPoint方法,得到这个点选中的图像,将其加入selectGraphics,在这个List里面的对象,每隔0.5秒更改一次可见性,从外观看来,选中的图形会闪烁。若是鼠标移动了,且选项选中的是图形选项卡,则绘制对应的图形,并且刷新画布,并把之前实时跟踪鼠标的虚线图形删去。接下来判断选项卡是否为贝塞尔曲线选项,若是则把该点增加到贝塞尔曲线的List里,当List里的成员个数增加到4时,绘制一条贝塞尔曲线并清空该List。最后是最为复杂的rbSelect按钮,如果当时选中的图形为0,即selectGraphics为空,则说明用户想要选中他框选的图形,则调用SelectRect得到选中的所有图形,并把其加入selectGraphics中使其闪烁。若selectGraphics不为空,则说明用户想要移动选中的图形,则调用move方法移动选中的图形,并把selectGraphics清空。同时,如果整个过程中Shift键被按下且rbSelect被选中,说明用户想要增加选择图形,于是将选中的图形增加到selectGraphics里面。

    以上方法还各自特判了贝塞尔曲线绘制时的点四个点的情况。
    3.2.1.8 Paint各种图形的绘制,包括:

    直线
    长方形
    正方形

    椭圆
    圆角矩形
    贝塞尔曲线

    其中(1)~(6)的绘制方式基本相同,都是新建一个该图形的对象,然后根据传来的两个点确定图形的长宽和位置,将这个对象添加至总画布compositeGraphic,然后刷新画布。
    对于(7),绘制方式是在画布上点四个点,则出现贝塞尔曲线。
    3.2.1.9 其他零碎功能主要包括一些动画效果。启动时工具条的移入运用了ThicknessAnimation控件。为了美观我特意写了一个函数隐藏了工具条尾部的小箭头。选中图形闪烁的功能则使用了一个DispatcherTimer计时器,每过500ms就把selectGraphics的成员的可见性改变一次。图形个数统计调用总画布compositeGraphic的count属性,每隔0.5秒刷新一次。系统时间标签则使用DateTime.Now.ToString方法获取。
    3.2.2 启动窗口MainWindow部分这个窗口很简单,只实现了弹出WorkWindow和关闭窗口退出的功能。并贴了一张图,写了版本号。
    3.2.3 颜色拾取窗口StyleSettingWindow部分颜色拾取窗口我并没有花太多时间自己写,本来以为WPF自带调色板控件,结果发现没有,于是在网上找了一个扩展控件,并组合到WorkWindow类中,在按下上部菜单栏中的“样式选择”按钮时弹出。

    3.3 类库Ccao-big-homework-core-wpf的实现3.3.1概述。本类库(Ccao-big-homework-core-wpf.csproj)基于WPF,文件统一注释为”Du 2015.9”。

    由于本类库被设计作为实现图形操作的类的类库,所以各类之间比起“is-implemented-in-term-of-a“或者”has-a“来说更符合”is-a”关系,所以本类库大量使用了继承、多态、递归调用这些OO的方法,和UI相比更好的体现了这次大作业的教育目的。

    3.3.2 MyGraphic类基本的图像类。之所以声明为类而不是接口是因为有几个函数要有默认实现。
    下图是Mygraphic类的全部类图。各函数基本都是函数名自解释的,如果一眼看不出来有什么用可以参见源代码的注释。

    乍看上去,Ienumerator和Ienumerable两个接口(包括count,getenumerator,current,add,clear,dispose,reset等,可以使得这个类被像list一样用foreach遍历)看上去都不应该是MyGraphic类作为一个基本的图像类应该有的方法或者属性。事实上这里采用了Composite设计模式。为了使得单个对象和组合对象的使用具有一致性,其他库函数(以及用户)能够统一的使用组合结构中的所有对象(具体来说,使得我们不用在override各种函数的时候来写出诸如MyGraphic g as CompositeGraphic这种效率很低的运行时类型判定代码来),所以在MyGraphic类中定义了Composite类的各种接口,并且给以了默认实现(当然了由于MyGraphic不是CompositeGraphic,默认实现理应是空实现)。
    另外,MyGraphic类还实现了接口ICloneable,这个接口允许MyGraphic被深复制,因而实现了UI的复制-粘贴功能。
    IsVisible指示本图像是否会被画到画布上。
    Father指示MyGraphic类的父图像。Father类的set方法被重写来调用父图像的DisposeChildren方法来删除父图像维护列表中自己的存在。这样就能保证整个MyGraphic类是一棵树(树的顶点是由UI保存的一个Composite,事实上充当了画布的功能),为递归遍历创造了条件。所以同时,当Father被置null的时候,本图形就会因为失去一切引用而被C#的GC机制自动回收,相当于是被Dispose了。因此本程序的效率大大提高,本人的电脑在有1500个图形的时候还能够正常工作。
    SelectError指示点击一个点的时候允许多大的误差以选中一个图形。
    还有不少函数在MyGraphic类中被声明仅仅是为了作为接口被递归调用,例如SelectRect、Count属性等就是这样,当然整个程序中最重要的Draw也不例外。
    3.3.3 SingleModeGraphic类顾名思义,是整个图形有同一个drawmode的类,也是所有基本图形(basic_graphics)的基类。显然composite类由于可以add进各种drawmode的图形所以不可能能维持有同一个drawmode。

    drawmode指示一个给定border的geometry的图形如何被绘制,将调用drawmode的draw方法(见3.3.6)。
    重载了基类的SelectRect方法,当选中时返回包括自己的一个CompositeGraphic,反之返回null。
    getGeometry方法返回这个对象的border的geometry。
    3.3.4 CompositeGraphic类可以包含其他graphic的graphic的实现,或者说用windows画图打比方的话,就是那个选框功能。重载了IEnumerable和IEnumerator接口。

    首先,作为Composite设计模式的一部分,override了MyGraphic中的Composite相关的函数(isComposite, Clear, Add, MoveNext, Reset, Current),进行了事实上的操作。
    CompositeGraphic类维护一个私有的辅助list,用来保存每个成员相对于本体的左上角的相对位置。当CompositeGraphic类要执行某个操作的时候(例如Draw或者SelectRect),它就递归调用每个成员的相应操作,好像这个类不曾存在过一样(如果设置了isCombined = false,当这个标记被置true的时候整个CompositeGraphic将被视为是一个整体,类似于PPT或者Flash提供的“组合”功能)
    另外CompositeGraphic也像SingleModeGraphic一样有drawmode类型的属性backgroundmode,这是为了画出它的边界和背景色。边界总是一个矩形。
    当调用SelectRect的时候,由于递归调用的过程会产生大量很多层的CompositeGraphic(因为selectRect的原理是将所有被选中的图形改为连接到同一个Composite中,然后将这个Composite的父图形设为this),所以设计了MergeComposite,将一个Composite中的全部内容合并到另一个中,减少了递归的层次(在这种设计之下,递归最多两层),有效提高了运行效率。
    3.3.5 基本的图像类(basic_graphics)这些类都继承自SingleModeGraphic并且是Sealed类。有几个属性来记录自己的形状,并且override了getGeometry接口来确定自己的外形。如类图所显示的那样,新建一个基本的图像类是非常简单的,只需要实现getGeometry方法和Clone方法两个方法,并且定义几个足够确定图形位置和形状的属性,就能够被立刻应用到类库之中,像其他图形一样被选择、被移动、被更改颜色、被剪切、被复制、被粘贴,这充分体现了本类库良好的可扩充性。

    对于Ellipse、Line、Rectangle,WPF都提供了相应的Geometry对象来绘画,然而对于Bezier,WPF并没有提供相应的Geometry,而是提供了一个PathSegment——BezierSegment。BezierSegment只有三个参数(也就是说默认从原点开始画),第四个点需要通过设置PathFigure的起始点来确定。
    3.3.6 DrawMode类
    抽象类。指示如何绘制一个Geometry形状的Mygraphic为drawing。这个类其实做成接口也没什么问题。如果深究起来的话把绘图方式单独封装起来似乎也是一种设计模式,学名叫做Strategy模式。Strategy模式的好处也是显而易见的——如果直接将各种绘图方式以硬编码的方式写在SingleModeGraphic中,虽然可以少几个类,但是这意味着你可以在完全不破坏类库的情况下添加新的DrawMode,使得它易于切换、易于理解、易于扩展,进一步体现了作为一个类库的重要理念——可扩充性。(嘛,不过这毕竟只是大作业,虽说类库要可扩充性良好,但是也就我自己扩充我自己的类库玩罢了…但是当我发现WPF的drawing系列图像的构架思想竟然和我一开始手画的类图一模一样的时候,我心中别提有多得意了)
    3.3.7 DrawMode类的派生类
    这些类都继承自Drawmode类并且是Sealed类。有几个属性来记录自己的绘图方式,且override了draw来绘制。
    3.3.8 defaultConstant类
    储存一些常量。在GDI+中有System.Drawing.Color枚举,然而在WPF中似乎没有对应字段,为了方便类库的设计所以作为static readonly变量定义在这里,效果类似于C++的全局const。另外准备了defaultbrush(透明色)和defaultpen(黑色,宽2.0f)来作为GeometryMode的默认构造值。
    由于是常量,所以被作为partial类分散定义在drawmode的各个派生类中。
    3.3.9 DrawingUIElement类
    本来作为一个普适的类库有上面的类就已经能够绘图了;但是我所写的这个类库毕竟是个wpf类库,然而wpf的基本控件Canvas的Children只接受某种System.Windows.UIElement作为它的子成员。所以为了避免推倒重来,此处采用了Adapter(或者叫做Wrapper)设计模式,将本类库的接口类型(System.Windows.Media.Drawing)转换为我的调用方,邵键准同学的UI能够接受的参数UIElement。
    重写了OnRender方法,只要对它调用UIElement所固有的InvalidateVisual方法就能迫使控件重画,从而将drawing指示的画面显示在wpf界面上。
    3.4 类库Ccao-big-homework-core-winform的实现本类库(Ccao-big-homework-core-winform.csproj,并没有被本次大作业引用)是一开始我的队友尚未开始施工的时候编写而成的(文件注释为”Du 2015.8”),当时前期调研做的不够好,没有意识到wpf和winform不兼容,所以手贱就先写了一个winform版。Winform版实现比wpf版要早,Class Diagram也和wpf版类似,几乎所有的类名都一样,只是winform版基于GDI+,命名空间为System.Drawing,而wpf版基于WPF,命名空间为System.Windows。在此对于两个类库之间雷同的部分不再赘述。
    在本大作业中并没有用到winform类库(因为winform的界面远没有wpf美观),但是winform类库在我们往届的测试中证明是可以使用的(见github上9.6晚上的commit,hash为87ce7da,当时有一个基于VB的样例test基于winform测试了这个类库的可行性)。如果有兴趣做winform开发,可以尝试使用这个类库。
    本类库和wpf版的唯一区别在于draw的实现。在wpf版中,draw函数是一个普通的返回System.Windows.Media.Drawing的函数。然而在winform中我们采取的并不是Adapter模式,而是另一种著名设计模式Bridge模式:我们有一个接口IWindow实现drawpath和fillpath两个接口,然后draw函数接受参数IWindow,并在函数体内调用这两个接口进行绘图,没有返回值。这样做的好处在于可以将抽象部分与它的实现分离,使得双方都可以有独立的变化。无论类库将要面对怎样的UI(即便这是个WPF UI甚至是个VB项目,例如我的测试用项目),只要这个UI实现了接口IWindow,类库就可以不作任何修改的应用到这个UI上。
    以下是项目文件列表,除了IWindow外的所有类都和wpf版一样。

    4、项目总结4.1 亮点首先,由于采用了Composite模式,所以几乎所有的Composite接口都是通过递归调用来完成的,简洁明了,体现了合理设计模式的优越性。假使不采用Composite模式,那么(任举一例),SelectRect方法将不得不返回一个list<MyGraphic>,且不说这样子将使得这个类库丧失组合/打散的固有功能,而且返回list将使得对于这个list的每个操作都必须由客户端通过类似foreach MyGraphic g…这样子的调用方式手动对每个list的成员执行。现在只需要调用Composite的对应函数,就能通过递归调用在对客户隐藏的情况下执行了,体现了良好的封装。
    其次,本类库的可扩充性堪称良好。向本类库中加入新的几何图形只需要继承SingleModeGraphic并且重写getGraphic和Clone;向本类库中加入新的绘图方式只需要继承DrawMode并且重写Draw。
    4.2 OOP模式的优越性接口和实现的分离极大提高了我们的开发效率。只需要知道给接口提供什么参数,能得到什么结果就可以了,不需要知道它是如何被实现的,这样的黑箱极大地提高了我们的开发效率。
    4.3 版本控制的重要性版本控制不仅让我们能够清晰地了解自己的开发进度,而且在开发出现重大bug的时候,能够迅速地回滚到以前的版本。这样不会出现自己写了什么挂了都不知道的,结果只好全部删除重来的情况。况且,每一次在Git上的Commit都意味着我实现了新的功能,内心总能感到满足。
    4.4 自主解决困难的能力为了写大作业我估计百度了不下300次,基本开发的状态就是:想实现某功能—>发现自己不会—>百度google大法好—>会了—>实现该功能—>又想实现某功能。
    开发过程中我遇到过很多困难,举个例子:关闭窗口时我写了个淡出动画,结果发现动画刚开始运行就程序就被退出,表现为动画无法播放。百度了一个小时,终于找到解决办法:在给程序的OnClosing事件中,把close命令取消,然后坐等0.6秒动画放完后再退出程序。
    真正到开发程序才发现以前学的都是纸上谈兵,经过一份大作业的洗礼我收获了极大提升的编程技能。
    16  留言 2019-04-02 10:31:39
  • 基于Python的PyGame的俄罗斯方块游戏设计与实现

    摘 要近年来,随着游戏产业的突飞猛进,游戏玩家的技术也是与日俱增,当你看见游戏高手完美的表演时,你是否想过我也能达到那种水平,本程序用Python语言编写俄罗斯方块,左侧显示正在运行的游戏,右边显示下一个出现的形状、等级和积分等。游戏运行时随着等级的提高而速度加快,这需要玩家快速操作。因此,本程序主要意义是为高手玩家提供训练平台。其次也可为刚踏入游戏领域的新手们,有助于反应能力的提升。本程序利用Python设计实现俄罗斯方块游戏的算法、游戏内部功能、游戏区域的绘图的程序设计。对于每个俄罗斯方块各自的形态变化改变到下一个形态利用数组来实现。
    关键词:游戏;Python;俄罗斯方块;算法;功能
    第一章 绪论1.1 任务概述1.1.1 软件功能该俄罗斯方块是有Python编写而成的。它具有对游戏的正常操作,可以控制方块下落位置、下落时改变方向,以及对方块的直接下落。该游戏分左右两个界面,左边显示游戏的运行状态,右边显示游戏下一个即将出现的方块,以及游戏的等级类别和当前分数、消过得方块行数等。
    1.1.2 运行环境程序运行于Anoconda,将Anoconda安装于Windows系统上。然后在Anoconda Prompt 上安装pygame组件。
    Anaconda是一个用于科学计算的Python发行版,支持 Linux, Mac, Windows系统,提供了包管理与环境管理的功能,可以很方便地解决多版本python并存、切换以及各种第三方包安装问题。Anaconda利用工具/命令conda来进行package和environment的管理,并且已经包含了Python和相关的配套工具。
    这里先解释下conda、anaconda这些概念的差别。conda可以理解为一个工具,也是一个可执行命令,其核心功能是包管理与环境管理。包管理与pip的使用类似,环境管理则允许用户方便地安装不同版本的python并可以快速切换。Anaconda则是一个打包的集合,里面预装好了conda、某个版本的python、众多packages、科学计算工具等等,所以也称为Python的一种发行版。其实还有Miniconda,顾名思义,它只包含最基本的内容——python与conda,以及相关的必须依赖项,对于空间要求严格的用户,Miniconda是一种选择。
    进入下文之前,说明一下conda的设计理念——conda将几乎所有的工具、第三方包都当做package对待,甚至包括python和conda自身!因此,conda打破了包管理与环境管理的约束,能非常方便地安装各种版本python、各种package并方便地切换。
    Anaconda的下载页参见官网下载,Linux、Mac、Windows均支持。
    安装时,会发现有两个不同版本的Anaconda,分别对应Python 2.7和Python 3.5,两个版本其实除了这点区别外其他都一样。后面我们会看到,安装哪个版本并不本质,因为通过环境管理,我们可以很方便地切换运行时的Python版本。(由于我常用的Python是2.7和3.4,因此倾向于直接安装Python 2.7对应的Anaconda)
    下载后直接按照说明安装即可。这里想提醒一点:尽量按照Anaconda默认的行为安装——不使用root权限,仅为个人安装,安装目录设置在个人主目录下(Windows就无所谓了)。这样的好处是,同一台机器上的不同用户完全可以安装、配置自己的Anaconda,不会互相影响。
    对于Mac、Linux系统,Anaconda安装好后,实际上就是在主目录下多了个文件夹(~/anaconda)而已,Windows会写入注册表。安装时,安装程序会把bin目录加入PATH(Linux/Mac写入~/.bashrc,Windows添加到系统变量PATH),这些操作也完全可以自己完成。以Linux/Mac为例,安装完成后设置PATH的操作是
    # 将anaconda的bin目录加入PATH,根据版本不同,也可能是~/anaconda3/binecho 'export PATH="~/anaconda2/bin:$PATH"' >> ~/.bashrc# 更新bashrc以立即生效source ~/.bashrc
    配置好PATH后,可以通过which conda或conda —version命令检查是否正确。假如安装的是Python 2.7对应的版本,运行python —version或python -V可以得到Python 2.7.12 :: Anaconda 4.1.1 (64-bit),也说明该发行版默认的环境是Python 2.7。
    Pygame是跨平台Python模块,专为电子游戏设计,包含图像、声音。建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言和汇编语言)束缚。包含图像、声音。
    Pygame建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言和汇编语言)束缚。基于这样一个设想,所有需要的游戏功能和理念都(主要是图像方面)都完全简化为游戏逻辑本身,所有的资源结构都可以由高级语言提供,如Python。
    1.2 需求分析1.2.1 游戏界面
    1.2.2 菜单操作打开Anoconda Prompt,安装pygame组件。安装完成后就会显示 如图5.1所示的界面。点击Play!,将进入游戏,点击Quit将退出程序。
    1.2.3 游戏操作进入游戏后,可以用键盘上下左右键控制方块的下落方向,速度。当选好下落的位置后,按下空格键将快速下降到需要填充的地方。当游戏中方块积累的和游戏最上面相平时,此局游戏将结束。
    1.3 设计目的这款游戏设计的主要目的是为了应用自己已学过的编程语言更好的去应运到实际中,同时对小游戏的开发有助于提升自己的编程能力,还能更好的找出自身存在的一些缺陷问题,加以及时的弥补。这款游戏也能供玩家适当缓解压力,提高思维能力和反应能力。
    第二章 相关技术及开发工具2.1 python介绍Python (英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种面向对象的解释型计算机程序设计语言,由荷兰人Guido van Rossum于1989年发明,第一个公开发行版发行于1991年。
    Python是纯粹的自由软件, 源代码和解释器CPython遵循 GPL(GNU General Public License)协议。Python语法简洁清晰,特色之一是强制用空白符(white space)作为语句缩进。
    Python具有丰富和强大的库。它常被昵称为胶水语言,能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起。常见的一种应用情形是,使用Python快速生成程序的原型(有时甚至是程序的最终界面),然后对其中有特别要求的部分,用更合适的语言改写,比如3D游戏中的图形渲染模块,性能要求特别高,就可以用C/C++重写,而后封装为Python可以调用的扩展类库。需要注意的是在您使用扩展类库时可能需要考虑平台问题,某些可能不提供跨平台的实现。
    Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。
    Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法结构。
    Python 是一种解释型语言: 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。
    Python 是交互式语言: 这意味着,您可以在一个Python提示符,直接互动执行写你的程序。
    Python 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术。
    Python 是初学者的语言:Python 对初级程序员而言,是一种伟大的语言,它支持广泛的应用程序开发,从简单的文字处理到 WWW 浏览器再到游戏。
    2.2 Python 发展历史Python 是由 Guido van Rossum 在八十年代末和九十年代初,在荷兰国家数学和计算机科学研究所设计出来的。
    Python 本身也是由诸多其他语言发展而来的,这包括 ABC、Modula-3、C、C++、Algol-68、SmallTalk、Unix shell 和其他的脚本语言等等。
    像 Perl 语言一样,Python 源代码同样遵循 GPL(GNU General Public License)协议。现在 Python 是由一个核心开发团队在维护,Guido van Rossum 仍然占据着至关重要的作用,指导其进展。
    2.3 Python 特点
    易于学习:Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单
    易于阅读:Python代码定义的更清晰
    易于维护:Python的成功在于它的源代码是相当容易维护的
    一个广泛的标准库:Python的最大的优势之一是丰富的库,跨平台的,在UNIX,Windows和Macintosh兼容很好
    互动模式:互动模式的支持,您可以从终端输入执行代码并获得结果的语言,互动的测试和调试代码片断
    可移植:基于其开放源代码的特性,Python已经被移植(也就是使其工作)到许多平台
    可扩展:如果你需要一段运行很快的关键代码,或者是想要编写一些不愿开放的算法,你可以使用C或C++完成那部分程序,然后从你的Python程序中调用
    数据库:Python提供所有主要的商业数据库的接口
    GUI编程:Python支持GUI可以创建和移植到许多系统调用
    可嵌入: 你可以将Python嵌入到C/C++程序,让你的程序的用户获得”脚本化”的能力

    2.4 python开发环境构建首先下载Visual Studio Code,Anaconda安装包,安装在Windows电脑,打开Visual Studio Code导入需要的Python 库,搭建环境,建立新的项目。

    第三章 概要设计本游戏的设计采用MVC模式(Model -View -Controller),即把本游戏的输入、处理、输出流程按照Model、View、Controller的方式进行分离,这样本游戏的设计被分成三个层—模型层、视图层、控制层。MVC模型图如下所示:

    3.1 程序流程
    3.1.1 程序主流程
    3.1.2 游戏视图
    3.1.3 游戏控制流程
    3.2 模块说明3.2.1 游戏模块
    3.2.2 辅助模块通过图形工厂随机产生方块的某种状态,这里运用了工厂设计模式,这种设计模式是为了实现高内聚低耦合而存在的,这种设计模式的通俗解释就是:你想要一个产品,你需要让工厂给你生产一个,你要告诉工厂你要生产什么产品,就把你需要生产的这个产品当做参数传给工厂类的生产产品的方法,然后根据你传进来的产品,而决定工厂人生产什么产品,产生出来产品后,最后要返回您要的这个东西。

    3.3 重要数据3.3.1 存储型最高分Highscore(记录玩家最高得分)、Lines(显示本局游戏所消除的行数总和)、Score(显示本局游戏的所得分数)
    3.3.2 控制型等级Levels(根据不同等级调整游戏的不同速度)。
    第四章 详细设计4.1 界面设计4.1.1 窗口创建定义矩阵的宽、高、边框宽度、块大小然后计算出页面的宽度、高度。
    页面宽度:
    WIDTH = MATRIX_WIDTH*BLOCKSIZE + BORDERWIDTH*2 + MATRIS_OFFSET*2 + LEFT_MARGIN
    页面高度:
    HEIGHT = (MATRIX_HEIGHT-2)*BLOCKSIZE + BORDERWIDTH*2 + MATRIS_OFFSET*2
    可见矩阵宽度:
    VISIBLE_MATRIX_HEIGHT = MATRIX_HEIGHT - 2
    4.1.2 菜单设计定义了Menu类,定义main(self, screen)函数,设置“Play!”“Quit”两个菜单,对其进行绘制,着色,当点击“Play!”时,游戏开始;点击“Quit”时,游戏结束。定义construct_highscoresurf(self)来显示游戏最高得分。
    4.1.3 区域着色定义fo_surf (self)函数,有矩阵和矩阵的边框进行着色。定义construct_nightmare(size)函数,对菜单背景进行随机颜色设置。
    4.1.4 方块设计方块共设计了7中形状,分别设置不同的颜色,分别为“(blue)一字形”、“(red)Z字形”、“(green)反Z字形”、“(pink)反T字形”、“(orange)7字形”、“(cyan)反7字形”、“(yellow)田字形”。
    4.2 常量变量4.2.1 常量页面的大小,游戏行列格数目,不同方框的颜色是不变的。
    4.2.2 变量随机下落的方块形状,游戏的等级Level、消掉的行数Lines 、游戏的得分Score,最高分数Highscore。
    4.3 重要函数4.3.1 游戏状态定义renderpair(text, val)函数,当游戏行数消除时,右边的Score、Level、Lines、Combo都有一定的变化。
    4.3.2 游戏控制定义了Menu类,定义main(self, screen)函数,设置“Play!”“Quit”两个菜单,对其进行绘制,当点击“Play!”时,游戏开始;点击“Quit”时,游戏结束。
    4.4 重要算法4.4.1 判定定义lock_tetromino(self)函数,当游戏“死亡”之后,所有方块都会被记录,并且清除所有方块,会回到菜单界面。
    4.4.2 变换定义了request_movement(self, direction)函数,当玩家在键盘上按上下左右不同的键时,游戏界面的方块会有所变化。
    第五章 调试与测试5.1 调试分析游戏在编辑过程中遇到了很多的Bug,已经解决了大部分发现的,可能尚存在部分Bug。
    5.1.1 问题与解决游戏第一版出现偶尔闪退现象。经过调试,现在已经找到问题所在,并予以修改Bug。
    5.1.2 性能分析
    “控制”菜单测试

    点击“游戏”—>“Play!”
    输出:游戏开始结果:正常
    点击“游戏”—>“Quit”
    输出:控制菜单退出结果:正常

    “游戏”菜单测试

    游戏消除一行—>“Score”
    输出:在游戏面板右侧分数面板增加100结果:正常
    游戏消除一行—>“Lines”
    输出:在游戏面板右侧的消除行数增加1结果:正常
    游戏每消除1500分—>“Level”
    输出:在游戏面板右侧的等级加1结果:正常


    5.1.3 程序不足经过测试,本游戏实现了基本的功能,不过整体功能还不是很强大,作为开发的第一个版本,游戏在很多方面还需要进一步完善。
    5.2 测试结果5.2.1 游戏截图

    第六章 结论俄罗斯方块从最初的简单的黑白画面发展到后期追求图像的华丽,一直不断有人们的优化和改进。为了满足大众用户精神娱乐的需求,基于Python语言的俄罗斯方块游戏的设计,基本上已经达到了预期的目标。分析了俄罗斯方块游戏开发中Python语言程序算法、逻辑设计等一些关键技术,实现了游戏过程中游戏的消行和重得分的设置,该应用程序的主要功能已基本完善,是一款值得推荐的益智游戏。
    在系统分析阶段,针对俄罗斯方块游戏,初步地定下了要实现的功能,并将整个游戏分成若干个部分,逐步解决过程中遇到的每一个问题,最终实现所想要的效果。此程序的用户界面已基本实现了俄罗斯方块游戏的一些基本的控制操作,但由于时间和能力有限,程序难免存在许多不足之处,很多功能还没有完全实现。若要实现游戏的扩展,设计出更加具有可玩性和好玩性的俄罗斯方块游戏,就要靠以后更多和更深入的学习,对此程序进行进一步的完善。
    参考文献[1] Michael Dawson. Python编程初学者指南[M]. 人民邮电出版社, 2014.10.
    [2] Mark Lutz. Python编程(第四版)[M]. 中国电力出版社,2014.12.
    [3] Jonathan Harbour. 人民邮电出版社[M]. 人民邮电出版社,2015.1.
    [4] Robert Sedgewick Kevin Wayne[M]. 人民邮电出版社,2012.10
    4  留言 2018-11-12 14:57:11
  • 基于JAVA和MYSQL数据库实现的小型公司工资管理系统

    第一章 需求分析1.1 功能要求1.1.1 功能概况本次设计要求运用面向对象设计知识,利用 JAVA 语言设计实现一个“小型公司工资管理系统”。
    1.1.2 存储功能能够存储雇员的编号、姓名、性别、所在部门,级别,并进行工资的计算。 其中,雇员级别分为经理、技术人员、销售人员和销售经理。四类人员的月薪计算方法如下:经理拿固定月薪;技术人员按小时领取月薪;销售人员按其当月销售额的提成领取工资;销售经理既拿固定月薪也领取销售提成。由此要求,该项目需要利用数据库的关系表存储数据。
    1.1.3 添加功能程序能够任意添加上述四类人员的记录,可提供选择界面供用户选择所要添加的人员类别,要求员工的编号要唯一,如果添加了重复编号的记录时,则提示数据添加重复并取消添加。需要利用 Java 语言连接数据库,使用数据库添加语句。
    1.1.4 查询功能可根据编号、姓名等信息对已添加的记录进行查询,如果未找到,给出相应的提示信息,如果找到,则显示相应的记录信息。利用 Java Swing 控件进行展示。
    1.1.5 编辑功能可根据查询结果对相应的记录进行修改,修改时注意编号的唯一性。
    1.1.6 删除功能主要实现对已添加的人员记录进行删除。如果当前系统中没有相应的人员记录, 则提示“记录为空!”并返回操作;否则,输入要删除的人员的编号或姓名,根据所输入的信息删除该人员记录,如果没有找到该人员信息,则提示相应的记录不存。
    1.1.7 统计功能能根据多种参数进行人员的统计。例如,统计四类人员数量以及总数,或者统计男、女员工的数量,或者统计平均工资、最高工资、最低工资等信息。利用数据库语句完成统计。
    1.1.8 界面功能利用 java 的 GUI 功能设计系统的图形用户界面,要求界面美观,易用。
    第二章 概要设计2.1 系统分析2.1.1 架构设计本系统采用架构如下图所示,主要分为三个模块:用户操作界面、身份认证中心和用户数据库。其中,用户操作界面为最顶层功能,便于用户选择以添加、修改、 删除、查询统计公司信息;身份认证中心便于鉴别不同用户,以实现映射到不同的数据库(表);用户数据库中保存用户个人存储的信息,同时实现最底层功能,即各项功能的基本操作。

    2.1.2 实体设计登陆实体作为程序的进入窗口,用户登陆时,系统会要求用户输入账号和密码,并在数据库中进行匹配分析,若用户名存在且密码正确则通过身份认证,同时映射到用户 个人的数据表中,不同用户对应不同数据表存储相关信息。
    主界面实体登录成功后的功能选择界面,包含添加、查询、显示、统计四个功能,其中,对于查询或显示的信息可直接选择并完成修改删除功能。
    添加功能实体从主界面中选择进入,负责接收用户输入的员工信息,判断员工编号是否重复、录入的员工信息格式是否正确,并将符合要求的数据项其上传至数据库,完成添加员工信息的操作。
    查询功能实体从主界面中选择进入,按照用户选择的查找方式和具体查找要求,从数据库中筛选符合条件的信息并以表格形式显示,如果未查询到相关信息会给以提示。
    修改功能实体从查询功能实体进入。查询结果会以表格的形式显示在用户面前,用户可以双击某项查询结果进入编辑界面完成修改功能,同添加功能类似,系统会判断用户修改的编号是否已经重复、修改的信息是否格式正确,并将符合要求的信息在数据 库中完成更新修改操作。
    删除功能实体从查询功能实体进入。查询结果会以表格的形式显示在用户面前,用户可以双击某项查询结果进入编辑界面,在编辑界面中可选择删除此信息,删除完成后将会予以反馈。
    统计功能实体自动统计以下内容:公司整体信息(职位分布、部门人数分布等)、各部门详细信息(男女比例、工资情况、年龄情况、职位分部等),并可由用户自行选取具体部门、具体级别并显示统计结果,所有统计结果均自动绘图显示。

    2.1.3 分层设计数据库驱动层(数据层)数据库驱动层为最底层,主要以各种 JDBC 数据库驱动语句以及各种算法组成,完成添加、修改、删除、查询、统计分析的基本功能,并完成添加修改过程中的编号查重、格式判断等异常判断。各功能成功或失败情况将传递给上层,同时,异常情况也将分类传递给上层。
    异常处理层(功能层)主要分类处理数据库反馈的结果,例如查询成功失败、添加的编号重复、修改成功等操作的反馈结果。并处理来自底层抛出的异常,捕获后生成提示信息传递给上一层。
    用户界面层(表示层)接受来自异常处理层的处理结果:对于异常,采用消息框提示的方式展示给用户;对于查询结果,以表格的形式显示在界面上;对于统计结果,自动绘制图表显示给用户;其他提示信息同样采用消息框给用户反馈。同时用户可以直接在界面上选择相应功能,底层会自动实现相应操作并反馈给用户界面层。

    2.2 程序流程2.2.1 系统流程图
    2.2.2 调用关系运行程序后,首先进入用户登陆界面,用户名与密码不匹配则登陆失败,成功则进入用户操作主界面。
    在用户操作主界面中有四个按钮供用户选择,分别是:添加记录、查询记录、显示信息、统计整理,点击不同按钮跳转至不同功能实现界面。
    添加信息模块允许用户添加新信息,系统会判断编号是否重复、各式是否正确。若通过审核则将此信息添加至数据库中,询问用户是否继续添加。如果退出则重新返回主界面。
    查询信息模块允许用户按个人需求查询相关信息,如:职员编号、姓名、性别、级别、部门等。查询得到的信息会显示在表格中,用户可以点击表格中具体某一项选择编辑信息,在编辑模块中可以修改或删除选中的信息。
    显示信息模块与查询功能类似,显示的内容会自动放入表格中同时允许编辑修改。 统计整理模块会将统计得到的信息以图表形式显示给用户,同时允许用户自行查询想要统计的信息并自动绘制图表。
    第三章 详细设计3.1 功能类定义与实现3.1.1 常量与参数的定义(Parameter 类)JDBC 数据库连接所用参数与端口JDBC(Java Data Base Connectivity,java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 为数据库开发人员提供了一个标准的 API,据此可以构建更高级的工具和接口,使数据库开发人员能够用纯 Java API 编写数据库应用程序,并且可跨平台运行,并且不受数据库供应商的限制。
    本系统采用的是MySQL数据库,编程语言为Java语言,编译器为Eclipse Oxygen,Java连接数据库时需要用到JDBC驱动器、数据库URL以及数据库管理员账号密码等。
    根据数据库的 URL、用户名和密码,创建一个 JDBC Connection 对象。如: Connection connection = DriverManager.geiConnection(“连接数据库的 URL”, “ 用户名”, “密码”)。其中,URL=协议名+IP 地址(域名)+端口+数据库名称;用户名 和密码是指登录数据库时所使用的用户名和密码。示例创建 MySQL 的数据库连接代码如下:
    Connection connectMySQL = DriverManager.geiConnection("jdbc:mysql://localhost:3306/myuser","root" ,"root"); public static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; public static final String DB_URL = "jdbc:mysql://localhost:3306/java?useSSL=true&characterEncoding=UTF8"; public static final String ACC = "root"; public static final String PWD = "zxczxc";
    职员级别对应代号本系统中将职员级别分为四种:经理、技术人员、销售人员、销售经理,其中经理拿固定月薪;技术人员按小时领取月薪;销售人员按其当月销售额的提成领取工资;销售经理既拿固定月薪也领取销售提成。若想方便统计需要将对应职位转换成相应的代号。此处经理对应 int 型数字 1,技术人员对应 2,销售人员对应 3,销售经理对应 4.具体如下:
    public static final int Manager = 1; //经理 public static final int Engineer = 2; //技术人员 public static final int Salesman = 3; //销售人员 public static final int SalesManager = 4; //销售经理
    工资自动计算所需常量由于经理拿固定月薪;技术人员按小时领取月薪;销售人员按其当月销售额的提成领取工资;销售经理既拿固定月薪也领取销售提成。若想方便计算工资,必须提前订好各种常量,如:固定的月薪、工作每小时的工资、销售额提成比例等,具体如下:
    public static final int BASE = 5000; //底薪 public static final int HOUR = 80; //每小时工资 public static final double Parameter = 0.015; //提成比例
    3.1.2 职工个人信息的定义(Person 类)职工个人信息职工个人信息包括:职工编号(唯一)、姓名、性别、年龄、电话号码、所属部 门、工作级别、月工作时间、月销售总额、月工资。其中月工资不可显式输入,自动由程序结算得到。通过构造函数直接创建对象,具体如下:
    public Person(String ID,String name,String sex,int age,String phone, String department,int kind,int hours,double sells) { this.ID = ID; this.name = name; this.sex = sex; this.age = age; this.phone = phone;this.department = department; this.kind = kind; this.hours = hours; this.sells = sells;}
    通过此方法得到私有成员变量:
    public String getID() { return ID; }
    职工工资计算方法经理拿固定月薪;技术人员按小时领取月薪;销售人员按其当月销售额的提成领取工资;销售经理既拿固定月薪也领取销售提成。具体计算方法如下:
    switch (kind) { case 1: this.salary = Parameter.BASE; break; case 2: this.salary = Parameter.HOUR*hours; break; case 3: this.salary = Parameter.Parameter*sells; break; case 4: this.salary = Parameter.BASE + Parameter.Parameter*sells; break; default: return; }
    3.1.3 数据库查询与底层算法(JDBC 类)JDBC 连接数据库在开发环境中加载指定数据库的驱动程序。使用的数据库是MySQL,所以需要下载MySQL支持JDBC的驱动程序(mysql-connector-java-5.1.18-bin.jar);而开发环境是Eclipse,将下载得到的驱动程序加载进开发环境中。
    在Java程序中加载驱动程序。在Java程序中,可以通过 “Class.forName(“指定数据库的驱动程序”)” 方式来加载添加到开发环境中的驱动程序,例如加载MySQL的数据驱动程序的代码为:
    Class.forName(“com.mysql.jdbc.Driver”);
    创建数据连接对象:通过DriverManager类创建数据库连接对象Connection。DriverManager类作用于Java程序和JDBC驱动程序之间,用于检查所加载的驱动程序是否可以建立连接,然后通过它的getConnection方法,根据数据库的URL、用户名和密码,创建一个JDBC Connection 对象。如:
    Connection connection = DriverManager.geiConnection(“连接数据库的URL", "用户名", "密码”)
    其中,URL=协议名+IP地址(域名)+端口+数据库名称;用户名和密码是指登录数据库时所使用的用户名和密码。
    创建Statement对象:Statement 类的主要是用于执行静态 SQL 语句并返 回它所生成结果的对象。通过Connection 对象的 createStatement()方法可以创建一个Statement对象。例如:
    Statement statament = connection.createStatement();
    具体示例创建Statement对象代码如下:
    Statement statamentMySQL =connectMySQL.createStatement();
    调用Statement对象的相关方法执行相对应的 SQL 语句:通过execuUpdate() 方法用来数据的更新,包括插入和删除等操作,例如向staff表中插入一条数据的代码:
    statement.excuteUpdate("INSERT INTO staff(name, age, sex,address, depart, worklen,wage)" + " VALUES ('Tom1', 321, 'M', 'china','Personnel','3','3000' ) ");
    通过调用Statement对象的executeQuery()方法进行数据的查询,而查询结果会得到 ResulSet对象,ResulSet表示执行查询数据库后返回的数据的集合,ResulSet对象 具有可以指向当前数据行的指针。通过该对象的next()方法,使得指针指向下一行,然后将数据以列号或者字段名取出。如果当next()方法返回null,则表示下一行中没有数据存在。使用示例代码如下:
    ResultSet resultSel = statement.executeQuery( "select * from staff" );
    关闭数据库连接:使用完数据库或者不需要访问数据库时,通过Connection的 close() 方法及时关闭数据连接。
    身份认证对于用户,需要先要求输入账号密码,并将账号密码传入数据库身份鉴别表进行匹配,若用户名密码均匹配成功,则映射到用户创建的表中。传入参数为账号和密码,返回判断情况:返回1为账号不存在、返回2为密码不正确、返回3为出现数据库连接异常、返回0则认证成功。
    public static int LoginCharge(String acc,String pwd) { try { Class.forName(Parameter.JDBC_DRIVER); Connection conn = DriverManager.getConnection(Parameter.DB_URL, Parameter.ACC, Parameter.PWD); Statement stat = conn.createStatement(); String query = "select * from `" + Parameter.COMPANY + "` where acc=\"" + acc + "\""; ResultSet rs = stat.executeQuery(query); if (!rs.next()) return 1;//账号不存在 String pwd2 = rs.getString("pwd"); if(!pwd2.equals(pwd)) return 2;//密码不正确 String table = rs.getString("table"); Parameter.TABLE = table; return 0; }catch (Exception e) { e.printStackTrace(); return 3; }
    插入信息模块中,需要判断插入的编号是否重复,如果重复则不允许插入,函数 rs.next()如果返回结果不为空,则说明已经存在此编号。输入信息为Person对象,返回判断结果:返回1为编号已存在,返回2为数据库连接异常,返回0为插入成功,具体如下:
    public static int insertIntoSql(Person p) { try { Class.forName(Parameter.JDBC_DRIVER); Connection conn = DriverManager.getConnection(Parameter.DB_URL, Parameter.ACC, Parameter.PWD); Statement stat = conn.createStatement(); // 判断是否已经存在编号 String query1 = "select * from `" + Parameter.TABLE + "` where no=\"" + p.getID() + "\""; ResultSet rs = stat.executeQuery(query1); if (rs.next()) { return 1; } String query = "INSERT INTO `" + Parameter.TABLE + "` VALUES (\"" + p.getID() + "\",\"" + p.getName() + "\",\"" + p.getSex() + "\",\"" + p.getAge() + "\",\"" + p.getPhone() + "\",\"" + p.getDep() + "\",\"" + p.getKind() + "\",\"" + p.getHours() + "\",\"" + p.getSells() + "\",\"" + p.getSalary() + "\")"; stat.executeUpdate(query); return 0; } catch (Exception expt) { return 2; } }
    从数据库中删除信息删除信息模块中,需要删除的编号是否存在,如果不存在则不允许删除,函数 rs.next()如果返回结果为空,则说明此编号对应的职员不存在。输入信息为职员的 编号ID,返回判断结果:返回1为编号不存在,返回2为数据库连接异常,返回0为插 入成功,具体如下:
    public static int deleteFromsql(String id) { try { Class.forName(Parameter.JDBC_DRIVER); Connection conn = DriverManager.getConnection(Parameter.DB_URL, Parameter.ACC, Parameter.PWD); Statement stat = conn.createStatement(); String query1 = "select * from `" + Parameter.TABLE + "` where no=\"" + id + "\""; ResultSet rs = stat.executeQuery(query1); if (!rs.next()) { return 1; } String query2 = "delete from `" + Parameter.TABLE + "` where no=\"" + id + "\""; stat.executeUpdate(query2); return 0; } catch (Exception expt) { return 2; } }
    从数据库中查询信息查询信息模块中,具体分为按照ID查询和按照其他条件查询,具体差别为ID唯一,至多只能查找到一条信息,而按照其他条件查询可能会返回多条信息,因此需要区 别处理,输入信息为职员的信息,返回值为查询结果,为空则查询失败具体如下:
    public static String searchByID(String id) {//按照ID查询 try { Class.forName(Parameter.JDBC_DRIVER); Connection conn = DriverManager.getConnection(Parameter.DB_URL, Parameter.ACC, Parameter.PWD); Statement stat = conn.createStatement(); String query1 = "select * from `" + Parameter.TABLE + "` where no=\"" + id + "\""; ResultSet rs = stat.executeQuery(query1); if (!rs.next()) { return null; } String name = rs.getString("name"); String sex = rs.getString("sex"); int age = rs.getInt("age"); String phone = rs.getString("phone"); String department = rs.getString("department"); int kind = rs.getInt("kind"); int hours = rs.getInt("hours"); double sells = rs.getDouble("sales"); double salary = rs.getDouble("salary"); String result = id + "@" + name + "@" + sex + "@" + age + "@" + phone + "@" + department + "@" + kind + "@" + hours + "@" + sells + "@" + salary; // 生成日志 File f = new File("log.txt"); if (!f.exists()) f.createNewFile(); PrintStream ps = new PrintStream(new FileOutputStream(f)); ps.println(result); ps.close(); return result; } catch (Exception expt) { return "Fail"; } } public static String searchByOthers(String chosen, String others) { //按照其他条件查询,chosen为条件,others为具体筛选内容 try { Class.forName(Parameter.JDBC_DRIVER); Connection conn = DriverManager.getConnection(Parameter.DB_URL, Parameter.ACC, Parameter.PWD); Statement stat = conn.createStatement(); String query; if (chosen.equals("age") || chosen.equals("kind")) query = "select * from `" + Parameter.TABLE + "` where " + chosen + "=\"" + Integer.parseInt(others) + "\""; else query = "select * from `" + Parameter.TABLE + "` where " + chosen + "=\"" + others + "\""; ResultSet rs = stat.executeQuery(query); if (!rs.next()) { return null; } String id = rs.getString("no"); String name = rs.getString("name"); String sex = rs.getString("sex"); int age = rs.getInt("age"); String phone = rs.getString("phone"); String department = rs.getString("department"); int kind = rs.getInt("kind"); int hours = rs.getInt("hours"); double sells = rs.getDouble("sales"); double salary = rs.getDouble("salary"); String result = id + "@" + name + "@" + sex + "@" + age + "@" + phone + "@" + department + "@" + kind + "@" + hours + "@" + sells + "@" + salary; while (rs.next()) { id = rs.getString("no"); sex = rs.getString("sex"); name = rs.getString("name"); age = rs.getInt("age"); phone = rs.getString("phone"); department = rs.getString("department"); kind = rs.getInt("kind"); hours = rs.getInt("hours"); sells = rs.getDouble("sales"); salary = rs.getDouble("salary"); result += "#" + id + "@" + name + "@" + sex + "@" + age + "@" + phone + "@" + department + "@" + kind + "@" + hours + "@" + sells + "@" + salary; } } catch (Exception expt) { expt.printStackTrace(); return "Fail"; } }
    从数据库中更新信息编辑信息模块中,如果需要修改信息,则需要启用更新操作:先判断是否修改了编号,如果修改编号则需要判断修改后的编号是否重复,如果重复则不允许修改为此编号。输入修改前的Person对象和修改后的Person对象,如果修改失败则回退操作,即把原来的Person对象恢复,具体实现如下:
    public static int UpdateFromsql(Person newP, Person oldP) { try { Class.forName(Parameter.JDBC_DRIVER); Connection conn = DriverManager.getConnection(Parameter.DB_URL, Parameter.ACC, Parameter.PWD); Statement stat = conn.createStatement(); if (deleteFromsql(oldP.getID()) != 0) { return 1; } String query1 = "select * from `" + Parameter.TABLE + "` where no=\"" + newP.getID() + "\""; ResultSet rs = stat.executeQuery(query1); if (rs.next()) { if (insertIntoSql(oldP) != 0) JOptionPane.showMessageDialog(null, "发生未知错误"); return 2; } if (insertIntoSql(newP) != 0) { if (insertIntoSql(oldP) != 0) JOptionPane.showMessageDialog(null, "发生未知错误"); return 3; } return 0; } catch (Exception expt) { if (insertIntoSql(oldP) != 0) JOptionPane.showMessageDialog(null, "发生未知错误"); return 4; } }
    从数据库中统计整理信息统计整理模块功能较为复杂,但实现方法大体类似,根据具体的查询条件和查询内容查询相关的信息,并返回结果,具体实现如下:
    统计数量
    public static int statisticCount(String option) { try { Class.forName(Parameter.JDBC_DRIVER); Connection conn = DriverManager.getConnection(Parameter.DB_URL, Parameter.ACC, Parameter.PWD); Statement stat = conn.createStatement(); String query = "select count(*) from `" + Parameter.TABLE + "` "+option; ResultSet rs = stat.executeQuery(query); if (!rs.next()||rs.getString(1)==null) { return 0; } int result = Integer.parseInt(rs.getString(1)); return result; } catch (Exception expt) { JOptionPane.showMessageDialog(null, "发生未知错误"); return 0; } }
    统计不同职位的人数
    public static int statisticKindCount(int kind, String option1, String option2)
    统计不同性别的人数
    public static int statisticSexCount(String sex, String option1, String option2)
    统计最高、最低、平均工资
    public static double statisticAvgSalary(String option1, String option2) public static double statisticMaxSalary(String option1, String option2) public static double statisticMinSalary(String option1, String option2)
    统计最大、最小、平均年龄
    public static double statisticAvgAge(String option1, String option2) public static int statisticMaxAge(String option1, String option2) public static int statisticMinAge(String option1, String option2)
    3.2 图形界面类定义与实现3.2.1 JFreeChart 制图(JFreeChart 类)柱形图制作传入图表的名称(标题)、Dataset 容器(存放图形参数)、以及所要绘制图形的颜色,返回绘制的 Chart 图形。
    public class CategoryChart { public static JFreeChart createChart(String name, CategoryDataset dataset,Color color) { JFreeChart chart = ChartFactory.createBarChart3D(name, null, null, dataset, PlotOrientation.VERTICAL, true, false, false); CategoryPlot plot = chart.getCategoryPlot();// 设置图的高级 属性 BarRenderer3D renderer = new BarRenderer3D();// 3D 属性修改 plot.setNoDataMessage("无 信 息"); renderer.setSeriesPaint(0,color); //设置柱的颜色 // 设置网格竖线颜色 plot.setDomainGridlinePaint(Color.gray); plot.setDomainGridlinesVisible(true); // 设置网格横线颜色 plot.setRangeGridlinePaint(Color.gray); plot.setRangeGridlinesVisible(true); // 图片背景色 plot.setBackgroundPaint(Color.LIGHT_GRAY); plot.setOutlineVisible(true); // 设置墙体颜色 renderer.setWallPaint(Color.gray); // 对 X 轴做操作 CategoryAxis domainAxis = plot.getDomainAxis(); // 对 Y 轴做操作 ValueAxis rAxis = plot.getRangeAxis(); renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator()); renderer.setBaseItemLabelsVisible(true); renderer.setBaseItemLabelFont(new Font("黑体", Font.CENTER_BASELINE, 12), true); renderer.setBasePositiveItemLabelPosition( new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.CENTER_LEFT)); renderer.setItemLabelAnchorOffset(15); //设置距离图片左端距离 domainAxis.setUpperMargin(0.2); //设置距离图片右端距离 domainAxis.setLowerMargin(0.2); //设置柱的透明度 plot.setForegroundAlpha(0.6f); // 最后还得将 renderer 放到 plot 中 plot.setRenderer(renderer);// 将修改后的属性值保存到图中 return chart; } }
    饼图制作传入图表的名称(标题)、Dataset 容器(存放图形参数),饼图颜色自动分配,返回绘制的 Chart 图形。
    public class PieChart { public static JFreeChart createChart(String name,PieDataset piedataset) { JFreeChart jfreechart = ChartFactory.createPieChart3D(name, piedataset, true, true, false); PiePlot3D pieplot3d = (PiePlot3D) jfreechart.getPlot(); pieplot3d.setNoDataMessage("无 信 息"); pieplot3d.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}:{1}({2})"));//设置标签显示的格式 pieplot3d.setSectionOutlinesVisible(false); pieplot3d.setLabelBackgroundPaint(new Color(220, 220, 220));//设置标签的背景颜色。 //设置旋转角度 pieplot3d.setStartAngle(180.0); //设置旋转方向,Rotation.CLOCKWISE)为顺时针 pieplot3d.setDirection(Rotation.CLOCKWISE); //设置图表透明图 0.0~1.0 范围。0.0 为完全透明,1.0 为完全不透明 pieplot3d.setForegroundAlpha(0.9F); return jfreechart; } }
    3.2.2 Dataset 容器(JFreeChart 制图所需容器)柱形图数据集主要包括年龄统计的数据集和工资统计的数据集,对于年龄和工资,本系统统计最大值、平均值和最小值,对于这三项数据,采用柱形图绘制比较合适。代码见附录。

    饼图数据集主要包括性别比例统计的数据集和不同部门、级别人数分布统计的数据集,对于年龄比例和人数比例,本系统绘制饼图直观的展现不同种类人数所占比例的大小。代码见附录

    3.2.3 界面设计与制作(JFrame 继承子类)主要包括登陆界面、主界面、插入信息界面、查询信息界面、编辑界面(修 改删除查询详情)、显示信息界面(表格显示)、统计整理界面(自动制作柱形图、 饼图),详情见第五章。 设计界面主要使用 Java Swing 和 Java AWT,采用框架 Java BeautyEye,同 时采用事件处理等接口完成图形界面设计,做到良好的美观性以及实用性、易用性。便于用户操作。
    3.3 函数调用关系图
    程序由 LoginFrame()进入,登陆成功进入 MainFrame(),Mainframe 可以选择进入 InsertFrame、QueryFrame 和 StatisticFrame,其中 QueryFrame 调用 Delete 和 Update 函数,Statistic 调用 HandleDate、JFreeChart 和 JDBC 函数完成统计并自动将数据可视化。
    第四章 测试结果4.1 登陆界面


    4.2 程序主界面如果登陆失败,如账号不存在或密码错误,则会给以相应提示,登陆成功则跳转至程序主界面。

    4.3 添加信息界面
    如果编号重复、添加内容格式不正确,则会有一定提示:


    4.4 查询信息界面查询界面打开后默认显示全部记录,以表格形式显示

    共有五种查询方式可选择:

    在此处我们选择查询部门为技术部的所有职员,其他类似不做重复测试。可以看到表格刷新,显示出符合条件的所有记录:

    4.5 编辑信息界面在查询界面点击每条查询结果的最右侧按钮可进入编辑界面,可以修改、删除、查询详细信息

    4.6 统计整理界面



    17  留言 2018-11-05 11:25:51
  • C++实现的基于α-β剪枝算法的井字棋游戏

    一、井字棋游戏规则“井字棋”游戏(又叫“三子棋”),是一款十分经典的益智小游戏,操作简单,娱乐性强。两个玩家,一个打圈(O),一个打叉(X),轮流在3乘3的格上打自己的符号,最先以横、直、斜连成一线则为胜。
    如果双方都下得正确无误,将得和局。
    这种游戏实际上是由第一位玩家所控制,第一位玩家是攻,第二位玩家是守。
    X方取胜的情况

    和局的情况

    第一位玩家在角位行第一子的话赢面最大,只要第二位玩家不是在中间下子,第一位玩家就可以以两粒连线牵制着第二位玩家,然后制造“两头蛇”。

    二、井字棋与人工智能这种游戏的变化简单,常成为博弈论和赛局树搜寻的教学例子。这个游戏只有765个可能局面,26830个棋局。如果将对称的棋局视作不同,则有255168个棋局。
    由于这种游戏的结构简单,这游戏就成为了人工智能的一个好题目。学生都要从既有的玩法中,归纳出游戏的致胜之道,并将策略演绎成为程式,让电脑与用户对奕。
    世界上第一个电脑游戏,1952年为EDSAC电脑制作的OXO游戏,就是以该游戏为题材,可以正确无误地与人类对手下棋。
    三、设计方案将井字棋游戏设计为人机对弈系统,分别用X代替人(玩家)的棋子,用O代替机(电脑)的棋子。玩家可以选择自己或电脑先下(游戏实际上是由先下的一方所控制),电脑一方是由程序选择对自己最有利的棋局决定下一步,程序利用Minimax算法结合α-β剪枝算法实现电脑的走步。结局有三种情况:玩家赢,电脑赢或平局。
    用一个3×3的井字格来显示用户与电脑下的界面,使用提示信息要求用户输入数据。当用户与计算机分出了胜负后,机器会显示出比赛的结果,并由用户选择是否重玩游戏。
    四、算法实现4.1 Minimax(极大极小法)算法思想设博弈双方中一方为MAX,另一方为MIN。然后为其中的一方(计算机)找一个最佳走法。
    为了找到当前棋局的最优走法,需要对各个可能的走法所产生的后续棋局进行比较,同时也要考虑对方可能的走法,并对后续棋局赋予一定的权值(或者称之为分数)。也就是说,以当前棋局为根节点生成一棵博弈树,N步后的棋局作为树的叶子节点。同时从树根开始轮流给每层结点赋予Max和Min的称号。
    用一个评估函数来分析计算各个后续棋局(即叶子节点)的权值,估算出来的分数为静态估值。要注意将某方获胜的状态节点的评估函数值设为计算机能表示的最大数(无穷大)或最小数(无穷小)以表明在该状态下有一方获胜。
    当端节点的估值计算出来后,再推算出父节点的得分。推算的方法是:对于处于MAX层的节点,选其子节点中一个最大的得分作为父节点的得分,这是为了使自己在可供选择的方案中选一个对自己最有利的方案;对处于MIN层的节点,选其子节点中一个最小的得分作为父节点的得分,这是为了立足于最坏的情况,这样计算出的父节点的得分为倒推值。
    如此反推至根节点下的第一层孩子,如果其中某个孩子能获得较大的倒推值,则它就是当前棋局最好的走法。
    不过在极大极小法中,必须求出所有端点的评估值,当预先考虑的棋步比较多时,计算量会大大增加。因此,α-β剪枝方法是一种效率比较高的方法。在α-β剪枝方法中,采用了两个变量a和β,它们是最终可以获得的对最大评估值和最小评估值的估计值。
    α-β剪枝的方法如下:

    max节点的α值为当前子节点的最大倒推值
    min节点的β值为当前子节点的最小倒推值
    α-β剪枝的规则如下:

    任何max节点n的α值大于或等于它先辈节点的β值,则n以下的分枝可停止搜索,并令节点n的倒推值为α。这种剪枝称为β剪枝任何min节点n的β值小于或等于它先辈节点的α值,则n 以下的分支可停止搜索,并令节点n的倒推值为β。这种剪枝技术称为α剪枝

    算法流程图

    五、程序模块分析
    程序用一个二维数组chess[3][3]表示一个棋盘
    IsWin()函数:判断某一状态是否胜负已决。返回0表示没有人赢,返回-1表示玩家赢了,返回1表示电脑赢了
    Evaluation()函数:评估函数,主要思想是计算每一行、每一列、斜线中连成3个棋子的有多少个
    AlphaBeta()函数:α-β剪枝算法的实现。获胜的状态节点的评估函数值设为计算机能表示的最大数(无穷大)或最小数(无穷小)以表明在该状态下有一方获胜
    PlayerInput()函数:提示玩家输入,用户通过此函数来选择落子的位置,并提示不正确的输入
    PrintChess()函数:将棋盘以界面的形式显示出来,棋盘的信息存储在一个二维数组chess[3][3]中
    PlayChess()函数:人机对弈模拟过程的实现

    六、结果演示X代替玩家的棋子,用O代替电脑的棋子,口代表空位置

    玩家选择谁先走,这里我们让玩家先走:

    两者交替走步,直到分出胜负:

    结果分析
    上述情况事实上是玩家唯一可以赢电脑的情况,除此之外,玩家至多平局。赢电脑的情况是因为当玩家两个棋子占据棋盘对角的时候,电脑还预测不到玩家的意图,当第三个棋子占据棋盘一角三个棋子成掎角之势时,出现了两种赢的情况,电脑已经守不住了。
    5  留言 2018-11-04 22:40:48
eject