teardrop的资源

  • 基于Java实现的2048小游戏

    一、实验目的通过编写Java的应用系统综合实例——2048小游戏,总结、回顾和实践面向对象的编程思想以及编程方法,并通过编写程序来掌握Java语言编程技巧,将学习到的知识融会贯通,同时提高调试程序的能力,养成良好的编程习惯,并增强对程序设计整体思路的把握。
    程序使用Eclipse集成开发环境完成,熟悉并掌握在Eclipse开发环境下编写Java程序。
    二、设备与环境
    硬件:计算机
    软件:Windows系列操作系统、JDK开发包、Eclipse开发环境

    三、实验内容及要求3.1 实验要求使用Java swing编程实现2048游戏。游戏功能是将4*4的方框里的数字之和加到2048即获成功。
    游戏规则:在一个4*4的方格里,玩家可以用键盘控制(上、下、左、右以及W、S、A、D均可),可将同一方向的数字进行累加。通过有限次的移动,将随机出现的数字2在任意一个方向一直累加到2048即获成功。若四个方向不能再移动并且没有达到2048,游戏即结束。
    3.2 实验内容3.2.1 各类及主要成员函数的功能


    序号
    类名及方法名
    主要功能




    1
    Copy2048
    该类为程序入口,含有main方法


    2
    Copy2048(构造方法)
    实现控制运行等总体要求,实现游戏主板、字体设置、游戏控制、分数计算等具体要求


    3
    do_label_keyPressed
    该方法实现游戏对键盘的响应,如上、下、左、右以及W、S、A、D。即对游戏累加方向的控制


    4
    Create2
    该方法实现在游戏面板内的随机位置出现数字2


    5
    setColor
    该方法实现对每个数字显示不同颜色的控制



    3.2.2 具体代码及实现程序入口主方法
    public static void main(String[] args){ EventQueue.invokeLater(new Runnable(){ //调用完毕后,它会被销毁,因为匿名内部类是作为临时变量存在的,给它分配的内存在此时会被释放 public void run(){ try{ Copy2048 frame = new Copy2048(); frame.setVisible(true); //透明度 } catch(Exception e1){ e1.printStackTrace(); } } });}
    实现控制运行等总体要求,实现游戏主板、字体设置、游戏控制、分数计算等具体要求:
    public Copy2048(){ super(); setResizable(false); //禁止调整窗体大小 getContentPane().setLayout(null); //设置空布局 setBounds(500,50,500,615); //setBounds(x,y,width,height); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //标题窗口退出的设置,窗口上的叉号 setTitle("2048PC版 计科B122班 张灏"); //设置窗体标题 scoresPane = new JPanel(); //创建分数显示面板 scoresPane.setBackground(Color.green); //设置分数显示面板的背景色 scoresPane.setBounds(20, 20, 460, 40); scoresPane.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.YELLOW)); //设置得分面板的边框 getContentPane().add(scoresPane); //将得分面板添加到窗体 scoresPane.setLayout(null); //设置面板空布局 labelMaxScores = new JLabel("最高分:"); //最高分标签 labelMaxScores.setFont(font); //设置字体类型和大小 labelMaxScores.setBounds(10, 5, 50, 30); //设置最高分标签的位置尺寸 scoresPane.add(labelMaxScores); //将最高分标签添加到得分容器中 textMaxScores = new JTextField("暂不可用"); //得分标签 textMaxScores.setBounds(60, 5, 150, 30); textMaxScores.setFont(font); textMaxScores.setEditable(false); scoresPane.add(textMaxScores); //将得分标签添加到分数面板中 labelScores = new JLabel("得 分:"); labelScores.setFont(font); //设置字体类型和大小 labelScores.setBounds(240, 5, 50, 30); scoresPane.add(labelScores); textScores = new JLabel(String.valueOf(scores)); textScores.setFont(font); textScores.setBounds(290, 5, 150, 30); scoresPane.add(textScores); mainPane = new JPanel(); //创建游戏主面板 mainPane.setBounds(20, 70, 460, 500); //设置主面板位置尺寸 this.getContentPane().add(mainPane); mainPane.setLayout(null); //设置空布局 texts = new JLabel[4][4]; //创建文本框二维数组 for(int i = 0; i < 4; i++){ //遍历数组 for(int j = 0; j < 4; j++){ texts[i][j] = new JLabel(); //创建标签 texts[i][j].setFont(font2); texts[i][j].setHorizontalAlignment(SwingConstants.CENTER); //设置文本的水平对齐方式 texts[i][j].setText(""); texts[i][j].setBounds(120 * j, 120 * i, 100, 100); //设置方块的大小位置 setColor(i, j, ""); texts[i][j].setOpaque(true); //设置控件不透明 texts[i][j].setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.green));//设置方块边框颜色 mainPane.add(texts[i][j]); //将创建的文本框放在 } } tips = new JLabel("Tips:使用上、下、左、右键或者W、S、A、D键控制"); tips.setFont(font); tips.setBounds(60,480,400,20); mainPane.add(tips); textMaxScores.addKeyListener(new KeyAdapter(){ //为最高分标签添加按键监听器 public void keyPressed( KeyEvent e){ //键盘敲击 do_label_keyPressed(e); //调用时间处理方法 } }); Create2(); Create2();}
    该方法实现游戏对键盘的响应,如上、下、左、右以及W、S、A、D。即对游戏累加方向的控制:
    protected void do_label_keyPressed(final KeyEvent e){ int code = e.getKeyCode(); //获取按键代码 int a ; //a 的引入是为了防止连加的情况出现 String str ; String str1; int num; switch(code){ case KeyEvent.VK_LEFT: case KeyEvent.VK_A: //如果按键代码是左方向键或者A键 for(int i = 0; i < 4; i++){ a = 5; for(int k = 0; k < 3; k++){ for(int j = 1; j < 4; j++){ //遍历16个方块 str = texts[i][j].getText(); //获取当前方块标签文本字符 str1 = texts[i][j-1].getText(); //获取当前左1方块标签文本字符 if(str1.compareTo("") == 0){ //如果左1方块文本为空字符 texts[i][j-1].setText(str); //字符左移 setColor(i, j-1,str); texts[i][j].setText(""); //当前方块字符置空 setColor(i, j, ""); } else if((str.compareTo(str1) == 0) && (j !=a) && (j != a-1)){ //如果当前方块和左1方块文本字符相等 num = Integer.parseInt(str); scores += num; times ++; str = String.valueOf(2 * num); texts[i][j-1].setText(str); //左1方块文本字符变为两方块之和 setColor(i, j-1, str); texts[i][j].setText(""); //当前方块字符置空 setColor(i, j, ""); a = j; } } } } l1 = 1; //用于判断游戏是否失败 Create2(); break; case KeyEvent.VK_RIGHT: case KeyEvent.VK_D: for(int i = 0; i < 4; i ++){ a = 5; for(int k = 0; k < 3; k++){ for(int j = 2; j >= 0; j--){ str = texts[i][j].getText(); str1 = texts[i][j + 1].getText(); if(str1.compareTo("") == 0){ texts[i][j + 1].setText(str); setColor(i, j+1, str); texts[i][j].setText(""); setColor(i, j, ""); } else if(str.compareTo(str1) == 0 && j !=a && j != a+ 1){ num = Integer.parseInt(str); scores += num ; times ++; str = String.valueOf(2 * num); texts[i][j + 1].setText(str); setColor(i, j+1, str); texts[i][j].setText(""); setColor(i, j, ""); a = j; } } } } l2 = 1; Create2(); break; case KeyEvent.VK_UP: case KeyEvent.VK_W: for(int j = 0; j < 4; j++){ a = 5; for(int k = 0; k < 3; k++){ for(int i = 1; i < 4; i++){ str = texts[i][j].getText(); str1 = texts[i - 1][j].getText(); if(str1.compareTo("") == 0){ texts[i - 1][j].setText(str); setColor(i-1, j, str); texts[i][j].setText(""); setColor(i, j, ""); } else if(str.compareTo(str1) == 0 && i != a && i != a -1){ num = Integer.parseInt(str); scores += num ; times ++; str = String.valueOf(2 * num); texts[i - 1][j].setText(str); setColor(i-1, j, str); texts[i][j].setText(""); setColor(i, j, ""); a = i; } } } } l3 =1; Create2(); break; case KeyEvent.VK_DOWN: case KeyEvent.VK_S: for(int j = 0; j < 4; j ++){ a = 5; for(int k = 0; k < 5; k++){ for(int i = 2; i >= 0; i--){ str = texts[i][j].getText(); str1 = texts[i + 1][j].getText(); if(str1.compareTo("") == 0){ texts[i + 1][j].setText(str); setColor(i+1, j, str); texts[i][j].setText(""); setColor(i, j, ""); } else if(str.compareTo(str1) == 0 && i != a && i != a + 1){ num = Integer.parseInt(str); scores += num ; times ++; str = String.valueOf(2 * num); texts[i + 1][j].setText(str ); setColor(i+1, j, str); texts[i][j].setText(""); setColor(i, j, ""); a = i; } } } } l4 = 1; Create2(); break; default: break; } textScores.setText(String.valueOf(scores));}
    该方法实现在游戏面板内的随机位置出现数字2:
    public void Create2(){ int i ,j; boolean r = false; String str; if(times > 0){ while(!r){ i = random.nextInt(4); j = random.nextInt(4); str = texts[i][j].getText(); if((str.compareTo("") == 0)){ texts[i][j].setIcon(icon2); texts[i][j].setText("2"); setColor(i, j, "2"); times --; r = true; l1 = l2 = l3 = l4 = 0; } } } else if(l1 >0 && l2 >0 && l3 > 0 && l4 > 0){ //l1到l4同时被键盘赋值为1说明任何方向键都不能产生新的数字2,说明游戏失败 tips.setText(" GAME OVER !"); }}
    该方法实现对每个数字显示不同颜色的控制:
    public void setColor(int i, int j, String str){ switch(str){ case "2": texts[i][j].setBackground(Color.yellow); break; case "4": texts[i][j].setBackground(Color.red); break; case "8": texts[i][j].setBackground(Color.pink); break; case "16": texts[i][j].setBackground(Color.orange); break; case "32": texts[i][j].setBackground(Color.magenta); break; case "64": texts[i][j].setBackground(Color.LIGHT_GRAY); break; case "128": texts[i][j].setBackground(Color.green); break; case "256": texts[i][j].setBackground(Color.gray); break; case "512": texts[i][j].setBackground(Color.DARK_GRAY); break; case "1024": texts[i][j].setBackground(Color.cyan); break; case "2048": texts[i][j].setBackground(Color.blue); break; case "": case "4096": texts[i][j].setBackground(Color.white); break; default: break; }}
    3.2.3 游戏运行结果初始游戏界面,上面有最高分及得分情况,最下面为帮助菜单:

    每个方向都能累加,并在空白处随机显示新的数字2,并且计算得分:


    游戏失败的情况:
    四个方向不能再移动并且没有达到2048,游戏即结束。

    四、实验结果及分析在学习Java程序从设计这门课之前,自己接触过一些有关Java的东西,当初觉得要比C和C++简单,因为没有C的指针,而且又和C++一样有封装的概念。后来上网查了查,发现Java语言在现在使用的非常广泛,也是一门比较容易入门的语言。
    这个2048小游戏是当初非常流行的手机游戏。不过代码确实很简单,只有350行左右。刚开始看代码的时候也是看不懂,因为有些函数只是认识但是不知道怎么用,后来自己通过查资料,掌握了它们的用法,觉得是一件非常自豪的事情。但是我觉得游戏还是做得不够完美,应该再加入时间计时以及查看得分排行榜的功能。现在我虽然达不到这个要求,但是我相信只有肯钻研,还是会做好的。以后要多练习敲代码的能力,不要怕辛苦,努力定会成功。
    1  留言 2021-02-19 10:21:36
  • 基于ASP.NET和Sql Server 2008实现的学籍管理系统

    1 引言1.1 项目目标学籍管理系统是一个教育单位不可缺少的部分,它的内容对于学校的决策者和管理者来说都至关重要,所以学籍管理系统应该能够为用户提供充足的信息和快捷的查询手段。但一直以来人们使用传统人工的方式管理文件档案,这种管理方式存在着许多缺点,如:效率低、保密性差,另外时间一长,将产生大量的文件和数据,这对于查找、更新和维护都带来了不少的困难。
    学生学籍管理系统具体目标如下:

    提高学生信息管理效率,节约管理成本,增强学生管理的安全性
    满足学校学生管理的人员、老师和学生的不同层次和不同方面的需要
    为学校将来的信息化建设提供必要的支持

    总之,通过该系统的建设来提高学校的学生信息管理效率,使得学校的发展能够适应当前的教育信息化建设的中体发展趋势。
    1.2 项目背景
    名称:学籍管理系统 V1.0
    用户:已注册学生、老师和系统管理维护人员

    1.3 定义
    Microsoft Windows Server 2008:微软服务器专用操作系统
    Sql Server:数据库开发管理工具
    Database:数据库

    2 运行环境2.1 硬设备可联网的普通 PC 机或者移动端设备。
    2.2 支持软件
    运行环境:Windows server 2008
    数据库:Sql Server 2008、2010

    3 概要设计学生学籍管理系统是一个较为复杂的系统。在功能需求方面,它要实现学生基本信息管理、学生课表管理、学生选课管理、学生成绩管理、学生学分管理以及学生奖惩情况管理等功能;在性能上要求该系统能够方便快捷地完成学生信息管理的各项工作,录入数据合法性的校检程度高,数据查询速度快;为了系统的安全和保密,要求系统对不同权限的用户提供不同的功能模块,对历史数据的更改和新数据的添加只有一定权限的用户才能进行操作,一般的用户只能进行查询操作。
    4 开发流程具体描述4.1 开发环境搭建学生学籍信息管理系统其开发主要包括后台数据库的建立和维护以及前端应用程序的开发两个方面,对于前者要求建立起数据库一致性和完整性、安全性好的数据库。而对于后者则要求应用程序功能完备,易使用的特点。
    由于采用 ASP.NET 技术,因此前端主要使用微软提供的 Visual Studio 开发工具,数据库采用 SqlServer。利用其提供的集成开发工具和数据库可视化操作界面,短时间内可快速开发出项目版本。
    4.2 系统分析4.2.1 系统功能分析学籍管理系统主要面向用户有注册学生,老师和系统管理人员,因此在通过登录验证之后进入主界面,注册学生只提供查询功能,可查看个人信息和个人相关科目成绩,也可以使用辅助功能进行在线查询。老师可进行学生信息和学生成绩管理,包括增、删、改、查等。系统管理员还可以修改数据库。以上用户均提供密码修改功能。
    4.2.2 业务流程
    4.3 系统架构设计4.3.1 总体结构设计用于描述系统总体功能

    4.3.2 独立模块设计用于描述各独立模块具体功能


    4.3.3 数据库设计用于描述数据库概要设计

    4.3.4 界面设计登录页面设计

    系统主界面

    学生信息查询页面

    学生成绩查询页面

    学生信息录入页面

    成绩统计页面

    奖励统计页面

    在线查询页面

    密码修改页面

    4.4 系统实现4.4.1 数据库建立根据 ER 图建立符合系统设计的数据库,主要数据表设计如下:

    Student 表

    Score 表

    Admin 表

    Scholarship 表

    User 表

    数据库表间关系

    数据填充

    4.4.2 程序编码项目文件结构图

    页面代码举例

    5 系统评价及功能完善
    完成基本项目所需功能,包括登录验证,以及权限限制等
    信息查询功能完善,统计查询功能查询效率较高
    数据库设计合理
    需要注意数据库后台防止 SQL 恶意注入,需添加数据库后台安全机制
    成绩模块要增强可扩展性
    0  留言 2021-02-15 10:23:05
  • 基于JAVA的葫芦娃救爷爷游戏

    一、游戏简介葫芦娃救爷爷
    1.1 游戏背景这是一个很久很久以前,发生的故事
    葫芦娃啊,为了救爷爷,然后一个一个去找妖精单挑,然后就一去不复返了。当然正义最终要打败邪恶,所以这次游戏的开始在七个葫芦娃找到了爷爷,但是蛇精和蝎子精发现了,带了一群喽啰要来捉拿他们,战斗一触即发,葫芦娃他们能逃离生天吗?
    1.2 游戏人物


    姓名
    阵营
    介绍
    技能




    大娃
    葫芦娃
    老大,红色,翻天掀地,力大无穷。他是七兄弟中的老大哥,生来就是一个大力士,身体可以任意变大或缩小
    游戏中,可以近战,并发射葫芦攻击敌人


    二娃
    葫芦娃
    老二,橙色,慧眼千里,耳闻八方。橙娃天生便拥有一双千里眼和一对顺风耳,妖怪的一切秘密都瞒不住他
    游戏中,可以近战,并发射葫芦攻击敌人


    三娃
    葫芦娃
    老三,黄色,铜头铁臂,刀枪不入。黄娃三弟是个拥有钢筋铁骨的神娃,刀枪箭炮对他丝毫无伤
    游戏中,可以近战,并发射葫芦攻击敌人


    四娃
    葫芦娃
    老四,绿色,炉火纯青,刚阳烈焰。绿娃四弟乃天界火神下凡,可任意吞吐烈火
    游戏中,可以近战,并发射火焰攻击敌人


    五娃
    葫芦娃
    老五,蓝色,惊涛骇浪,气吞山河。青娃五弟乃江河水神转世,可任意吞吐江河之水,难怪吞饮百坛烈酒而不醉
    游戏中,可以近战,并发射水流攻击敌人


    六娃
    葫芦娃
    老六,蓝色,来无影,去无踪。蓝娃六弟是七兄弟中最灵敏,最聪明的。他的隐身法令妖怪束手无策
    游戏中,可以近战,并发射葫芦攻击敌人


    七娃
    葫芦娃
    老七,紫色,镇妖之宝,本领无穷。紫娃七弟自身没多大本事,但他有个宝葫芦,是太上老君修炼仙丹用的紫金神葫
    游戏中,可以近战,并发射葫芦攻击敌人


    爷爷
    人类
    善良勇敢的老爷爷播下葫芦籽,种出七个葫芦娃
    游戏中,可以近战,并发射葫芦攻击敌人


    蛇精
    妖精
    从葫芦山中逃脱的蛇精,法术高强,诡计多端,身有如意、魔镜和刚柔阴阳剑三件宝物,残害众生
    游戏中,可以近战,并发射光波攻击敌人


    蝎子精
    妖精
    蝎子精性情暴虐,武艺高强,但有勇无谋
    游戏中,可以近战,并发射光波攻击敌人


    喽啰
    妖精
    一众小妖
    游戏中,可以近战,并发射光波攻击敌人



    1.3 游戏运行
    游戏初始:打开游戏,首先看到引导画面,拥有两个按钮——“开始游戏”和“退出游戏”。点击前者进入游戏,开始时,葫芦娃阵营站左边,妖精阵营站右边,两队均呈长蛇阵。此时葫芦娃为随机排列,可以通过点击File菜单中的restart可以改变葫芦娃的排队顺序,实现随机站队
    变换阵型:点击“妖精阵型”和“葫芦娃阵型”两个菜单中点击任意阵型,即可看到画面上对应的阵型变换
    开始运行:点击start或者按键盘SPACE键均可控制游戏开始。一般在真正开始前,我会要求选择存储路径,选择之后,开始游戏,同时记录文件开始记录。两方开始向对方进攻。我为每个Creature设置一个简单的寻找对手的算法,找到最近的对手,开始移动并且发出技能进行远程攻击,直到和对手碰面,双方近战肉搏并随机死亡。一旦在移动过程中中受到远程攻击直接死亡。死后即显示墓碑,有人经过墓碑即消失。直到有一方全部死亡,弹出弹框告知获胜
    游戏回放:战斗结束后可以选择存储的记录文件进行回放,点击open找到刚才存储的路径选择文件,即可回放(目前仅支持人物回放,不支持技能回放)
    重新开始:点击restart可以重新开始下一轮游戏
    版本信息:点击Help菜单中about显示版本和开发者信息
    退出游戏:直接点击右上角关闭即可退出

    1.4 运行效果图
    1.5 游戏特色基本功能
    完成所有要求的基本功能,包括实现战斗记录与回放、增加注解、编写单元测试用例、使用maven进行构建管理等等。
    设计思路
    这个游戏一开始想设计成为一款纯粹的弹幕游戏,通过双方射子弹进行躲避攻击。但是最后依然保留了近战,所以两相结合成了现在的版本。
    新的想法

    首先实现了远程攻击,两方通过远程攻击和近战肉搏两种方式进行战斗,更加添加游戏的趣味性。并且给不同人物增加不同的远程攻击技能,包括葫芦、火球、火焰和水波等,增加人物多样性
    其次实现了背景音乐,一点击开始游戏,葫芦娃原声大碟带你回到童年,享受打怪的快感
    第三实现了关卡切换,当然这里的关卡主要是指背景切换,主要是图个乐子

    二、实现说明2.1 代码结构2.1.1 package GUI
    class Main是整个工程项目的入口
    class StagePageController是JavaFX框架,使用FXML Scene Builder工具的一个控制器,用来显示引导页,并且点击开始游戏时加载CalabashWorldController进入游戏
    class CalabashWorldController也是JavaFX框架,使用FXML Scene Builder工具的一个控制器。主要用于处理程序的各种外部事件处理函数以及控制整个游戏的UI刷新
    class Battle是自己定义的一个线程,用于开始游戏后的子线程管理和监测,在 CalabashWorldController 中产生,控制各个Creature的运行

    2.1.2 package Field
    class Position是二维平面坐标,显示具体在平面上的横纵坐标
    class Unit是二维平面的基本组成单位,生物体能够放置在Unit上。T其具有一系列接口可以判断该对象上是否存在生物体、移除对象上的生物体、放置生物体等
    class BattleMap实现一个由Unit组成的二维平面地图。可以对该对象的某一个具体坐标单元进行操作,可以显示GUI平面和输出普通平面
    class BattleField是主体类,用于实现双方在其上战斗的相关功能,同时又包含记录和重放的相关接口,并且负责向javafx主线程发送UI刷新信号

    2.1.3 package Creature
    class Creature是所有生物体的基类,能够在地图上行走战斗。并且实现了Runnable接口,用于多线程的运行
    class Sprite继承于Creature
    class CalabashBoy继承于Creature
    class Grandpa继承于Creature
    class Snake继承于Creature
    class Scorpion继承于Creature。
    class Bullet继承Creature 。新添加的类,作为子弹同样包括Creature的部分属性,所以将其加入子类中
    class CalabashBrother是七个CalabashBoy的集合,为了实现排序等功能,所以将其构造进一个类
    class Heroes为英雄阵营的集合,包括爷爷和七个葫芦娃
    class Monsters为妖怪阵营集合,包括蛇精、蝎子精和一群小喽啰,喽啰数可根据阵型进行调整
    Enum Color为颜色,用于构造葫芦娃时进行标记
    Interface Queueup为实现摆出阵型的接口,每个阵营类有这个接口,可以摆出相应的阵型


    2.1.4 package Formation
    class Formation是一个抽象类,并具有抽象接口用于排列阵型
    class Changshe继承了Formation类,具体实现了长蛇阵法
    class Fangyuan继承了Formation类,具体实现了方圆阵法
    class Fengshi继承了Formation类,具体实现了锋矢阵法
    class Chonge继承了Formation类,具体实现了冲轭阵法
    class Heyi继承了Formation类,具体实现了鹤翼阵法
    class Yanxing继承了Formation类,具体实现了雁行阵法
    class Yanyue继承了Formation类,具体实现了偃月阵法
    class Yulin继承了Formation类,具体实现了鱼鳞阵法


    2.1.5 package Musicclass MusicPlayer使用AudioClip进行音乐读取和播放,并设置循环播放。
    2.1.6 package XML
    class XMLFormat定义了xml文件的基本结构,用于以后实现读写
    class FileReader继承XMLFormat类,定义了一些接口,用于BattleField进行文件读取重放
    class FileWriter继承XMLFormat类,定义了一些接口,用于BattleField进行文件记录


    2.2 功能实现2.2.1 阵型排列用户可以通过菜单栏中的按钮布置阵型。用户点击按钮后通过按钮的触发事件处理函数,将生物体排列成相应的阵型。妖精和葫芦娃阵型可以单独排队。
    2.2.2 战斗功能
    CalabashWorldController类中有一个成员BattleField newbattle包含了游戏所有的二维空间和生物体们
    当用户按下空格后, CalabashWorldController类中的键盘事件处理函数捕捉到该事件,然后通过调用Battle类运行所有的生物体线程
    在生物体子线程void run()函数里,生物体会执行while循环,条件自己是否已经死亡或者胜利,在其中会检测对手是否全部死亡,若死亡则胜利
    通过Battle类中定义CountDown计数器进行判断,在其不为零时,Battle线程阻塞,直至某一方全部死亡所有生物体子线程结束运行,计数器归零
    子线程运行时会在findEnemy和nearstEnemy函数中寻找离该生物体曼哈顿距离最接近的一个敌人作为进攻目标。然后通过执行move函数生成路径进行移动,移动过程中会调用remoteAttack生成Bullet子线程进行远程攻击,如和目标距离只为1,会调用fight函数对附近的敌人进行攻击
    如果生物体已经死亡,进程立即结束,该生物体会以墓碑的形式继续存在,直到其他人到达该地,墓碑被清除
    最终子线程全部结束,Battle线程通知javafx游戏结束,跳出弹框通知哪一方胜利

    2.2.3 线程安全的实现
    首先当每个生物线程进行移动和攻击时,对方法加上synchronized锁,对BattleField对象加上锁,这样可以保证一个生物体进程在进行移动和攻击时独享BattleField资源,避免多个生物站上同一位置、多个生物同时杀死某个生物等问题
    其次在子线程移动攻击死亡时,会向BattleFiled调用相关函数进行UI刷新。本来我以为可以直接在子线程中进行UI的修改,但在javafx中,并不允许。所有UI的修改必须在Javafx主线程中,所以我才用Platform.runlater将其发送给主线程,进行UI的刷新,这样确实会导致画面的卡顿,但符合了实验的要求,并且更加深入了解了多线程UI刷新

    2.2.4 记录回放
    在记录文件的选择上我考虑到XML文件结构更加清晰,读写也比较方便,所以使用它。同时使用dom4j的支持,方便代码的编写
    当游戏开始时,首先选择记录文件的路径,默认游戏目录,创建xml文件,以及FileWriter对象。并且传给BattleField。然后启动所有生物体线程,当生物线程进行move和attack等操作时,BattleField会调用addRecord使得FileWriter对象记录对应的位置状态信息,然后写入对应的xml文件,实现了记录文件的功能与Creature类的功能分离
    进行录像回放时,首先选择打开记录文件,然后将程序游戏控制器的FileReader会根据其创建对象,此时回放考虑到其结果的固定性,我采用Timeline进行动画的实现,由FileReader每隔固定时间读取一轮各个生物的状态进行显示生成动画效果
    注意:每个生物体线程是并发进行的,运行时其move等操作都是顺序进行的,记录写入也是固定顺序。回放按照记录写入的顺序依次重现可以实现

    2.3 单元测试使用了JUnit4对Field,Creature等几个模块的部分方法进行了单元测试,内容简单,不详述。

    2.4 设计原则2.4.1 SRP 单一职责原则在CalabashWorlController里,需要对记录文件和控制子线程运行进行响应,一开始的设计是直接控制器之中里进行文件读写和子线程运行。后来考虑到文件读写的功能和控制子线程运行并不是javafx控制器职责,所以加入了一个XML包和Battle类,XML包中对读写文件操作进行了封装,直接与BattleField进行交互实现读写,而Battle类直接实现控制战斗开始和结束,实现了文件操作、战斗运行和控制器只负责对UI进行响应和刷新功能的分离。
    2.4.2 OCP 开放封闭原则开放封闭原则是指实体应该对扩展是开放的,对修改是封闭的。即,可扩展,不可修改。应该通过增加代码来扩展功能,而不是修改已经存在的代码。在阵法类的设计体现这条原则:所有阵型抽象成一个abstract class Formation,然后具体的阵型通过继续抽象类Formation,实现其中的抽象方法来获得不同的阵型,但接口保证统一。而生物只能通过Queueup接口选择阵型,不能改变阵型内的具体内容。
    2.4.3 LSP LISKOV替换法则Creature类的子类CalabashBoy、Sprite、Grandpa、Snake和Scorpion都可以替换程序中的Creature类。实际上在后续操作中,都是用ArrayList来表示生物集合。
    2.4.4 CARP 合成/聚合复用原则
    二维地图BattleMap是有一组Unit类合成而来
    战场类BattleField类由Creature的子类和BattleMap类聚合而成
    Monsters由snake、Scorpion、Sprite等聚合
    Heroes由CalabashBrother、Grandpa聚合
    CalabashBrother由Calabashboy聚合

    2.5 注解自定义了一个注解类用来标记编作者网站和版本:
    @Documented @Target(ElementType.TYPE) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Author{ String name() default "Zhou Yuhang"; String website() default "cn.edu.nju"; int revision() default 1; }
    调用:

    @Author()

    三、思考总结通过这学期的JAVA课学习,我真的收获了很多,对于面向对象不再是空洞的名字,而是真的要去实现“面向对象是对现实世界的刻画”。对于并发等等平时接触很少的技术有了新的认识,我还有IOS要接着去体会。
    1  留言 2019-04-15 23:08:24
  • 基于QT实现的学生成绩管理系统

    1.系统需求分析学生成绩管理系统记录了学生大一的各科成绩及每门课程的成绩,它包括:学期、姓名、班级(汉字)、学号、科目、学分、分数,试设计学生成绩管理系统,使之能提供以下功能:

    录入、修改学生的各科的成绩信息:从键盘输入数据(提示:为避免重复从键盘输入数据,测试时可将数据存储在文件中,利用输入重定向功能读入),输入格式为:学期 姓名 班级 学号 科目 学分 分数。每行一条记录。并在输入不合法记录时报错。若该信息已存在则覆盖原信息。系统根据分数得到该信息对应的评级、学分绩及是否挂科。

    例如:大一上学期 王世杰 无七六 2016010539 工程制图 2 87此时系统得到该信息评级为 B+,学分绩为 3.3,未挂科
    查询某个学生某学期或整个学年各门课的成绩:按照分数降序排列,相同的课程按学分降序排列,并提供该课程的评级和学分绩。同时给出该时间段平均学分绩
    统计某课程所有学生总成绩情况,按照分数(总学分绩)降序排列,相同的按学号升序排列
    查询某课程所有学生成绩,按照分数(总学分绩)降序排列,相同的按学号升序排列
    统计学生挂科数并按降序排列,相同的按姓名升序排列
    系统以菜单方式工作。(所谓菜单指用户可以自由选择所要执行的功能。学生可以通过以上功能录入信息、修改信息、查询信息、整理统计出所要了解的信息,除了要实现上述的基本功能之外,本系统还应该在细节上下工夫,使用户使用方便)

    2.总体设计大一学生成绩管理系统包含五个大的功能,分别是:录入、修改学生成绩,查询某学生成绩,查询某课程所有学生成绩,查询所有学生总成绩情况,查询挂科情况。每个功能对应一个界面,每个界面均有操作提示,并可返回之前的界面。学生的成绩信息主要包含学期、姓名、班级(汉字)、学号、科目、学分、分数,以及根据分数转换得到的学分绩、评级和是否挂科。信息存储基于文件操作。
    打开系统首先是进入欢迎界面,打出欢迎使用的字样。在欢迎界面,系统会自动根据存储信息的文件统计文件中的信息条数,创建录入信息类对象数组来存放最新版本的信息并将其写入文件。同时制作所有学生的 studentGrade 类对象数组。
    紧接着进入主界面,有 6 个选项,分别是进入对应 5 个功能的界面及结束界面。
    在录入、修改学生成绩界面(page1),根据系统提示一次性输完一整条信息。若输入信息有误,如学期不正确(不为大一上学期或大一下学期),则报错,提示重新输入。系统根据信息的学期、姓名、科目信息判断是否为新信息,若为新信息则覆盖原信息。在退出该界面时,更新文件并统计文件中的信息数,创建录入信息类对象数组来存放最新版本的信息。同时制作所有学生的 studentGrade 类对象数组。
    在查询某学生成绩界面(page2),用户首先输入要修改的学生学号,再选择要查询的学期。如果系统中没有该学生的相关信息,则系统会给相关提示。如果系统中有该学生的相关信息则按照分数降序排列,相同的课程按学分降序排列,并提供该课程的评级和学分绩。
    在查询某课程所有学生成绩情况界面(page3),用户先输入要查询的学期和课程名,系统检索判断是否存在该课程。若不存在则提示系统中无此课程。若存在显示该课程所有学生成绩,按分数降序排列,分数相同则按学号升序排列。
    在查询所有学生总成绩情况界面(page4),用户先根据提示选择查询的学期。显示该学期所有学生成绩,按平均学分绩降序排列,相同则按学号升序排列。
    在查询挂科情况界面(page5),用户先根据提示选择查询的学期。显示该学期所有出现挂科情况的同学的挂科学分和挂科数目,按挂科数降序排列。相同按挂科学分降序排列。再相同按学号升序排列。
    在结束界面(page6),系统会自动清空所有动态内存、关闭文件,同时打出感谢使用本系统的字样,希望给用户最好的体验。
    大一学生成绩管理系统中功能模块图:

    3.详细设计大一学生成绩管理系统中五个类的类层次图为:

    大一学生成绩管理系统中各功能模块的实现:

    界面 1:录入、修改学生的成绩

    界面 2:查询某学生成绩

    界面 3:查询某课程所有学生成绩

    界面 4:查询所有学生总成绩

    界面 5:查询挂科情况

    欢迎界面

    结束界面

    大一学生成绩管理系统中五个类的 UML 图为:

    4.系统调试程序编写完成后,我进行了调试。调试过程中,出现了以下三个主要问题:

    以姓名为关键词不能对重名现象进行很好的结局。编 一开始我才去以姓名为关键字进行搜索,在室友的提醒下,我将程序改为用学号为关键词进行搜索,解决了这一问题
    系统应对错误输入的能力严重不足。最初版本的程序对输入没有任何检测,经常会出现原本应输入汉字或汉字的地方我输入一堆字母,系统没有任何提示,并将错误输入写入了文件中,导致在检索、排序的时候以及显示所有学生成绩的时候,出现重大问题。于是我在所有需要输入的地方加入了检测功能,一旦输入不合要求就出现提示并重新回到输入界面
    系统应对大量误操作时会崩溃。作为开发者我对程序较为了解,输入等操作比较合法,也比较舒缓。当我把系统交给我室友检测时,他在短时间内随便按下键盘,这就导致在主界面短时间内出现大量误操作。当时我将 update 部分放在了进入主界面时执行,而主界面出现误操作后会重新进入主界面,这就意味着如果主界面短时间出现大量误操作,就需要短时间内执行多次 update,而 update 部分需要执行扫描整个文件、排序、 重新写入文件、建立 studentGrade 对象动态数组等多个步骤,执行需要时间相对较长。 这导致系统无法在短时间内处理多次 update,所以系统会崩溃。由于 update 函数无法更 改,我就尝试从其他角度解决这个问题。我想到 update 函数负责更新,而只有在信息发 生变化的时候才需要进行更新。在本系统中只有界面 1 会对信息进行更改,于是我将 update 函数放在了界面 1 返回主界面的时候执行,有考虑到可能使用者不会对信息进行 修改,又在欢迎界面开始处执行一次 update 以建立 studentGrade 对象动态数组。这样就 解决了主界面无法承受大量误操作的问题。

    这种发现问题并解决问题的过程对我的帮助很大,通过对程序的设计和测试,我意识到开发一个成熟的系统需要非常的耐心以及不停的完善,后期测试也必不可少。这次程序设计真的让我在程序调试方面有了很大的进步。
    5.测试结果与分析本程序的测试数据文件是 grade.txt,, 测试结果截图如图所示。

    欢迎界面

    主界面及其错误操作反馈演示

    界面 1:错误操作反馈演示

    界面 1:录入修改学生成绩操作演示

    经过上述操作后存储信息文件的变化

    界面 2:查询某学生成绩操作演示


    界面 3:查询某课程所有学生成绩操作演示


    界面 4:查询所有学生总成绩操作演示


    界面 5:查询挂科情况操作演示


    界面 6:退出界面

    这次大作业总得来说完成的还算顺利,主要原因是老师要求在正式做大作业前先做一份选题报告。这份选题报告帮了我大忙。它让我在一开始就想好了程序的架构,如:需要实现的功能及如何实现、如何实现题目要求的四个类等,同时了解了工作量方便安排计划。这让我有了一个非常顺利的开始。但在开始编写之后仍遇到了不小的麻烦。
    第一个问题是如何存放代码。我一开始的想法是头文件放类和类的函数,之后一个文件存放界面,一个文件存放界面的操作。但后来发现这样做存放操作的文件代码量太大,而且各个界面分别的不够鲜明,不利于编译和工作的进行。于是我就改成了每个界面对应一个文件,通过主界面统一调用,解决了上面的问题,有力地推进了工作。
    第二个问题是规划不到位,在动手编程前没有确定算法,经常是编写到一大半想到更好的算法、更简单的代码,但是已经无法更改了,导致代码不够简洁,有很多效率很低的算法以及很多重复代码。这让我明白在开始写代码前,一定要将每一个细节都想明白,甚至要动笔去写下来,而不是脚踩西瓜皮写到哪是哪。
    第三个问题是全局变量、全局函数的使用。我对“全局”的理解非常不到位。开始编程时我连如何使用都不知道。了解如何使用之后我却未对全局变量做好规划,导致了变量乱取名、多声明了一些全局变量等问题。在编程前一定要将全局变量规划好,不能编到一半发现需要了再去声明。
    第四个问题是不注意细节。经常出现 i、j,1、l,<、>,=、==,&、&&之类的字母、数字、符号打错,导致程序崩溃,浪费了大量时间在调试上。这个问题必须要引起重视!
    希望能吸取这次大作业的经验教训,为以后的编程打好基础。
    6. 总结这次大作业让我的编程能力有了很大的提升。这是我第一次为实际满足需求编写一个较大的程序,很贴近实际情况。刚拿到题目时我不知所措、心乱如麻不知如何下手。
    冷静下来思考之后发现,只需根据功能将程序分成一个个界面,之后各个击破即可。这种将一个大问题拆成很多小问题各个击破的方式很有作用。同时编写这种代码数较多的程序对我的调试能力也有很大提升。
    这次大作业同时提升了我对课上学习的基础知识的理解。尤其是全局变量、全局函数以及类的多继承、虚函数这几部分。由于笔试不考所以学完了就忘掉了,这次大作业让我明白这些知识有多么重要,使用起来多么方便。
    这次大作业对我最大的提升是解决问题的能力。遇到不会的地方翻书找、上网查、问同学;编译出错了根据提示上网搜索出错原因;和同学们一起讨论算法、讨论解决问题的最优方式……这种发现问题、解决问题的过程真的很迷人。
    总之,这次大作业提升了我的编程能力,增加了我对编程的兴趣,提高了我的自信心,让我明白了细节的重要性,让我受益匪浅。
    1  留言 2019-02-24 12:40:32
  • 语法分析器的设计与实现-LR

    语法分析器的设计与实现-LR环境
    使用语言 - C++
    开发环境 - Qt Creator 5.8.0 + MinGW
    运行环境 - Windows 10


    虽然使用Qt Creator开发,程序仍为console application,只是使用了Qt提供的容器库。

    概述
    能够自动生成LR(0) DFA与SLR(1)分析表
    程序内含翻译方案,能够计算输入表达式的结果

    要求实验内容编写语法分析程序,实现对算术表达式的语法分析。要求所分析算术表达式由如下文法产生:
    E -> E+T | E-T | TT -> T*F | T/F | FF -> (E) | num实验要求
    在对输入的算术表达式进行分析的过程中,依次输出所采用的产生式
    构造识别该文法所有活前缀的DFA
    构造文法的LR分析表
    编程实现算法4.3,构造LR分析程序

    算法4.3
    输入 - 文法G的一张分析表和一个输入符号串ω
    输出 - 如果ω∈L(G),得到ω自底向上的分析,否则报错

    方法:
    初始化 { 初始状态S0压栈 ω$存入输入缓冲区 置ip指向ω$的第一个符号}do { 令S是栈顶状态,a是ip指向的符号; if (action[S, a] = shift S'){ 把a和S'分别压入符号栈和状态栈的栈顶; 推进ip,使它进入下一个输入符号; } else if (action[S, a] = reduce by A -> β){ 从栈顶弹出|β|个符号; 令S'是栈顶状态,把A和goto[S', A]分别压入符号栈和状态栈的栈顶; 输出产生式A -> β; } else if (action[S, a] = accept) return; else error();} while (1);
    设计文法存储符号的存储struct Symbol{ enum SymbolType { T, // terminator NT // non-terminator }; SymbolType type; int index; // index in SymbolTable};
    定义了枚举常量T(终结符)和NT(非终结符)作为符号类型。定义结构Symbol,包含一个符号类型type和一个在符号表中的索引index
    定义以下两个特殊的终结符:
    const Symbol EPSILON = {Symbol::SymbolType::T, 0}; // εconst Symbol END = {Symbol::SymbolType::T, 1}; // $
    严格来说空串ε和输入结束符$不算是终结符。此处视为终结符方便管理
    符号表template <bool isTerminatorTable>class SymbolTable{private: Symbol::SymbolType type; QVector<QString> symbols;public: SymbolTable(); int getIndex(const QString &str); // if str not exist, push it into symbols int getIndex(const QString &str, bool) const; // return -1 if str not exist QString getStr(int i) const; // return blank QString if i is invalid int size() const { return symbols.size(); } Symbol operator[](int n) const { Symbol result = {type, n}; return result; }};using T_Table = SymbolTable<true>;using NT_Table = SymbolTable<false>;
    符号表分为两种:终结符表和非终结符表。其中终结符表在构造时就需要添加符号$和ε。为了方便理解,使用using对不同类型的符号表进行了重命名,即T_Table和NT_Table。为了实现以上两点,使用模板来设计符号表,模板参数isTerminatorTable表示了此符号表是否是终结符号表。
    符号表中并没有保存符号Symbol,而是保存了每个符号的字符串量,即QVector<QString> symbols,所以虽然可以使用operator[]从符号表获得符号但是不能修改符号表中已存在的内容。可以根据符号的下标来查询字符串量(即getStr方法),也可以根据符号的字符串量来查询下标(即getIndex)。所以符号表只是一个用来查询的表,并不是一个符号的容器。
    函数解释:

    int getIndex(const string &str) - 查询字符串并返回下标。如果没有此字符串则插入此字符串int getIndex(const string &str, bool) const - 仅查询字符串并获得下标string getStr(int i) const - 查询对应下标的符号的字符串int size() const - 查询符号表容量
    DFA与自动机为了构造有效项目集规范族,首先定义了有效项目struct Project
    struct Project{ int ntIndex; int candidateIndex; int index; // position of Point in a project};
    其中ntIndex表示此项目的生成式左侧非终结符的下标,candidateIndex表示此项目的生成式是此非终结符的第几个生成式。index表示项目中圆点的位置。
    自动机的每个状态都是一个项目的集合,即
    using State = QVector<Project>;
    自动机保存为一个map,此map的key应该是状态和输入符号的二元组,所以定义DFA_Key:
    struct DFA_Key{ State state; Symbol s;};
    那么DFA就是:
    using DFA = QMap<DFA_Key, State>;
    类似地,分析表也是一个map,分析表的key应该是当前状态(的下标)和输入符号的二元组。因为可以和DFA_Key都包含状态和输入符号,所以直接使用DFA_Key作为分析表的key:
    using SLR_Key = DFA_Key;
    而分析表map的value应该是一个分析动作,所以定义Action:
    struct Action{ enum ActionType { Shift, Reduce, Goto, Accept }; ActionType type; int index;};
    可以看到Action包含一个枚举类型ActionType,有四种取值,分别表示:

    Shift - 移进,此时index表示移进后转移到的状态下标
    Reduce - 规约,此时index表示规约使用的产生式的下标
    Goto - 转移,此时index表示应该转移到的状态下标
    Accept - 接收,此时index无意义

    那么分析表的数据结构就是:
    using SLR_Table = QMap<SLR_Key, Action>;
    文法表class GrammaTable{private: // input QVector<Gramma> grammas; T_Table tTable; NT_Table ntTable; // error handling int lineCount; bool error; // process QVector<First> firsts; QVector<Follow> follows; QVector<State> states; DFA dfa; SLR_Table slrTable; void killBlank(QString &str) const; // discard blank chars bool format(QString &str) const; // return false if format is wrong /** * killDuplicated: * eliminate same Candidate in grammas[index] if index != -1 * eliminate same Candidate in each Gramma when index == -1 */ void killDuplicated(int index = -1); void killEpsilon(); void getFirsts(); First getFirst(const Candidate &candidate) const; void getFollows(); void getDFA(); // construct DFA void getState(State &state); // construct a state int getIndex(int ntIndex, int candidateIndex) const; void getSLR_Table(); int getReduceIndex(const State &s, int &ntIndex, int &candidateIndex) const; void getCandidateIndex(int index, int &ntIndex, int &candidateIndex) const; int candidateCount() const; // return empty candidate if error Candidate parseInputToCandidate(const QString &str, QVector<int> *values = nullptr) const; void outputSingleCandidate(int ntIndex, int candidateIndex) const; void outputProject(const Project &p) const; void outputSymbol(const Symbol &s) const; void outputSLR_Key(const SLR_Key &key) const; void outputAction(const Action &a) const;public: GrammaTable() : lineCount(0), error(false) {} int insert(const QString &grammaLine); // return 0 if ok, otherwise return lineCount /** * generate first set, follow set, index of candidates and predict table * return false if error */ bool generate(); void output() const; bool ok() const { return !error; } int currentLineCount() const { return lineCount; } bool parse(const QString &str, bool calculateResult = false) const;};
    文法表包括了文法grammas、符号表tTable和ntTable、此文法生成的FIRST集与FOLLOW集、DFA、所有状态和预测分析表。
    一个候选式Candidate由若干个符号Symbol组成,一个符号的生成式Gramma由若干个候选式Candidate组成
    using Candidate = vector<Symbol>;using Gramma = vector<Candidate>;
    FIRST集和FOLLOW集是符号Symbol的集合。
    using First = set<Symbol>;using Follow = set<Symbol>;
    变量lineCount和error用来控制错误,当错误发生时不能够继续输入文法、不能生成FIRST集和FOLLOW集、不能解析输入串。输入错误时会返回lineCount,根据此值可以定位输入的错误。
    函数概述:

    private
    void killBlank(QString &str) const; - 清除输入串中的空白符(空格、制表、换行)bool format(QString &str) const; - 如果输入串没有->则报错void killDuplicated(int index = -1); - 清除第index个非终结符的重复生成式。index为-1时清除所有非终结符的重复生成式void killEpsilon(); - 清除所有候选式中多余的εvoid getFirsts(); - 生成所有非终结符的FIRST集First getFirst(const Candidate &candidate) const; - 在所有非终结符的FIRST存在的情况下生成单个候选式的FIRST集void getFollows(); - 生成所有非终结符的FOLLOW集void getDFA(); - 生成DFAvoid getState(State &state); - 根据当前State进行扩充从而形成一个完整的Statevoid getIndex(int ntIndex, int candidateIndex) const; - 根据生成式左侧非终结符的下标和生成式是此非终结符的第几个生成式来判断生成式下标void getSLR_Table(); - 生成SLR分析表int getReduceIndex(const State &s, int &ntIndex, int &candidateIndex) const; - 根据当前状态判断是否能规约。如果能,返回规约使用的生成式下标,并把ntIndex和candidateIndex指向生成式void getCandidateIndex(int index, int &ntIndex, int &candidateIndex) const; - 根据输入的index计算生成式在grammas中的位置,通过ntIndex和candidateIndex返回int candidateCount() const; - 返回生成式数量Candidate parseInputToCandidate(const QString &str, QVector<int> *values = nullptr) const; - 把字符串解析为符号串。如果values不为空,则解析输入串中的数字void outputSingleCandidate(int ntIndex, int candidateIndex) const; - 输出单个生成式void outputProject(const Project &p) const; - 输出一个项目void outputSymbol(const Symbol &s) const; - 输出一个符号void outputSLR_Key(const SLR_Key &key) const; - 输出一个分析表的keyvoid outputAction(const Action &a) const; - 输出一个分析动作
    public
    int insert(const string &grammaLine); - 插入一行文法。如果文法出错则返回lineCount,否则返回0bool generate(); - 生成FIRST集、FOLLOW集和预测分析表,如果生成失败则返回falsevoid output() const; - 输出消左递归后的文法、FIRST集、FOLLOW集、预测分析表bool ok() const { return !error; } - 判断此预测分析表是否能够正常继续输入int currentLineCount() const { return lineCount; } - 返回当前行数,通常用来确定错误发生的位置bool parse(const string &str, bool calculateResult = false) const; - 试图解析输入的字符串。如果calculateResult为true则输出结果。如果解析失败则返回false。

    翻译方案设计E' -> E { print(E.v) }E -> E1 + T { E.v = E1.v + T.v }E -> E1 - T { E.v = E1.v - T.v }E -> T { E.v = T.v }T -> T1 * F { T.v = T1.v * F.v}T -> T1 / F { T.v = T1.v / F.v}T -> F { T.v = F.v }F -> (E) { F.v = E.v }F -> num { F.v = num.v }其中所有符号的属性v均为综合属性,用来保存和传递结果。print(E.v)用来输出结果。
    输入设计与限制文法已内置在main函数中,不需要输入文法。能够输入的内容为:

    需要解析的符号串
    需要解析的表达式

    输入符号串时,如果输入的符号长度超过1位,需要使用两个$符号括起来。在输入符号串时,唯一需要括起来的符号就是num。示例输入:
    $num$$num$+$num$*$num$/$num$-$num$($num$+($num$-($num$*($num$/($num$)))))输入表达式时,输入的数字也需要用两个$括起来,不论数字长短。示例输入:
    $123$$123$+$123$*$123$/$123$-$123$($123$+($123$-($123$*($123$/($123$)))))
    之所以使用这样的设计是因为上次写自顶向下语法分析器的时候已经写好了解析两个$括起内容的函数

    表达式解析时的输入限制:毕竟不是词法分析器,输入数字时限制只能输入正整数,即两个$中间不能包含0-9以外的符号,但是输出可以是小数和负数。解析时会把所有由两个$括起的内容解析为num
    关键函数设计
    此处只解释DFA和分析表的生成,以及解析函数的工作原理。关于First集和Follow集的构造,在上次自顶向下语法分析时已实现,略去。

    DFA的生成void GrammaTable::getDFA(){ // 首先清除所有状态以及之前的DFA states.clear(); dfa.clear(); // 构造第一个状态,即状态0 State firstState; firstState.push_back({0, 0, 0}); // 把项目 E' -> .E 装入状态 getState(firstState); // 根据项目中的已有状态,拓展状态而生成完整状态 states.push_back(firstState); // 把状态0装入状态集合 // 扩展DFA for (int i = 0; i < states.size(); ++i) { // 在拓展的过程中states的size可能会发生变化 // 所以要注意边界 State state = states[i]; // 当前状态 QVector<State> tStates; // 临时保存由当前状态生成的状态(因为状态可能重复 DFA tDfa; // 临时保存由当前状态生成的一些转移函数 // 开始扩展 for (int j = 0; j < state.size(); ++j) { // 遍历当前状态中的每个项目 auto project = state[j]; if (project.index < grammas[project.ntIndex][project.candidateIndex].size()) { // 项目中的圆点可以右移 // 保存右移时接收的符号 auto sym = grammas[project.ntIndex][project.candidateIndex][project.index]; ++project.index; // 圆点右移 if (!tDfa.contains({state, sym})) { // 临时DFA中没有此转换函数,则新建状态和转换函数 State t; t.push_back(project); // 此状态目前仅有这一个项目 tStates.push_back(t); // 装入临时状态集 tDfa.insert({state, sym}, t); // 装入临时DFA } else { // 临时DFA中已有此转换函数 // 把此项目装入转换函数的目标状态中 DFA_Key key = {state, sym}; int index = tStates.indexOf(tDfa[key]); tStates[index].push_back(project); tDfa[key] = tStates[index]; } } } // 至此,当前状态中的每个项目都已经处理过了 // 扩展临时状态集中的状态 for (auto &s : tStates) { auto key = tDfa.key(s); getState(s); tDfa[key] = s; } // 合并DFA与临时DFA、状态集与已有状态集 // 检查扩展后的临时状态集中的状态是否与已有的状态重复 auto keys = tDfa.keys(); for (auto key : keys) { int index = states.indexOf(tDfa[key]); if (index == -1) { states.push_back(tDfa[key]); dfa.insert(key, tDfa[key]); } else { dfa.insert(key, states[index]); } } }}
    分析表的生成void GrammaTable::getSLR_Table(){ // 先根据所有状态生成规约和接收的表项 // 遍历所有状态 for (auto state : states) { int ntIndex; int candidateIndex; // 判断当前状态是否存在规约项 int reduceIndex = getReduceIndex(state, ntIndex, candidateIndex); if (ntIndex != -1 && candidateIndex != -1) { // 当前状态能够规约(默认只有一种规约情况 // 判断是接收还是规约,生成表项 for (auto s : follows[ntIndex]) { Action a; a.index = reduceIndex; if (reduceIndex == 0) { a.type = Action::ActionType::Accept; } else { a.type = Action::ActionType::Reduce; } slrTable.insert({state, s}, a); } } } // 生成移进和转移的表项 // 遍历DFA auto keys = dfa.keys(); for (auto key : keys) { if (key.s.type == Symbol::SymbolType::T) { // 移进 slrTable.insert(key, {Action::ActionType::Shift, states.indexOf(dfa[key])}); } else { // 转移 slrTable.insert(key, {Action::ActionType::Goto, states.indexOf(dfa[key])}); } }}
    输入串的解析bool GrammaTable::parse(const QString &str, bool calculateResult) const{ QVector<int> values; // 用来保存每个符号的value。没有value的符号默认value为0 Candidate candidate; // 用来保存解析输入串后得到的符号串 if (calculateResult) candidate = parseInputToCandidate(str, &values); else candidate = parseInputToCandidate(str); if (candidate.size() == 0) { cout << "Error input.\n"; return false; } candidate.push_back(END); // 在符号串末尾加上终止符$ // 声明与初始化变量 QStack<int> stateStack; QStack<Symbol> symbolStack; QStack<double> valueStack; stateStack.push(0); symbolStack.push(END); int index = 0; // 当前分析到的符号串的下标 while (index < candidate.size()) // 没有分析结束时 { // 根据当前状态和符号,从分析表获取分析动作 SLR_Key key = {states[stateStack.top()], candidate[index]}; if (!slrTable.contains(key)) // 分析表没有此项,停止分析 break; auto action = slrTable[key]; outputAction(action); // 输出动作 cout << endl; switch (action.type) { case Action::ActionType::Accept: // 接收 cout << "Accepted.\n"; if (calculateResult) cout << "Result is " << valueStack.top() << endl; return true; break; case Action::ActionType::Reduce: // 规约 { // calculate value if (calculateResult) { // 直接通过栈顶使用value栈中的内容计算结果 double t = 0; switch (action.index) { case 1: // E -> E1 + T { E.v = E1.v + T.v } t = valueStack.top(); valueStack.pop(); valueStack.pop(); t += valueStack.top(); valueStack.pop(); valueStack.push(t); break; case 2: // E -> E1 - T { E.v = E1.v - T.v } t = valueStack.top(); valueStack.pop(); valueStack.pop(); t = valueStack.top() - t; valueStack.pop(); valueStack.push(t); break; case 3: // E -> T { E.v = T.v } break; case 4: // T -> T1 * F { T.v = T1.v * F.v} t = valueStack.top(); valueStack.pop(); valueStack.pop(); t *= valueStack.top(); valueStack.pop(); valueStack.push(t); break; case 5: // T -> T1 / F { T.v = T1.v / F.v} t = valueStack.top(); valueStack.pop(); valueStack.pop(); t = valueStack.top() / t; valueStack.pop(); valueStack.push(t); break; case 6: // T -> F { T.v = F.v } break; case 7: // F -> (E) { F.v = E.v } valueStack.pop(); t = valueStack.top(); valueStack.pop(); valueStack.top() = t; break; case 8: // F -> num { F.v = num.v } break; default: break; } } // 规约状态栈和符号栈 int ntIndex; int candidateIndex; getCandidateIndex(action.index, ntIndex, candidateIndex); for (int i = 0; i < grammas[ntIndex][candidateIndex].size(); ++i) { stateStack.pop(); symbolStack.pop(); } // 规约后的状态转移 SLR_Key gotoKey = {states[stateStack.top()], {Symbol::SymbolType::NT, ntIndex}}; auto gotoAction = slrTable[gotoKey]; outputAction(gotoAction); // output cout << endl; stateStack.push(gotoAction.index); symbolStack.push(ntTable[ntIndex]); --index; // do not increase index break; } case Action::ActionType::Shift: // 移进 stateStack.push(action.index); symbolStack.push(candidate[index]); if (calculateResult) valueStack.push(values[index]); break; default: break; } ++index; } cout << "This line not belongs to this gramma.\n"; return false;}
    工作流程设计generate函数bool GrammaTable::generate(){ // 判断输入文法时是否出错 if (error) return false; // 未出错 getFirsts(); // 生成FIRST集 getFollows(); // 生成FOLLOW集 getDFA(); // 生成DFA getSLR_Table(); // 生成分析表 return true;}
    main函数GrammaTable gt; // 声明文法表QString t; // 用于保存输入串// 文法表注入目标文法gt.insert("E' -> E");gt.insert("E -> E+T | E-T | T");gt.insert("T -> T*F | T/F | F");gt.insert("F -> (E) | $num$");gt.generate(); // 生成FIRST集、FOLLOW集、DFA和分析表cout << "Output:\n";gt.output(); // 输出while (1){ // 输出提示信息 cout << "\nPress 1: Just parse input.\nPress 2: Calculate result.\nOtherwise: Exit\n"; // 获取当前模式 int mode = getch() - '0'; if (mode != 1 && mode != 2) // error input mode = 3; if (mode == 3) return 0; bool flag = true; while (flag) { cout << "\nInput a line to parse, input blank line to stop.\n"; getline(cin, t); if (t.length()) { // 未输入空串,解析 gt.parse(t, mode == 2); } else { // 输入空串,结束循环 flag = false; } }}
    测试与说明初始输出启动程序后生成First集、Follow集、DFA和分析表并输出这些内容:
    Output:Format gramma:E' -> EE -> E+T | E-T | TT -> T*F | T/F | FF -> (E) | numCandidates with index:0 E' -> E1 E -> E+T2 E -> E-T3 E -> T4 T -> T*F5 T -> T/F6 T -> F7 F -> (E)8 F -> numFirst sets:First(E'): num (First(E): num (First(T): num (First(F): num (Follow sets:Follow(E'): $Follow(E): - + $ )Follow(T): - + $ ) / *Follow(F): - + $ ) / *LR(0) DFA states:State[0]: E' -> .E E -> .E+T E -> .E-T E -> .T T -> .T*F T -> .T/F T -> .F F -> .(E) F -> .numState[1]: E' -> E. E -> E.+T E -> E.-TState[2]: E -> T. T -> T.*F T -> T./FState[3]: T -> F.State[4]: F -> (.E) E -> .E+T E -> .E-T E -> .T T -> .T*F T -> .T/F T -> .F F -> .(E) F -> .numState[5]: F -> num.State[6]: E -> E+.T T -> .T*F T -> .T/F T -> .F F -> .(E) F -> .numState[7]: E -> E-.T T -> .T*F T -> .T/F T -> .F F -> .(E) F -> .numState[8]: T -> T*.F F -> .(E) F -> .numState[9]: T -> T/.F F -> .(E) F -> .numState[10]: F -> (E.) E -> E.+T E -> E.-TState[11]: E -> E+T. T -> T.*F T -> T./FState[12]: E -> E-T. T -> T.*F T -> T./FState[13]: T -> T*F.State[14]: T -> T/F.State[15]: F -> (E).LR(0) DFA transition functions:1 + '+' -> 61 + '-' -> 711 + '*' -> 811 + '/' -> 912 + '*' -> 812 + '/' -> 92 + '*' -> 82 + '/' -> 98 + 'F' -> 138 + '(' -> 48 + 'num' -> 59 + 'F' -> 149 + '(' -> 49 + 'num' -> 510 + '+' -> 610 + '-' -> 710 + ')' -> 156 + 'T' -> 116 + 'F' -> 36 + '(' -> 46 + 'num' -> 57 + 'T' -> 127 + 'F' -> 37 + '(' -> 47 + 'num' -> 50 + 'E' -> 10 + 'T' -> 20 + 'F' -> 30 + '(' -> 40 + 'num' -> 54 + 'E' -> 104 + 'T' -> 24 + 'F' -> 34 + '(' -> 44 + 'num' -> 5SLR_1 table:13 + '$' -> R413 + '+' -> R413 + '-' -> R413 + '*' -> R413 + '/' -> R413 + ')' -> R414 + '$' -> R514 + '+' -> R514 + '-' -> R514 + '*' -> R514 + '/' -> R514 + ')' -> R53 + '$' -> R63 + '+' -> R63 + '-' -> R63 + '*' -> R63 + '/' -> R63 + ')' -> R615 + '$' -> R715 + '+' -> R715 + '-' -> R715 + '*' -> R715 + '/' -> R715 + ')' -> R75 + '$' -> R85 + '+' -> R85 + '-' -> R85 + '*' -> R85 + '/' -> R85 + ')' -> R81 + '$' -> ACC1 + '+' -> S61 + '-' -> S711 + '$' -> R111 + '+' -> R111 + '-' -> R111 + '*' -> S811 + '/' -> S911 + ')' -> R112 + '$' -> R212 + '+' -> R212 + '-' -> R212 + '*' -> S812 + '/' -> S912 + ')' -> R22 + '$' -> R32 + '+' -> R32 + '-' -> R32 + '*' -> S82 + '/' -> S92 + ')' -> R38 + 'F' -> GOTO 138 + '(' -> S48 + 'num' -> S59 + 'F' -> GOTO 149 + '(' -> S49 + 'num' -> S510 + '+' -> S610 + '-' -> S710 + ')' -> S156 + 'T' -> GOTO 116 + 'F' -> GOTO 36 + '(' -> S46 + 'num' -> S57 + 'T' -> GOTO 127 + 'F' -> GOTO 37 + '(' -> S47 + 'num' -> S50 + 'E' -> GOTO 10 + 'T' -> GOTO 20 + 'F' -> GOTO 30 + '(' -> S40 + 'num' -> S54 + 'E' -> GOTO 104 + 'T' -> GOTO 24 + 'F' -> GOTO 34 + '(' -> S44 + 'num' -> S5Press 1: Just parse input.Press 2: Calculate result.Otherwise: Exit符号串解析在初始界面按下1进入符号串解析状态。
    输入:
    $num$+$num$*$num$/$num$-$num$($num$+($num$-($num$*($num$/($num$)))))$num$($num$+($num$-($num$*($num$/($num$))))$num$+($num$-($num$*($num$/($num$)))))($num$+($num$-($nu*($num$/($num$))))输出:
    Input a line to parse, input blank line to stop.$num$+$num$*$num$/$num$-$num$S5R8GOTO 3R6GOTO 2R3GOTO 1S6S5R8GOTO 3R6GOTO 11S8S5R8GOTO 13R4GOTO 11S9S5R8GOTO 14R5GOTO 11R1GOTO 1S7S5R8GOTO 3R6GOTO 12R2GOTO 1ACCAccepted.Input a line to parse, input blank line to stop.($num$+($num$-($num$*($num$/($num$)))))S4S5R8GOTO 3R6GOTO 2R3GOTO 10S6S4S5R8GOTO 3R6GOTO 2R3GOTO 10S7S4S5R8GOTO 3R6GOTO 2S8S4S5R8GOTO 3R6GOTO 2S9S4S5R8GOTO 3R6GOTO 2R3GOTO 10S15R7GOTO 14R5GOTO 2R3GOTO 10S15R7GOTO 13R4GOTO 2R3GOTO 10S15R7GOTO 3R6GOTO 12R2GOTO 10S15R7GOTO 3R6GOTO 11R1GOTO 10S15R7GOTO 3R6GOTO 2R3GOTO 1ACCAccepted.Input a line to parse, input blank line to stop.$num$S5R8GOTO 3R6GOTO 2R3GOTO 1ACCAccepted.Input a line to parse, input blank line to stop.($num$+($num$-($num$*($num$/($num$))))S4S5R8GOTO 3R6GOTO 2R3GOTO 10S6S4S5R8GOTO 3R6GOTO 2R3GOTO 10S7S4S5R8GOTO 3R6GOTO 2S8S4S5R8GOTO 3R6GOTO 2S9S4S5R8GOTO 3R6GOTO 2R3GOTO 10S15R7GOTO 14R5GOTO 2R3GOTO 10S15R7GOTO 13R4GOTO 2R3GOTO 10S15R7GOTO 3R6GOTO 12R2GOTO 10S15R7GOTO 3R6GOTO 11R1GOTO 10This line not belongs to this gramma.Input a line to parse, input blank line to stop.$num$+($num$-($num$*($num$/($num$)))))S5R8GOTO 3R6GOTO 2R3GOTO 1S6S4S5R8GOTO 3R6GOTO 2R3GOTO 10S7S4S5R8GOTO 3R6GOTO 2S8S4S5R8GOTO 3R6GOTO 2S9S4S5R8GOTO 3R6GOTO 2R3GOTO 10S15R7GOTO 14R5GOTO 2R3GOTO 10S15R7GOTO 13R4GOTO 2R3GOTO 10S15R7GOTO 3R6GOTO 12R2GOTO 10S15R7GOTO 3R6GOTO 11R1GOTO 1This line not belongs to this gramma.Input a line to parse, input blank line to stop.($num$+($num$-($nu*($num$/($num$))))Error input.Input a line to parse, input blank line to stop.按下回车输入空行以回到初始界面。
    表达式解析在初始界面按下2进入表达式解析状态。
    输入:
    $123$$66$+$66$*$66$/$66$-$66$($123$+($789$-($789$*($123$/($123$)))))($123$+($123$-($123$*($123$/($123$))))$123$+($123$-($123$*($123$/($123$)))))输出:
    Press 1: Just parse input.Press 2: Calculate result.Otherwise: ExitInput a line to parse, input blank line to stop.$123$S5R8GOTO 3R6GOTO 2R3GOTO 1ACCAccepted.Result is 123Input a line to parse, input blank line to stop.$66$+$66$*$66$/$66$-$66$S5R8GOTO 3R6GOTO 2R3GOTO 1S6S5R8GOTO 3R6GOTO 11S8S5R8GOTO 13R4GOTO 11S9S5R8GOTO 14R5GOTO 11R1GOTO 1S7S5R8GOTO 3R6GOTO 12R2GOTO 1ACCAccepted.Result is 66Input a line to parse, input blank line to stop.($123$+($789$-($789$*($123$/($123$)))))S4S5R8GOTO 3R6GOTO 2R3GOTO 10S6S4S5R8GOTO 3R6GOTO 2R3GOTO 10S7S4S5R8GOTO 3R6GOTO 2S8S4S5R8GOTO 3R6GOTO 2S9S4S5R8GOTO 3R6GOTO 2R3GOTO 10S15R7GOTO 14R5GOTO 2R3GOTO 10S15R7GOTO 13R4GOTO 2R3GOTO 10S15R7GOTO 3R6GOTO 12R2GOTO 10S15R7GOTO 3R6GOTO 11R1GOTO 10S15R7GOTO 3R6GOTO 2R3GOTO 1ACCAccepted.Result is 123Input a line to parse, input blank line to stop.($123$+($123$-($123$*($123$/($123$))))S4S5R8GOTO 3R6GOTO 2R3GOTO 10S6S4S5R8GOTO 3R6GOTO 2R3GOTO 10S7S4S5R8GOTO 3R6GOTO 2S8S4S5R8GOTO 3R6GOTO 2S9S4S5R8GOTO 3R6GOTO 2R3GOTO 10S15R7GOTO 14R5GOTO 2R3GOTO 10S15R7GOTO 13R4GOTO 2R3GOTO 10S15R7GOTO 3R6GOTO 12R2GOTO 10S15R7GOTO 3R6GOTO 11R1GOTO 10This line not belongs to this gramma.Input a line to parse, input blank line to stop.$123$+($123$-($123$*($123$/($123$)))))S5R8GOTO 3R6GOTO 2R3GOTO 1S6S4S5R8GOTO 3R6GOTO 2R3GOTO 10S7S4S5R8GOTO 3R6GOTO 2S8S4S5R8GOTO 3R6GOTO 2S9S4S5R8GOTO 3R6GOTO 2R3GOTO 10S15R7GOTO 14R5GOTO 2R3GOTO 10S15R7GOTO 13R4GOTO 2R3GOTO 10S15R7GOTO 3R6GOTO 12R2GOTO 10S15R7GOTO 3R6GOTO 11R1GOTO 1This line not belongs to this gramma.Input a line to parse, input blank line to stop.按下回车输入空行以回到初始界面。
    运行截图
    文法和带下标的生成式


    First集、Follow集与DFA状态


    DFA转移函数


    SLR(1)分析表


    提示信息


    解析简单符号串


    解析复杂符号串


    解析简单表达式


    解析复杂表达式
    1  留言 2019-02-20 08:27:36
  • 基于JAVA CS远程监控系统软件的实现

    摘 要近年来,网络技术的不断发展,为远程监控技术的发展创造了条件。远程监控系统软件越来越受到人们的重视,其实用性也毋庸质疑。基于JAVA C/S远程监控系统软件突破了空间的限制,使用者不用亲临,在自己的电脑面前就能轻松的实现对被监控端机器的监控。本系统采用Java网络编程和Java图形编程实现。笔者在开发过程中将网络技术与远程监控理论基础相结合,实现了以下功能:能连续获得被监控端机器屏幕变化;实现被监控端硬盘文件的上传、下载;实现对鼠标、键盘的模拟;实现在远程机器上执行任意DOS命令;远程关机、远程重启计算机,方便了用户监视和操作被监控端机器。本系统从系统需求分析、概要设计、详细设计到具体的编码实现和后期的代码优化、功能测试都严格遵循了软件工程的思想。
    关键词:远程监控;Java Robot;屏幕截取;Java Socket
    1 系统需求分析及理论基础1.1 系统功能需求1.连续获得被控端机器屏幕变化。
    2.实现被控端硬盘文件的上传、下载。
    3.实现对鼠标、键盘的模拟。
    4.实现在被控端机器上执行任意DOS命令。
    5.远程关机、远程重启计算机。
    1.2 其他需求1.系统实用,界面操作简便。
    2.被监控端自动隐藏运行。
    被监控端将随电脑启动而自动运行,运行时默认无任何可见界面。
    2 远程监控系统系统设计2.1 系统总体结构设计和分析本系统设计的方案为C/S模式,在主控端电脑上安装服务器端程序,在被监控端电脑上安装客户端程序。该系统的控制过程是先在主控端电脑上执行服务器程序,在命令收发过程中,主控端向被监控端配置的UDP端口发送命令,该命令是一个控制命令,要求被监控端来连接其开启的TCP端口和要求被监控端执行的操作,如果被监控端监听到该UDP命令,主动开启TCP端口向主控端发送连接请求。这样就建立了一个特殊的远程服务,然后通过这个远程服务,主控端使用各种远程控制功能发送远程控制命令,控制被监控端电脑中的各种应用程序运行。系统总体结构如图:

    图中把软件功能分解到通信的两个端点上,即客户端和服务器端,采用Client/Server模式。这样能提高设计的灵活性,易于系统的扩展。被监控端接受主控端命令,并处理命令,然后输出命令处理结果;主控端则提供交互界面及命令处理结果显示;数据交换通道的建立由双方的数据通信模块负责。从而实现点到点的直接控制监控,满足实时性要求。
    2.1.1 主控端的主要功能模块主控端包括的模块有:消息输入,命令处理结果显示模块,通信模块。消息输入模块负责将用户界面的事件传送到通信模块,通信模块将命令信息发送到被监控端;被监控端执行操作后返回的成功或失败信息交由主控端命令处理结果显示模块根据信息的格式进行显示。
    2.1.2 被监控端的主要功能模块被监控端包括的模块有:通信模块,命令处理模块。被监控端处于侦听状态,一旦接收到合法的连接请求,就开UDP端口,并开启命令接受线程,建立连接后,通过通信模块接收主控端发来的命令信息,经命令处理模块解释后,并将结果返回到主控端。
    2.2 程序流程及分析2.2.1 被监控端启动流程在被监控端安装客户端程序,程序启动的时候,尝试开启UDP端口,如果防火墙或者杀毒软件阻止,尝试开启配置的其它UDP端口,如果都尝试完毕,启动失败。否则,将开启的UDP套接字传入,然后开启命令接收线程,目的是进行命令获取,并对获取的命令进行处理。然后将自己复制到自启动项,并改名为Explorer.jar. 流程如图。

    2.2.2 主控端启动流程在主控端安装服务器程序,主控端首先启动其控制窗口类,初始化图形在主控端安装服务器程序,主控端首先启动其控制窗口类,初始化图形显示,然后用户输入被监控端的IP地址。接着主控端开启任意TCP端口,并向该IP配置的UDP端口发送初始化命令(命令格式为ordername:port),ordername
    为命令名字,port为主控端打开的TCP端口号。主控端对开启的TCP端口进行监听,如果超时,连接失败。否则,主控端读取被监控端的状态对象,接着启动控制模块和屏幕监视模块。
    流程如图。

    2.2.3 命令接收和处理流程被监控端UDP端口启动成功后,就开始对该UDP端口进行监听,如果监听到有命令,就读取它,并判断命令是否有效,命令以ordername:port为格式,如果无效,返回继续读取命令,否则,调用相应的命令处理模块对命令进行处理,命令处理完后将处理结果返回给主控端。流程如图。

    2.2.4 图形监视线程启动流程主控端开启任意TCP端口,向被监控端的UDP端口发送“要求被监控端连接”的UDP命令,命令形式为screen:TCP Port。如果命令超时,命令执行失败,否则,如果被监视端来连接,生成Soket对象,从Soket中读取被监视端发送过来的图形对象,并将图象显示在画布上,每隔一定时间(若干秒),重新读取图象,更新画布显示,从而实现连续获得对方屏幕变化。流程如图。

    2.2.5 远程控制流程被监控端收到主控端的UDP命令:control:TCP Port,获得主控端开放的TCP端口号。成功连接主控端的TCP端口后,并从中读取事件对象,接着对事件对象进行类型判断,如果是键盘事件,就对键盘事件重演;如果是鼠标事件,就对鼠标事件重演。接着判定控制套接是否关闭,如果关闭,控制结束。否则返回继续读取事件。流程如图。

    2.2.6 文件上传流程主控端开启任意TCP端口,向被监控端发送文件上传命令,命令格式为:fileup:TCP Port。如果命令超时,文件上传失败,否则,主控端读取文件数据并发送,发送过程中如果I/O错误,文件上传失败,否则,进行文件是否读取完判断,如果读取完,则上传成功,否则,继续读取文件数据。流程如图。

    3 系统主要模块设计和具体功能实现3.1 系统主要模块设计3.1.1 消息输入,命令处理结果显示模块主控端消息输入,显示模块的开发采用JAVA图形编程,利用模块化、通用性强的特点,实现远程监控中用户控制界面的编写。
    3.1.2 通信模块实现1.Socket编程
    数据通信模块中的数据通道建立采用Socket编程。Socket支持TCP/IP协议网络通信的基本操作;它屏蔽了网络底层的通信细节,使编程简单;它对通信端点进行了抽象,提供发送和接收数据机制及打开、计算和关闭会话的能力。本项目中,客户端和服务器代表运行在Windows操作平台下,采用Java Socket来编程实现命令和数据信息的传输。
    2.通信建立
    服务器与客户端开始都必须调用socket()函数产生一个Socket套接字。由于让被监控端开TCP端口,很容易被防火墙和杀毒软件阻止,监控就难以完成。这里,我们在主控端开TCP端口,等待被监控端来主动连接,这样监控功能就能更顺利的开始。主控端首先创建一个ServerSocket对象,然后调用ServerSocket的方法accept实现监听。如果被监控端来访问,accept会返回一个socket对象,利用这个对象就可以很轻松的完成服务器和客户端的数据交换。只有被监控端有请求时才建立连接,建立连接后,客户端与服务器之间便可以双向传输数据。当得到socket对象后,主控端建立ObjectInputStream对象,被监控端建立ObjectOutputStream对象实现消息的接收和发送。完成通信后,调用ServerSocket和socket对象的close关闭套接字,结束通信。
    主控端主要通信代码:
    ServerSocket server=NewRadomSocket.openNewPort(); //开启新端口Socket socket=null;server.setSoTimeout(Parameter.TCP_TIME_OUT); //设置超时socket=server.accept(); //开启ObjectInputStream readin=new ObjectInputStream(socket.getInputStream()); //封装流,准备读取一个对象socket.close(); //关闭socket,结束通信
    被监控端主要通信代码:
    Socket socket=new Socket(serverip, serverport);ObjectOutputStream send=new ObjectOutputStream(socket.getOutputStream());//封装流send.writeObject(cc); //发送send.close(); //关闭流socket.close(); //关闭套接
    3.1.3 命令处理模块命令处理模块的实现就是通过函数调用,调用各个命令处理类。在命令处理模块中,被监控端执行以下操作:发送自己的状态信息给主控端;建立图象传送;开启控制命令套接字;文件上传;文件下载;执行DOS命令,获取执行结果和错误流。
    3.2 连续获得被监控端机器屏幕变化功能实现3.2.1 比较几种屏幕截取方法在Java远程监控过程中,我们要截取软件运行GUI界面,并将其保存到一个或一系列图像文件中。
    目前,在Windows平台下,有关屏幕截取的工具软件有许多,比如:HyperCam等,当然还可以直接利用Windows操作系统支持的屏幕拷贝Print Screen键,将屏幕拷贝到剪贴板,在保存为图像文件。这些工具软件一定要屏幕截取者,在操作过程中要”精力集中”并且”伺机捕获”所需要的软件运行界面。事实上,有时候我们需要Java应用程序,自动对运行的GUI界面进行”拍照”,比如:一台计算机要获取网络上另一台计算机正在运行的GUI界面,要看看对方计算机上软件运行情况。这就需要在Java应用程序中,自动将运行的GUI界面保存到一个图像文件中,然后通过网络传输到另一台计算机上。而上述HyperCam等工具软件无法与我们的Java应用融合为一体。因此,我们需要在Java应用程序中编写一个屏幕”照相机”。
    3.2.2 Java“屏幕照相机”的编写原理“屏幕的截取”是比较接近操作系统底层的操作,在Windows平台下,该操作似乎成了VC、VB等语言开发的专利。事实上,”屏幕的截取”在Java应用程序中,及其简单。在Java JDK1.5.0 中提供了一个”机器人”Robot类。该类用于产生与本地操作系统有关的底层输入、测试应用程序运行或自动控制应用程序运行。Robot类提供了一个方法:.createScreenCapture(..),可以直接将全屏幕或某个屏幕区域的像素拷贝到一个BufferedImage对象中,我们只需要将该对象写入到一个图像文件之中,就完成了屏幕到图像的拷贝过程。
    3.2.3 Java“屏幕照相机”的实现为了构造一个比较完善的Java屏幕”照相机”,我们构造了一个ImageProvider JavaBean,其源代码和说明如下:
    /* 该JavaBean可以直接在其他Java应用程序中调用,实现屏幕的"拍照" */public class ImageProvider {private Robot robot=null; //图象采集类private Rectangle rect=null; //要复制的 屏幕区域/* 构造函数,输入要采集的屏幕的 矩形信息*/public ImageProvider() throws AWTException{rect=new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()); //要复制的屏幕区域,这里为全屏robot=new Robot(); //创建Robot对象}/* 复制全屏幕,返回BufferedImage对象 */public BufferedImage CopyScreen(){BufferedImage image=robot.createScreenCapture(rect); //截取屏幕,生成BufferedImage对象return image;}}
    3.2.4 远程屏幕监控的实现实现屏幕监控要完成被监控端发送屏幕截图和主控端接收图片的工作,该功能的实现中构造了两个类:SendImage Thread和GetImageThread,类图分别为下图。


    3.2.5 屏幕监视功能测试在系统实现过程中,成功完成了连续获得被监控端机器屏幕变化的功能,下面为测试图片:

    本节详细介绍了远程屏幕监视的功能实现。对于功能需求中的其他功能,这里就不逐一介绍了。对于远程控制功能,如果要实现该功能,首先需要主控端向被监控端发送远程控制命令,被监控端收到命令后,首先返回给主控端一个包含被监控端屏幕分辨率等的状态信息,主控端根据状态信息更新本机所存的被控端状态,并调整画布大小,使其比例符合被监控端屏幕分辨率,这样才可以精确定位。在画布中对事件进行监听,采集事件,封装成消息发送给被监控端,然后在被监控端使用Robot对象控制鼠标对象的位置与动作,实现事件重演。这样就能实现远程控制。
    参考文献[1] Herbert Schidt.Java 2[M].北京:清华大学出版社
    [2] 耿祥义,张跃平.JAVA 2(第二版)[M].北京:清华大学出版社
    [3] Bruce Eckel.JAVA编程思想[M].机械工业出版社
    [4] 张海藩.软件工程导论[M].北京:清华大学出版社
    0  留言 2019-02-10 09:05:23
  • 基于C#的单机版连连看游戏设计与实现

    摘 要游戏“连连看”,只要将相同花色的两张牌用三根以内的直线连在一起就可以消除,规则简单容易上手。游戏速度节奏快,画面清晰可爱,老少皆宜。丰富的道具和公共模式的加入,增强游戏的竞争性。多样式的地图,使玩家在各个游戏水平都可以寻找到挑战的目标,长期地保持游戏的新鲜感。使用新颖的连击积分规则,使游戏玩家在体会连击的快感后,同时对自己的游戏速度,更有挑战性和追求极速的欲望。
    游戏通过定义数组,根据一定的算法实现规定的路径判断。
    关键字:小游戏;连连看;数组;路径判断
    前 言现在小游戏、网页游戏越来越受网民欢迎,除了玩的方法简单外(不像其他游戏还需要注册下载繁琐过程),很多游戏不乏经典。连连看游戏就是典型了!
    不管你走到哪个游戏网站,连连看游戏总是排在受玩家欢迎排名的前5位,休闲、趣味、益智是连连看玩不厌的精华,且不分男女老少、工薪白领,是一款适合大众的经典网络休闲小游戏。
    游戏产业作为现代电脑电子技术的产物,正在以其独特的魅力在全世界的娱乐领域占据主流位置,我们在承认广大娱乐网民的选择空间狭小的同时,也必须明确的一点就是游戏本身所具有的强大的吸引力。游戏的吸引力主要在于,它在让玩家打发时间的同时,可以让人实现在显示生活中不能实现的理想,得到在现实中不能得到的东西。而且游戏产业促动高科技技术不断升级,作为经济增长的一大支撑点,已经成为经济腾飞的“第四产业”。
    一、游戏规则
    概要
    玩家可以将 2 个相同图案的牌连接起来,连接线不多于 3 根直线,就可以成功将两个牌消除;
    操作
    第一次使用鼠标点击游戏界面中的牌,该牌此时为”被选中”,以特殊方式显示;再次以鼠标点击其他牌,若该牌与被选中的牌图案相同,且把第一个牌到第二个牌连起来,中间的直线不超过 3 根,则消掉这一对牌,否则第一个牌恢复成未被选中状态,而第二个牌变成被选中状态。
    胜利条件
    将游戏界面上的牌全部消除掉。
    失败条件
    到规定时间,界面上的牌仍未全部消掉。

    二、发展概况游戏“连连看”顾名思义就是找出相关联的东西,这个连连看在网上基本是用在小游戏中,就是找出相同的两样东西,在一定的规则之内可以做为相关联处理。“连连看”的发展经历了从桌面游戏、在线游戏、社交游戏三个过程。
    游戏“连连看”是源自台湾的桌面小游戏,自从流入大陆以来风靡一时,也吸引众多程序员开发出多种版本的“连连看”。这其中,顾方编写的“阿达连连看”以其精良的制作广受好评,这也成为顾方“阿达系列软件”的核心产品。并于2004年,取得了国家版权局的计算机软件著作权登记证书。
    随着Flash应用的流行,网上出现了多种在线Flash版本“连连看”。如“水晶连连看”、“果蔬连连看”等,流行的“水晶连连看”以华丽界面吸引了一大批的女性玩家。
    2008年,随着社交网络的普及和开放平台的兴起,“连连看”被引入了社交网络。“连连看”与个人空间相结合,被快速的传播,成为一款热门的社交游戏,其中以开发者Jonevey在Manyou开放平台上推出的“宠物连连看”最为流行。
    三、方案论证3.1 设计原理本设计采用单机模式,当在规定的时间内消完全部的图片则当前关卡通过,如果在规定的时间内没能消完所有的图片则游戏结束,重新开始新游戏。游戏规则是模仿普通的连连看游戏,主要是鼠标两次点击的图片能否消去的问题。当前,前提是点击两张相同的图片,若点击的是同一张图片或者两张不同的图片,则不予处理。在两张想同图片用三根以内的直线能连在一起,就可以消去;否则,不予处理。
    游戏过程,如果玩家在一定的时间内消完则提示玩家胜利,并进入下一关。如果在一定时间内图片没有消完则提示玩家时间到。每关以此类推。
    考虑到本游戏软件是单机小游戏,所以充分考虑到了它的娱乐性,并没有很复杂的功能。
    3.2 方案选择在概要设计阶段,主要有两中方案可供选择:

    所有的图片都是按约定好的种类数和在同一区域的重复次数随机出现,并且每张图片的的出现次数为偶数,时间会有限制,每一关的图片数量或时间是不同的,这样就增加了游戏的难度
    在同一区域中,图片出现的种类数和重复次数是可以由玩家选择的,时间由游戏约定。不过玩家选择的种类数和重复次数必须是偶数才可以顺利完成游戏,否则游戏虽然可以正常运行,但无法完成游戏

    在第一种方案中,由于出现的图像按种类数和重复次数都由软件约定,这样就缺乏玩家自主选择的空间,只是在玩系统已经是设定好的游戏,不能改变什么,这样就在无意中降低了玩家在游戏的过程中乐趣,最后致使玩家放弃继续玩下去。我们参考了网络上的连连看游戏,考虑到游戏的娱乐性。所以我们放弃第一种方案的设计思想,参考网络上流行的连连看的游戏,设计出第二种方案。
    3.3 主要问题开始制作游戏时,主要要解决的问题有以下几个方面:如何设置整个游戏的界面;如何控制连连看游戏中随机图片的生成且每种图片必须为偶数个;游戏开始后,判断鼠标两次点击的图片能否消去,即图片是否相同且图片之间路径的判断;如何判断游戏是否结束以及输赢问题等。
    3.4 技术要求本游戏软件可以再大多数计算机上运行,游戏中能正确判断鼠标两次点下的图片是否可以消去、能正确判断游戏是否已经结束。
    四、系统设计针对上面的需求分析,我们把整个软件分成两个模块:

    整体界面的设计和图片的随机生成
    图片路径判断函数

    以下就是系统结构图:

    游戏运行界面

    4.1基本思路4.1.1 游戏画面问题的思路画面,对于设计者来说,可以算是最简单的地方;但对于玩家,这却是最重要的,一般玩家不会关心你是怎么实现的,他所关心的是画面的美观,漂亮,是不是能让人赏心悦目。
    4.1.2 获取图片位置的思路通过数组从图片库随即获取规定个数得到图片,随机分布在画布上。图片个数一定是偶数个。
    4.1.3 路径判断的思路连连看所要求的是:

    两个目标是相同的
    两个目标之间连接线的折点不超过两个

    连接线由x轴和y轴的平行线组成,那么分析一下连接的情况可以看到,一般分三种情况:

    直线相连
    一个折点
    两个折点

    可以发现,如果有折点,每个折点必定有且至少有一个坐标(x或者y)是和其中一个目标点是相同的,也就是说,折点必定在两个目标点所在的x方向或y方向的直线上。
    所以设计思路就是:
    假设目标点 p1 , p2 ,如果有两个折点分别为z1 , z2 那么,所要进行的是

    如果验证p1 , p2 直线连线,则连接成立
    搜索以p1,p2的x,y方向四条直线(可能某两条直线会重合)上的有限点,每次取两点作为z1,z2 ,验证p1到z1/z1到z2/z2到p2 是否都能直线相连 ,是则连接成立

    4.1.4 其他问题的思路其他功能将在后面的具体各个部分的设计过程当中分别进行介绍。
    4.2 主界面的设计由于这个程序的界面并不是很复杂,所以用到的控件也不多,主要核心内容还是后台的代码设计。图片的随机生成主要是用到一个random()函数将随机数赋值给flag[]数组中的每个元素,然后根据数组元素值,来显示图片。(这个在3.1.2的思路中有详细的介绍)
    4.2.1 界面的设计
    色彩上:总结人们的视觉习惯和色彩对眼睛的健康影响,决定对于画布采用黑色背景,神秘而大方;右边的控制区采用天蓝色,配合左边纯黑的背景,就像黑夜中的蓝天,纯洁而大气
    功能上:背景就是窗体,右侧是一个groupbox控件,用来放置控制按钮,下方是一个progressbar控件,用来显示时间条


    4.2.2 图片的随机生成实现这个功能要分很多个步骤:
    1.程序运行时即载入游戏需要的N张图片,默认情况下图片种类数是18,重复数是4(重复数必须是偶数),并且可以选择是否重列。通过一个循环,加载随即的选择N种图片。具体载入图片的代码如下:
    //加载图 private void IniteBmp(int maxnum) { g_g = this.CreateGraphics(); for (int i = 0; i < MAPWIDTH; i++) for (int j = 0; j < MAPHEIGHT; j++) gmap[i, j] = 0; IniteRandomMap(ref gmap, maxnum); AI = new Kernal(ref gmap); for (int i = 0; i < maxnum; i++) { ResourceManager rm = new ResourceManager("LLK.data", Assembly.GetExecutingAssembly()); img[i]= (Image)rm.GetObject(i.ToString()+".bmp"); //img[i] = (Image)Bitmap.FromFile("Images\\"+(i+1).ToString()+".bmp"); } for (int i = 0; i < 6; i++) { //bombimg[i] = (Image)Bitmap.FromFile("Images\\B"+(i+1).ToString()+".bmp"); } }
    2.当确认游戏开始时,通过画图过程完成图片生成,画图的过程代码如下:
    private bool CheckWin(ref int[,] map) { bool Win = true; for (int i = 0; i < MAPWIDTH; i++) for (int j = 0; j < MAPHEIGHT; j++) if (map[i, j] != 0) Win = false; return Win; } private void IniteRandomMap(ref int[,] map,int num) { Random r=new Random(); while (num > 0) { for (int i = 0; i < multipic; i++) { int xrandom = r.Next(19) ; int yrandom = r.Next(11) ; if (map[xrandom, yrandom] == 0) { map[xrandom, yrandom] = num; } else i--; } num--; } } private void FreshMap(ref int[,] map) { Random r = new Random(); for(int i=0;i<MAPWIDTH;i++) for (int j = 0; j < MAPHEIGHT; j++) { if (gmap[i, j] != 0) { int x = r.Next(19); int y = r.Next(11); int temp = gmap[x, y]; gmap[x, y] = gmap[i, j]; gmap[i, j] = temp; } } TransportMap(ref gmap); } private void TransportMap(ref int[,] map) { for(int i=0;i<MAPWIDTH;i++) for (int j = 0; j < MAPHEIGHT; j++) { AI.GiveMapValue(i, j, map[i, j]); } } //在指定位置画指定图 private void Draw(Graphics g, Image scrImg, int PicX,int PicY) { g.DrawImage(scrImg, new Point(PicX, PicY)); } private void Form1_Paint(object sender, PaintEventArgs e) { g_g.DrawLine(new Pen(new SolidBrush(Color.DeepSkyBlue), 5), 0, 11 * 34 + 5, 19 * 34, 11 * 34 + 5); if (bStart) { for (int i = 0; i < MAPWIDTH; i++) for (int j = 0; j < MAPHEIGHT; j++) { if (gmap[i, j] != 0) { Draw(g_g, img[gmap[i, j] - 1], i * PICWIDTH, j * PICHEIGHT); } } } }
    4.2.3 单击控件的事件触发总共有两个单击按钮,分别是:

    开始游戏(进入游戏状态)
    重列(重新加载图片)

    开始游戏实现代码如下:
    bool starttimer = false; private void button1_Click_1(object sender, EventArgs e) { //处理Processbar if (!starttimer) { progressBar1.Value = PBMAX; pbtimer.Interval = 500; pbtimer.Start(); starttimer = true; } //处理分数 score = 0; picnum = Convert.ToInt16(textBox1.Text); multipic = Convert.ToInt16(textBox2.Text); if (picnum * multipic > 209) { MessageBox.Show("游戏区域内最多只有209个空,您选的数据太多!请重新选 !"); textBox1.Text = "18"; textBox2.Text = "4"; return; } IniteBmp(picnum); if (bStart) { MessageBox.Show("游戏已在运行!"); return; } else { bStart = true; this.Invalidate(); music.Play("Sounds\\bg-03.mid"); } }
    重列实现代码如下:
    private void RefreshMap(ref int[,] map) { if (bStart) { for (int i = 0; i < MAPWIDTH; i++) for (int j = 0; j < MAPHEIGHT; j++) { if (gmap[i, j] != 0) { Draw(g_g, img[gmap[i, j] - 1], i * PICWIDTH, j * PICHEIGHT); } } } private void FreshMap(ref int[,] map) { Random r = new Random(); for(int i=0;i<MAPWIDTH;i++) for (int j = 0; j < MAPHEIGHT; j++) { if (gmap[i, j] != 0) { int x = r.Next(19); int y = r.Next(11); int temp = gmap[x, y]; gmap[x, y] = gmap[i, j]; gmap[i, j] = temp; } } TransportMap(ref gmap); } private void button2_Click(object sender, EventArgs e) { refreshplayer.Play(); FreshMap(ref gmap); this.Invalidate(); }
    4.2.4 得分设置本游戏一改前人风格,采用全新计分方式,对于不同的路径会得到不同的分数,使人们在寻找相同图片的同时还要注意路径的选择,更增加了游戏的趣味性,具体规则:直连得10分,一个拐点得20,两个拐点得40。用一个Label控件存储得分。实现代码:
    switch (corner[2].X) { case 1: score += 20;//一个拐点加20; g_g.DrawLine(pen, new Point(p1.X*31+15,p1.Y*34+17), new Point(corner[0].X*31+15,corner[0].Y*34+17)); g_g.DrawLine(pen, new Point(p2.X * 31+15, p2.Y * 34+17), new Point(corner[0].X * 31+15, corner[0].Y * 34+17)); Thread.Sleep(100); EraseBlock(g_g, p1, p2); g_g.DrawLine(bkpen, new Point(p1.X * 31 + 15, p1.Y * 34 + 17), new Point(corner[0].X * 31 + 15, corner[0].Y * 34 + 17)); g_g.DrawLine(bkpen, new Point(p2.X * 31 + 15, p2.Y * 34 + 17), new Point(corner[0].X * 31 + 15, corner[0].Y * 34 + 17)); break; case 2: score += 40;//两个拐点加40 Point[] ps ={ new Point(p1.X*31+15,p1.Y*34+17),new Point( corner[1].X*31+15,corner[1].Y*34+17),new Point(corner[0].X*31+15,corner[0].Y*34+17),new Point(p2.X*31+15,p2.Y*34+17)}; g_g.DrawLines(pen, ps); Thread.Sleep(100); EraseBlock(g_g, p1, p2); g_g.DrawLines(bkpen,ps); break; case 0: score += 10;//直连加10 g_g.DrawLine(pen, new Point(p1.X * 31 + 15, p1.Y * 34 + 17), new Point(p2.X * 31 + 15, p2.Y * 34 + 17)); Thread.Sleep(100); EraseBlock(g_g, p1, p2); g_g.DrawLine(bkpen, new Point(p1.X * 31 + 15, p1.Y * 34 + 17), new Point(p2.X * 31 + 15, p2.Y * 34 + 17)); break; default: break; } label5.Text = score.ToString();
    4.2.5 过关设置把界面上全部图片都消去即为通过此关,进入下一关,种类数加1。实现代码:
    private bool CheckWin(ref int[,] map) { bool Win = true; for (int i = 0; i < MAPWIDTH; i++) for (int j = 0; j < MAPHEIGHT; j++) if (map[i, j] != 0) Win = false; return Win; //判断过关 } if (CheckWin(ref gmap)) { starttimer = false; MessageBox.Show("恭喜过关!"); bStart = false; picnum++;//种类加1 }
    4.2.6 音乐设置音乐分为背景音乐和点击的音乐。具体实现如下:
    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using System.Media; using System.Runtime.InteropServices;namespace LLKclass Music { [DllImport("winmm.dll")] private static extern int mciSendString ( string lpstrCommand, string lpstrReturnString, int uReturnLength, int hwndCallback ); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern int GetShortPathName ( [MarshalAs(UnmanagedType.LPTStr)] string path, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath, int shortPathLength ); public void Play(string FileName) { StringBuilder shortPathTemp = new StringBuilder(255); int result = GetShortPathName(FileName, shortPathTemp, shortPathTemp.Capacity); string ShortPath = shortPathTemp.ToString(); mciSendString("open " + ShortPath + " alias song", "", 0, 0); mciSendString("play song", "", 0, 0); } public void Stop() { mciSendString("stop song", "", 0, 0); } public void Pause() { mciSendString("pause song", "", 0, 0); } public void Close() { mciSendString("close song", "", 0, 0); } }
    4.2.7 时间控制用pbtimer来控制时间,progressbar控件显示时间进度条。具体代码如下:
    private void pbtimer_Tick(object sender, EventArgs e) { pbvalue = pbvalue - reducestep; if (pbvalue > 100) pbvalue = 100; if (pbvalue == 0&&starttimer) { starttimer = false; pbtimer.Stop(); MessageBox.Show("Failed!"); return; } else progressBar1.Value = pbvalue; }
    4.2.8 其它控件与功能两个文本框中一个是用来输入加载的图片种类数;另一个是用来输入图片重复数的,因为画布的大小所限,只有209个格,所以要保证输入的两个数的乘积在209以下。
    picnum = Convert.ToInt16(textBox1.Text); multipic = Convert.ToInt16(textBox2.Text); if (picnum * multipic > 209) { MessageBox.Show("游戏区域内最多只有209个空,您选的数据太多!请重新选 !"); textBox1.Text = "18"; textBox2.Text = "4"; return; }
    4.3 图片是否可消除的判断实现的思路前面已经说了,具体实现代码如下:
    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using System.Media;using System.Runtime.InteropServices;namespace LLK { class Kernal { private const int M = 19; private const int N = 11; private const int BLANK = 0; private static int[,] map = new int[M, N]; Point[] arr1 = new Point[209]; int arr1Len, arr2Len; Point[] arr2 = new Point[209]; static Point[] corner = new Point[2]; //用来存储两个拐点 static int co = 0; //用来标识几个拐点 public Kernal(ref int[,] mmap) { for (int i = 0; i < M; i++) for (int j = 0; j < N; j++) map[i, j] = mmap[i, j]; corner[0] = new Point(0, 0); corner[1] = new Point(0, 0); } public Point[] GetPoints() { Point[] p = new Point[3]; p[0] = corner[0]; p[1] = corner[1]; p[2] = new Point(co, 0); return p; } public bool IsShare(ref Point[] a1, int a1Len, ref Point[] a2, int a2Len) { bool result = false; for (int i = 0; i < a1Len; i++) for (int j = 0; j < a2Len; j++) if (a1[i].X == a2[j].X && a1[i].Y == a2[j].Y) { corner[0] = new Point(a1[i].X, a1[i].Y); result = true; } return result; } public bool IsDirectLink(int x1, int y1, int x2, int y2) { if (x1 == x2 && y1 == y2) { return false; } if (x1 == x2) { int bigger = y1 > y2 ? y1 : y2; int smaller = y1 > y2 ? y2 : y1; int miny = smaller+ 1; while (map[x1, miny] == BLANK) { miny++; if (miny >= N) break; } if (miny == bigger) return true; else return false; } if (y1 == y2) { int bigger = x1 > x2 ? x1 : x2; int smaller = x1 > x2 ? x2 : x1; int minx = smaller + 1; while (map[minx, y1] == BLANK) { minx++; if (minx >= M) break; } if (minx == bigger) return true; else return false; } return false; } public int FindEmpty(int x, int y, ref Point[] arr) { int count = 0; int pos = x - 1; while (0 <= pos && pos < M && map[pos, y] == BLANK) { arr[count].X = pos; arr[count].Y = y; pos--; count++; } pos = x + 1; while (0 <= pos && pos < M && map[pos, y] == BLANK) { arr[count].X = pos; arr[count].Y = y; pos++; count++; } pos = y - 1; while (0 <= pos && pos < N && map[x, pos] == BLANK) { arr[count].X = x; arr[count].Y = pos; pos--; count++; } pos = y + 1; while (0 <= pos && pos < N && map[x, pos] == BLANK) { arr[count].X = x; arr[count].Y = pos; pos++; count++; } return count; } public bool IndirectLink(int x1, int y1, int x2, int y2) { int pos = 0; Point[] ar1 = new Point[209]; int ar1Len = 0; Point[] ar2 = new Point[209]; int ar2Len = 0; pos = y1 - 1; while (0 <= pos && pos < N && map[x1,pos] == BLANK) { ar1Len = FindEmpty(x1, pos, ref ar1); ar2Len = FindEmpty(x2, y2, ref ar2); if (IsShare(ref ar1, ar1Len, ref ar2, ar2Len)) { co = 2; corner[1] = new Point(x1, pos); return true; } pos--; } pos = y1 + 1; while (0 <= pos && pos < N && map[x1,pos] == BLANK) { ar1Len = FindEmpty(x1, pos, ref ar1); ar2Len = FindEmpty(x2, y2, ref ar2); if (IsShare(ref ar1, ar1Len, ref ar2, ar2Len)) { co = 2; corner[1] = new Point(x1, pos); return true; } pos++; } //如果两点是左右且非直连关系 pos = x1 - 1; while (0 <= pos && pos < M && map[pos,y1] == BLANK) { ar1Len = FindEmpty(pos, y1, ref ar1); ar2Len = FindEmpty(x2, y2, ref ar2); if (IsShare(ref ar1, ar1Len, ref ar2, ar2Len)) { co = 2; corner[1] = new Point(pos, y1); return true; } pos--; } pos = x1 + 1; while (0 <= pos && pos < M && map[pos,y1] == BLANK) { ar1Len = FindEmpty(pos, y1, ref ar1); ar2Len = FindEmpty(x2, y2, ref ar2); if (IsShare(ref ar1, ar1Len, ref ar2, ar2Len)) { co = 2; corner[1] = new Point(pos, y1); return true; } pos++; } //如果非上下非左右,即构成矩形的关系 return false; } public bool IsLink(int x1, int y1, int x2, int y2) { if (x1 == x2 && y1 == y2) { return false; } if (map[x1,y1] == map[x2,y2]) { if (IsDirectLink(x1, y1, x2, y2))//直线连接 { co = 0; return true; } else { arr1Len = FindEmpty(x1, y1, ref arr1); arr2Len = FindEmpty(x2, y2, ref arr2); if (IsShare(ref arr1, arr1Len, ref arr2, arr2Len))//单拐点连接 { co = 1; return true; } else { return IndirectLink(x1, y1, x2, y2);//双拐点连接 } } } return false; } public void GiveMapValue(int XX, int YY, int value) { map[XX,YY] = value; } }}
    五、结果分析由于经验不足,能力有限,第一次成功运行出现好多问题。

    过关后,时间不回满
    过关后,分数不累计,而且难度没加大,相当于重新开始了
    开始没有重列,以至于有时候进入死局,也就是没有能练的图片了

    以后不断地完善,终于解决了这些问题,使功能趋于完善。
    六、总结这个游戏是我张兴超合作完成的,由于以前没有配合过,控件命名,代码书写都有各自的习惯,给程序的写作带来了很多的麻烦。为了克服这些困难解决这些问题,我们花了一下午的时间用于交流,结合个人的习惯,取长补短,去芜存菁,统一了格式。
    实践证明我们那一个下午没有白费,在以后的合作中我们配合默契,节省了很多的时间。让我明白了,团队就不是一个人的事,要学会适应,交流和包容。
    这个游戏分两个模块,其中第一的内容是非常分散,而且内部又有很多小模块,互相之间也有非常密切的联系,很多变量之间数据的传递都要考虑好。而第二个模块(即路径判断问题)相对比较集中,代码内容非常长,工作量也是很大的,且几乎全部是在代码中编写,没有添加什么界面上的控件操作。
    另外这个游戏没有用到数据库,所使用的功能也不是很多,但是由于考虑到要按固有的游戏规则来编写,因此要写较多时间考虑游戏怎么玩,怎么通过代码判断用户每个鼠标点击的坐标以及当前坐标位置代表的图片内容等各方面的判断考虑得就较多了。
    参考文献[1] 明日科技公司. 《Visual C# 2005程序设计》. 北京: 人民邮电出版社
    [2] 李继武. 编著.《Visual C#.NET项目开发实战》. 北京: 清华大学出版社
    [3] 李继武、彭德林. 主编.《C#语言程序设计》. 中国水利水电出版社. 2006
    1  留言 2018-11-14 07:58:44
  • 基于C语言实现的24游戏-高级语言源程序注释部分的处理-单项选择题标准化考试系统

    项目一、24点游戏1 问题描述1.1 问题描述
    任意给出4张牌,计算能否用+、-、×、÷将其组合成24
    输出其可能的组合式

    1.2 游戏描述棋牌类益智游戏,要求结果等于二十四,一起来玩玩吧!这个游戏用扑克牌更容易来开展。拿一副牌,抽去大小王后(初练也可以把J/Q/K也拿去),剩下1~10这40张牌(以下用1代替A)。
    任意抽取4张牌(称为牌组),用加、减、乘、除(可加括号)把牌面上的数算成24。每张牌必须且只能用一次。如抽出的牌是3、8、8、9,那么算式为(9-8)×8×3=24。
    它始于何年何月已无从考究,但它以自己独具的数学魅力和丰富的内涵正逐渐被越来越多的人们所接受。这种游戏方式简单易学,能健脑益智,是一项极为有益的活动。
    2 功能分析2.1 帮我计算当我们用扑克牌玩24点时,抽出四张扑克牌,自己死活也想不出来,难道这四张扑克牌真的算不出来24点?
    “帮我计算”是24点程序的核心内容。用户输入4张扑克牌,由计算机帮助用户得到所有解并输出到屏幕上。用户不禁感叹,自己万万没有想到,原来可以这样算出24点。
    拓展:

    支持输入JQKA、jqka 甚至是小数
    支持24点的修改,用户可以改成36点、10点等等

    算法详见 算法分析 部分
    2.2 开始练习当你想锻炼一下自己算24点的能力时,那么请开始练习!
    系统随机给出4张扑克牌,当然,这四张扑克牌是可以算出24点的。然后,亲们请输入算式比如 (13+1)*2-4 然后按回车 程序内置的计算器可以计算出结果并且判断是不是等于24,如果不是24的话,程序会告诉你正确的解法的。通过不断的练习,你的计算24点的水平会越来越高的。
    2.3 开始挑战当你的计算水平越来越高,练习模式是远远不能满足你的需求的,那么,挑战(天梯、闯关)模式才是你的归属。一次出错,立刻出局。准备好了吗?挑战一下你自己吧,看看自己能闯多少关。
    2.4 双人模式独乐乐不如众乐乐,两个人比比谁算的快也是不错的。输入用户A和用户B的姓名后,开始PK!A先算出来按空格,B先算出来按回车,如果两个人都算不出来,可以按其他键跳过。A按了空格之后,要输入有效的算式才会加分。如果答案错误,那么A是会扣分哦。结束PK后,会有简单的判定判断谁获胜或是平局。
    2.5 设置输入相应的数字,可以修改如下设置:

    当前下限(最小的扑克牌):默认A
    当前上限(最大的扑克牌):默认K
    is 24点(该项设置不会在下次游戏开启时载入…)默认 24
    重置所有设置(程序有bug时也可以重置)
    下次运行游戏是否载入当前设置:默认 是
    返回菜单



    3 算法分析3.1 穷举法给定 4 个数 a,b,c,d ,如何用加减乘除以及括号将其组合起来,得到结果24,是求解24点游戏的算法。由于 a,b,c,d 可以任意交换位置,故有 A44 =24种不同的排列。对于a ,b,c,d 的某个排列 A B C D,中间可以安排运算( 表示+、-、×、 /中的任何一个),即:A B C D 。如果选取所有不同的运算符组合,3 个位置的运算符组合有4^3 =64种排列。
    再考虑到运算的先后顺序,又有 5 种排列。它们分别是:(A B) (C D)、 ((A B) C) D 、(A (B C)) D 、A ((B C) D)、A (B (C D))。总共有 24×64×5=7680 种不同的算法。此法穷尽了所有可能的组合,它不会遗漏任何一个可能的解。如果有 4 个数,按此法找不到结果为 24 的解,那就是无解。
    3.2 制定重解消除规则
    满足加法交换律的只输出1组解
    尽量少的使用括号,最好不使用括号
    如果有括号,那么括号必须放在最前

    经过以上规则,可以消除大部分重复解。
    3.3 模拟二叉树
    3.4 循环实现以及栈的恢复
    4 函数详解void main(void); //程序入口void menuPrint(); //显示菜单并根据settings.mode的值显示光标int move(); //根据键盘的输入,显示菜单和光标,如果点击回车或数字则进入相应下一级函数void exitGame(void);//退出程序void userGet();//与userGetIn()配合使用,直到用户正确输入四张牌才退出死循环void exercise();//练习模式核心函数void tianti();//天梯模式核心函数void pk();//pk模式核心函数void print_settings();//设置界面核心函数void help();//帮助界面核心函数void change_settings(int num);//设置界面二级函数,传入要修改设置的内容序号,修改相应的设置void save_settings();//保存设置到本地文件void initSettings_temp();//初始化菜单光标 指向 1.帮我计算void resetting();//重置所有设置void read_settings();//读取上次保存的设置.第一次打开游戏时显示帮助界面char * number_to_poker(float number);//把读入的数字变成扑克(字符数组)float poker_to_number(char *poker);//把扑克(字符数组)变成浮点数void initAllFromCon();//恢复堆栈数组(从原始数组中)void initArrFromCur();//恢复临时数组float calc(float n1, float n2, char o);//传入数字n1.n2.和操作符(+-*/),返回一个浮点数void randomGet();//随机得到四个能计算出24点的扑克牌void printResult_1(int a,int b,int c);//传来三个操作符 读取数组cur[4]的四个数据 打印出来void printResult_2(int a,int b,int c);//等同于printResult_1int s_first(int isPrint);//模拟平衡二叉树之单挂 传入参数为1时输出正解 0则不输出int s_second(int isPrint);//模拟平衡二叉树之双链 这两个函数是核心算法 返回1时则是可以算出 返回0就是算不出24点int userGetIn();//帮我计算核心函数char * keyControl_to_charPointer(int key);//逗比函数 未完成的函数 多半是废了char key_to_char(int key);//读取键盘输入 返回字符型 多半也是废了int test(void);//测试函数void translate(char str[],char exp[]);//对堆栈的细节处理float cal_value(char exp[]);//一个使用堆栈的计算器 支持() + - * /int zhabi();//调用translate和cal_value函数void goto_pos(int x, int y);//光标移动函数
    项目二、高级语言源程序注释部分的处理1 问题描述1.1 程序描述1.1.1 任务描述将C语言程序中的所有注释都去掉,并将去掉注释的文件和注释分别放入一个新的文件中。
    1.1.2 功能描述
    读取用户指定名字的源程序,例如,用户输入:exercise.cpp,程序能读取该文件进行处理
    将文件中的注释(同一行中//之后的部分,以及/*和*/之间的部分,包括//、/*和*/)部分删除
    将去掉的注释部分和删除注释后的C语言程序,分别保存到两个不同的文件中,文件名允许用户指定

    1.2 程序描述程序在实现上述功能后,还可以直接在框框里面输入代码,在Windows 10下可以直接Ctrl+V粘贴,但是在Windows 7下则不可以,这是由于Win32控制台的限制。同时,不能以各种键(如ESC)作为结束标志,也不能以回车作为结束标志。所以,我选择了@符号作为结束标志。如果代码中有@,推荐直接读取xxx.cpp就好。
    2 函数详解int first(void);//读取本地文件进行代码与注释分离int second(void);//在框框里面输入代码分离注释void menuPrint(int i);//根据光标的位置打印出对应的菜单int move();//移动光标int main(void);//主函数


    项目三、单项选择题标准化考试系统1 问题描述1.1 任务描述设计一个单项选择题的考试系统,可实现自动组卷功能。
    1.2 功能描述1.2.1 管理员功能
    试题管理:每个试题包括题干、4个备选答案、标准答案等信息。可进行试题添加、删除、修改、查询、保存、浏览等操作
    组卷功能:指定题目数、题目总分,生成试卷。将试卷输出到文件,将答案输出到另一个文件
    用户管理:对用户信息进行添加、删除、修改、查询、保存、浏览等操作
    用户答题情况管理:指定用户,统计其做试卷的总次数,以及每次所得的总分

    1.2.2 用户功能
    练习功能:用户指定题目数,随机选题。对用户的答案与标准答案进行对比,并最终给出成绩。对错误题目,要能给出正确答案
    考试功能:用户指定题目数,随机生成总分100分的试卷,系统可根据用户答案与标准答案的对比实现判卷,并给出成绩。并将用户所答试卷、用户的答案、用户所得总分,输出到磁盘文件保存。进行基本试题分析

    1.2.3 设计提示管理员和用户分别通过密码登录,进行题目维护和答题操作。用户产生的答题文件,应以用户名+系统时间.txt的形式存储,以便于进行管理。
    2 程序流程(框图结构)





    3 函数详解//main.cppvoid main(void);//程序入口int move_1(int num,void (*p)(int) );//集大成的move_1函数 传入菜单选项的个数已经相应的void menuPrint_x(int a); 即可打印各种菜单void menuPrint(int a);//菜单显示函数void printLaugh(int x, int y);//打印(*^_^*)并把光标移动至笑脸的中央void goto_pos(int x, int y);//光标移动函数int fuzzy_search(char str[],char str2[]);//模糊查找函数,char str[]为源字符串,char str2[]为查找的关键字
    //admin.cppint sign_in();//管理员登录界面void admin();//管理员控制界面 1.试题管理(增删改查) 2.组卷功能 3.用户账号及答题情况管理 4.修改管理员密码 0.注销登录void menuPrint_2(int a);//显示菜单~~管理员登录界面void question_admin();//question() -> question.cppvoid auto_paper();//自动出卷void user_admin();//进入函数admin_user()void change_admin_password();//修改管理员密码struct info * find_info_num(struct info * head,int num);//根据题号查找试题struct info * find_info_score_num(struct info * head,int lev);//根据分数查找试题 多半是废了
    //question.cppvoid question();//试题管理界面void menuPrint_3(int a);//显示菜单~~试题管理界面struct info * solo_info();//申请malloc单个题目void input_info(struct info * head);//新建一个题目并初始化题目信息带头结点的尾插法void write_info(struct info * head);//把题库写入到文件info.txt中struct info * read_info();//从本地文件info.txt读入题库void free_info(struct info * head);//释放链表void print_info(struct info * node);//输出所有题目的信息void print_info_solo(struct info * node);//输出一个题目全部信息到屏幕void fprint_info_solo(FILE *fp,struct info * node);//输出一个题目全部信息到文件fp
    //time.cppvoid print_time(long lt);//输出当前时间到屏幕void sprint_time(char * buf);//输出当前时间到字符数组
    //find_info.cppstruct info * find_info(struct info * head);//查找试题界面void menuPrint_4(int a);//显示菜单~~查找试题界面struct info * find_info_key(struct info * head);//按照关键字查找题目,返回题目的上一个指针void delete_info_no(struct info * find);//删除题目信息struct info * find_info_no(struct info * head);//按照题目编号查找题目,返回题目的上一个指针void change_info(struct info * node);//修改题目详细信息void find_info_next(struct info * node);//选择修改题目信息或删除题目,传来的是题目的上一个节点struct info * find_info_lever(struct info * head);//按题目难度查找struct info * find_info_score(struct info * head);//按题目分值查找struct info * find_info_time(struct info * head);//按最近添加/修改题目
    //admin_user.cppvoid admin_user();//学生信息管理界面void menuPrint_5(int a);//显示菜单~~学生信息管理界面struct user * solo_user();//申请内存void input_user(struct user * head);//学生注册不~带头结点的尾插法void write_user(struct user * head);//把学生信息写入文件中struct user * read_user();//从文件中读入学生信息void free_user(struct user * head);//释放内存void print_user(struct user * node);//输出所有学生信息void print_user_solo(struct user * node);//单个输出学生信息
    //find_user.cppstruct user * find_user(struct user * head);//查找学生界面void menuPrint_6(int a);//显示菜单~~查找学生界面struct user * find_user_name_key(struct user * head);//按学生姓名(模糊)查找struct user * find_user_no_key(struct user * head);//按学生学号(模糊)查找void delete_user_no(struct user * find);//按照学号删除用户信息struct user * find_user_no(struct user * head);//按照学号查找用户struct user * find_user_name(struct user * head);//按照姓名查找用户void change_user(struct user * node);//修改用户学号和密码void find_user_next(struct user * node);//修改用户信息或删除用户struct user * find_user_time(struct user * head);//最近登录的用户
    //user.cppvoid user();//学生登录界面void user_login(struct user * head,struct user * node);//用户登录后void menuPrint_7(int a);//显示菜单~~单项选择题标准化考试系统void printRectangle(int x, int y);//在指定位置输出黄色矩形void color(const unsigned short color1);//改变颜色---void exam(struct user * node);//考试int isQusetion(struct info * node,char *p);//题目是否满足要求int errorsNum(struct user * node);//计算用户错了多少题void answerWrong(struct user * node,struct info * temp);//用户答题错误之后...void execrise(struct user * node);//练习
    1  留言 2018-11-06 16:27:59
  • 基于虚拟存储区和内存工作区的页面置换算法

    一 需求分析编写程序实现:

    先进先出页面置换算法(FIFO)
    最近最久未使用页面置换算法(LRU)
    最佳置换页面置换算法(OPT)

    设计一个虚拟存储区和内存工作区,编程序演示以上三种算法的具体实现过程,并计算访问命中率,演示页面置换的三种算法,通过随机数产生一个指令序列,将指令序列转换为页地址流,计算并输出各种算法在不同内存容量下的命中率。
    二 程序设计2.1 功能设计
    产生随机序列功能

    随机生成1-128之间的整数,作为指令序列号,同时将随机生成的数字除以10取余作为该指令的页地址,随机数的生成以当前时钟做种子,保证每次生成的随机性。
    算法运行功能

    根据先进先出算法进行页面置换
    根据最近最久未使用算法进行页面置换
    根据最佳置换页面算法进行页面置换

    结果分析功能

    计算先进先出算法命中率
    计算最近最久未使用算法命中率
    计算最佳置换页面算法命中率
    分析出最优算法

    演示效果功能

    手动运行页面置换算法,一次运行一步
    自动运行页面置换算法,系统每个时间间隔自动运行一步


    2.2 运行流程程序流程图如下所示:

    算法流程图如下所示:

    三 程序实现3.1 FIFO算法 if (comboBox.getSelectedItem().equals("FIFO")) { for (int a = 0; a < block; a++) Memory[a] = -1; // 初始化为-1 for (int q = 0; q < 128; q++) { flag = false; pos++; int temp = seq[pos];// 中间变量暂时存放当前指令所在页地址 // 查看是否已经存在内存中 for (int i = 0; i < block; i++) { if (Memory[i] == temp) {// 如果命中,命中数+1,跳出循环 FIFOhit++; flag = true; break; } } // 如果未命中,考虑是否要替换 if (!flag) { if (memorynum < block) {// 如果内存没满,则不用替换 Memory[memorynum] = temp; memorynum++; } else {// 如果内存已满,选择最早调入的页面进行替换 Memory[replacepos] = temp;// Memory[replacepos]为被替换的元素,第一次满即替换memory[0] changecolor[replacepos][pos + 1] = 1;// 标记待变颜色的表格框 replacepos = (replacepos + 1) % block;// 按照block大小来循环替换 } } for (int i = 0; i < block; i++) data1[i][pos + 1] = Memory[i]; } // 显示命中率 FIFOrate.setText(String.valueOf((double) FIFOhit / 128)); int a = 0; for (int i = 0; i < block; i++) { a = i + 1; data1[i][0] = "第" + a + "块"; } DefaultTableModel tableModel1 = new DefaultTableModel(data1, tableTitle); // 表格模型对象 JTable Result1 = new JTable(tableModel1); int columnCount1 = Result1.getColumnCount(); Result1.getColumnModel().getColumn(0).setPreferredWidth(80); for (int i = 1; i < columnCount1; i++) { TableColumn tableColumn1 = Result1.getColumnModel().getColumn(i); tableColumn1.setPreferredWidth(80); } Result1.setRowHeight(40);// 指定每一行的行高40 Result1.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); JScrollPane jspResult1 = new JScrollPane(Result1); jspResult1.setBounds(10, 10, 1018, 320); jspResult1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); jspResult1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); TableCellRenderer tcr1 = new ColorTableCellRenderer(block, changecolor); Result1.setDefaultRenderer(Object.class, tcr1); JFrame FIFO = new JFrame("FIFO算法结果 命中率:" + (double) FIFOhit / 128); FIFO.getContentPane().setLayout(null); FIFO.setBounds(920, 10, 1048, 380); FIFO.setDefaultCloseOperation(2); FIFO.getContentPane().add(jspResult1); FIFO.setVisible(true); }
    3.2 LRU算法 if (comboBox.getSelectedItem().equals("FIFO")) { for (int a = 0; a < block; a++) Memory[a] = -1; // 初始化为-1 for (int q = 0; q < 128; q++) { flag = false; pos++; int temp = seq[pos];// 中间变量暂时存放当前指令所在页地址 // 查看是否已经存在内存中 for (int i = 0; i < block; i++) { if (Memory[i] == temp) {// 如果命中,命中数+1,跳出循环 FIFOhit++; flag = true; break; } } // 如果未命中,考虑是否要替换 if (!flag) { if (memorynum < block) {// 如果内存没满,则不用替换 Memory[memorynum] = temp; memorynum++; } else {// 如果内存已满,选择最早调入的页面进行替换 Memory[replacepos] = temp;// Memory[replacepos]为被替换的元素,第一次满即替换memory[0] changecolor[replacepos][pos + 1] = 1;// 标记待变颜色的表格框 replacepos = (replacepos + 1) % block;// 按照block大小来循环替换 } } for (int i = 0; i < block; i++) data1[i][pos + 1] = Memory[i]; } // 显示命中率 FIFOrate.setText(String.valueOf((double) FIFOhit / 128)); int a = 0; for (int i = 0; i < block; i++) { a = i + 1; data1[i][0] = "第" + a + "块"; } DefaultTableModel tableModel1 = new DefaultTableModel(data1, tableTitle); // 表格模型对象 JTable Result1 = new JTable(tableModel1); int columnCount1 = Result1.getColumnCount(); Result1.getColumnModel().getColumn(0).setPreferredWidth(80); for (int i = 1; i < columnCount1; i++) { TableColumn tableColumn1 = Result1.getColumnModel().getColumn(i); tableColumn1.setPreferredWidth(80); } Result1.setRowHeight(40);// 指定每一行的行高40 Result1.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); JScrollPane jspResult1 = new JScrollPane(Result1); jspResult1.setBounds(10, 10, 1018, 320); jspResult1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); jspResult1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); TableCellRenderer tcr1 = new ColorTableCellRenderer(block, changecolor); Result1.setDefaultRenderer(Object.class, tcr1); JFrame FIFO = new JFrame("FIFO算法结果命中率:" + (double) FIFOhit / 128); FIFO.getContentPane().setLayout(null); FIFO.setBounds(920, 10, 1048, 380); FIFO.setDefaultCloseOperation(2); FIFO.getContentPane().add(jspResult1); FIFO.setVisible(true); }
    3.3 OPT算法 if (comboBox.getSelectedItem().equals("FIFO")) { for (int a = 0; a < block; a++) Memory[a] = -1; // 初始化为-1 for (int q = 0; q < 128; q++) { flag = false; pos++; int temp = seq[pos];// 中间变量暂时存放当前指令所在页地址 // 查看是否已经存在内存中 for (int i = 0; i < block; i++) { if (Memory[i] == temp) {// 如果命中,命中数+1,跳出循环 FIFOhit++; flag = true; break; } } // 如果未命中,考虑是否要替换 if (!flag) { if (memorynum < block) {// 如果内存没满,则不用替换 Memory[memorynum] = temp; memorynum++; } else {// 如果内存已满,选择最早调入的页面进行替换 Memory[replacepos] = temp;// Memory[replacepos]为被替换的元素,第一次满即替换memory[0] changecolor[replacepos][pos + 1] = 1;// 标记待变颜色的表格框 replacepos = (replacepos + 1) % block;// 按照block大小来循环替换 } } for (int i = 0; i < block; i++) data1[i][pos + 1] = Memory[i]; } // 显示命中率 FIFOrate.setText(String.valueOf((double) FIFOhit / 128)); int a = 0; for (int i = 0; i < block; i++) { a = i + 1; data1[i][0] = "第" + a + "块"; } DefaultTableModel tableModel1 = new DefaultTableModel(data1, tableTitle); // 表格模型对象 JTable Result1 = new JTable(tableModel1); int columnCount1 = Result1.getColumnCount(); Result1.getColumnModel().getColumn(0).setPreferredWidth(80); for (int i = 1; i < columnCount1; i++) { TableColumn tableColumn1 = Result1.getColumnModel().getColumn(i); tableColumn1.setPreferredWidth(80); } Result1.setRowHeight(40);// 指定每一行的行高40 Result1.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); JScrollPane jspResult1 = new JScrollPane(Result1); jspResult1.setBounds(10, 10, 1018, 320); jspResult1.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); jspResult1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); TableCellRenderer tcr1 = new ColorTableCellRenderer(block, changecolor); Result1.setDefaultRenderer(Object.class, tcr1); JFrame FIFO = new JFrame("FIFO算法结果命中率:" + (double) FIFOhit / 128); FIFO.getContentPane().setLayout(null); FIFO.setBounds(920, 10, 1048, 380); FIFO.setDefaultCloseOperation(2); FIFO.getContentPane().add(jspResult1); FIFO.setVisible(true); }
    四 运行测试初始化界面如下:


    功能选择界面如下:

    运行结果界面如下:



    动态演示界面如下:

    1  留言 2018-10-31 16:57:07

发送私信

如果你想飞,放弃一切让你下降的重量

16
文章数
22
评论数
eject