HIT 软件构造 lab2实验报告

2020年春季学期
计算机学院《软件构造》课程

Lab 2实验报告

学号 1180300223
班号 1803002

目录

1 实验目标概述 1
2 实验环境配置 1
3 实验过程 1
3.1 Poetic Walks 2
3.1.1 Get the code and prepare Git repository 2
3.1.2 Problem 1: Test Graph 2
3.1.3 Problem 2: Implement Graph 3
3.1.3.1 Implement ConcreteEdgesGraph 3
3.1.3.2 Implement ConcreteVerticesGraph 6
3.1.4 Problem 3: Implement generic Graph 7
3.1.4.1 Make the implementations generic 7
3.1.4.2 Implement Graph.empty() 7
3.1.5 Problem 4: Poetic walks 8
3.1.5.1 Test GraphPoet 8
3.1.5.2 Implement GraphPoet 8
3.1.5.3 Graph poetry slam 9
3.1.6 Before you’re done 9
3.2 Re-implement the Social Network in Lab1 10
3.2.1 FriendshipGraph类 10
3.2.2 Person类 10
3.2.3 客户端main() 10
3.2.4 测试用例 11
3.2.5 提交至Git仓库 11
3.3 Playing Chess 12
3.3.1 ADT设计/实现方案 12
3.3.2 主程序MyChessAndGoGame设计/实现方案 21
3.3.3 ADT和主程序的测试方案 29
4 实验进度记录 32
5 实验过程中遇到的困难与解决途径 34
6 实验过程中收获的经验、教训、感想 34
6.1 实验过程中收获的经验和教训 34
6.2 针对以下方面的感受 34

1 实验目标概述
本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象 编程(OOP)技术实现 ADT。具体来说:
⚫ 针对给定的应用问题,从问题描述中识别所需的 ADT;
⚫ 设计 ADT 规约(pre-condition、post-condition)并评估规约的质量;
⚫ 根据 ADT 的规约设计测试用例;
⚫ ADT 的泛型化;
⚫ 根据规约设计 ADT 的多种不同的实现;针对每种实现,设计其表示 (representation)、表示不变性(rep invariant)、抽象过程(abstraction function)
⚫ 使用 OOP 实现 ADT,并判定表示不变性是否违反、各实现是否存在表 示泄露(rep exposure);
⚫ 测试 ADT 的实现并评估测试的覆盖度;
⚫ 使用 ADT 及其实现,为应用问题开发程序;
⚫ 在测试代码中,能够写出 testing strategy 并据此设计测试用例。

2 实验环境配置
IDE:Eclipse
JDK:1.8

3 实验过程
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。

3.1 Poetic Walks
该任务需要实现一个带有标记顶点的可变加权有向图的抽象数据类型, 之后的写诗为找路的过程。

实现该抽象数据类型, 题目给了两种实现方法:

  1. 通过基于边的数据类型实现带有标记顶点的可变加权有向图, 即为类ConcreteEdgeGraph.java
  2. 通过基于点的数据类型实现带有标记顶点的可变加权有向图, 即为类ConcreteVerticesGraph.java
    3.1.1 Get the code and prepare Git repository
    如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。

获取代码: 使用git-clone指令

建立本地仓库: 使用Eclipse自带的Team -> share project

管理本地开发: 使用Eclipse自带的Team -> Commit
3.1.2 Problem 1: Test Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
该部分就是将Graph.java接口中的empty函数修改为

在这里插入图片描述

但需要注意的是, 如果想通过GraphStaticTest的话, 需要将ConcreteEdgesGraph.java中的vertices函数修改为
在这里插入图片描述
3.1.3 Problem 2: Implement Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
3.1.3.1 Implement ConcreteEdgesGraph
想要实现该类的功能, 首先需要关注的是Edge类, 因为在ConcreteEdgesGraph类中使用了Edge类.
我们知道, 想要构成一条有向边, 我们需要有边的起点(源点), 边的终点, 以及边的权值. 所以在设计Edge类的时候, 我们需要这三个参数来构造一条边, 所以Edge类的参数就为

在这里插入图片描述
同样, Edge的构造器constructor为

在这里插入图片描述
对于Edge来说, source和target都不能是null, weight必须是非负数, 所以checkRep()为
在这里插入图片描述
同时对于每一个类来说, 都需要有Getter和Setter函数, 这个不必多做解释
需要介绍的是,我在Edge类中单独写了一个判断两条边是否相同的函数, 思路是如果两条边的source(起点)和target(终点)相同, 那么两条边就是相同的

在这里插入图片描述
Edge类到此结束, 接下来看ConcreteEdgeGraph类
ConcreteEdgeGraph类中题目给定的几个数据类型有点的集合vertices, 存储数据类型为Edge类的动态数组edges, 以及一个Edge类数据edge
由于没有参数, 所以构造器也不需要任何输入
在这里插入图片描述
对于ConcreteEdgeGraph类来说, edge是不能重复的, 所以checkRep()应当检测edges里是否有重复元素
在这里插入图片描述
由于ConcreteEdgeGraph类需要实现接口Graph中的函数, 忽略empty函数,接下来便一一介绍并实现Graph接口中的六个函数:

  1. add(L vertex) 函数
    该函数的作用是向点集中加入一个点. 由于Set类型的性质, 如果向Set里加入重复的元素会返回false, 所以不需要查重, 直接返回set.add()的值即可
    在这里插入图片描述
  2. set(L source, L target, int weight) 函数
    该函数规约很长, 作用也很多, 主要有四个作用:
    (1) 如果输入的点不存在且输入的权值为正数, 则需要加点加边并返回0
    (2) 如果输入的点存在边不存在且输入的权值为正数, 则需要加边并返回0
    (3) 如果输入的点和边存在且输入的权值为正数, 则需要更新这条边的权值并返回之前边的权值
    (4) 如果输入的点和边存在且输入的权值为0, 则需要删除该边并返回之前边的权值
    可以看到这个函数功能繁多, 所以需要对输入数据进行多次判定并执行相关操作, 但是想通了以后却很简单
  3. remove(L vertex) 函数
    该函数的作用是删除该点以及以该点为起点或终点的边, 具体思想就是遍历edges, 如果edges里有边以该点为起点或终点, 那么删除该边, 最后删除该点
  4. vertices() 函数
    该函数会返回图中的点集, 在这里我借鉴了学长的代码
    在这里插入图片描述
  5. sources(L target) 函数
    该函数会返回图中以target为终点的边的起点source与边的权值的映射关系(就是Map类型), 十分简单
  6. targets(L source) 函数
    该函数会返回图中以source为起点的边的终点target与边的权值的映射关系(就是Map类型), 遇上个函数极其类似

最后再讲一讲toString(), 该函数主要是为了防止表示泄露, 所以必须写, 至于怎么写比较自由, 能表现出该类的特征最好(Edge类中也要写, 但是我觉得放在这里说比较好)
这里提供我的想法
在这里插入图片描述

测试的话不用多说, 在上一次实验中已经很好地体验过测试, 仅提供测试通过的图片以及覆盖度

3.1.3.2 Implement ConcreteVerticesGraph
该类与Edge类大同小异, 主要说一说Vertex类
如果想要用vertex来实现图, 那么需要Java中的Map类型来存放与该点连接的点以及该边对应的权值, 所以需要的参数不仅有点的标签, 还需要有以该点为终点的边的起点及边的权值映射sources_map, 以及以该点为起点的边的终点及边的权值映射targets_map

在这里插入图片描述
同时为了防止泄露已经数据被篡改, 不能直接get到sources_map和targets_map, 对这两个map的操作必须在Vertex类内部实现, 这是我在听第二次试验辅导课时的老师讲的, 对我来说也算是一个帮助, 所以记录在这里

测试与ConcreteEdgesGraph相同, 仅提供测试通过的图片以及覆盖度

其余的没什么好说的, 与ConcreteEdgesGraph.java大同小异, 略去
3.1.4 Problem 3: Implement generic Graph
3.1.4.1 Make the implementations generic
只需要将原本是String的地方改为L以及加即可
需要注意的是, 我在修改之后, Edge类变成了static类型, 但也符合题目要求

3.1.4.2 Implement Graph.empty()
需要注意的不多, 之前说先不讨论的empty()函数在此才使用, 我选择ConcreteEdgeGraph类来实现
在这里插入图片描述
但是题目要求不能出现警告以及不能使用@SuppressWarnings注释, 在此附图

可以看到全部没有警告, 代码中也没有@SuppressWarnings注释

由于之前已经有String类型的测试了, 这次我选择了Integer类型, 同样只提供测试通过的图片以及覆盖度(覆盖图相对较低的原因是没有测试Edge类)

3.1.5 Problem 4: Poetic walks
3.1.5.1 Test GraphPoet
构建文本, 输入一串文字, 如果两个词之间存在一个词, 就会添加到其中,若存在多个, 选择权值最高的. 但需要注意的是, 若两个词之间没有这样的路径(即中间词的sources(起点)不包含第一个词或中间词的targets(终点)不包含第二个词)的话, 则不添加词
3.1.5.2 Implement GraphPoet
关于文件读入, 我在lab1里了解的十分详细了, 再贴一下我的那个博客地址
https://blog.csdn.net/weixin_43348617/article/details/104710633

主要讲一讲poem(String input)函数, 该函数是整个GraphPoet的核心, 但是我在参考往届学长的博客时, 发现有许多学长也没有很好地理解这个函数的作用. 像3.1.5.1说的, 需要注意的是, 若两个词之间没有这样的路径(即中间词的sources(起点)不包含第一个词或中间词的targets(终点)不包含第二个词)的话, 则不添加词, 这个是十分重要的判断标准, 是我询问一个同学时他给我的信息, 因为这是曾经自己犯的错, 所以在此需要提出, 其他的话其实难度不大,也就不需要贴代码了
3.1.5.3 Graph poetry slam
我认为与其在Main里更新, 不如直接在测试中测试好了, 我节选了两句来自莎士比亚的sonnet18中的诗句, 测试通过, 这里也仅提供测试通过的图片以及覆盖度

3.1.6 Before you’re done
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
git-push
在这里给出你的项目的目录结构树状示意图。

3.2 Re-implement the Social Network in Lab1
这个任务利用前面所写的ConcreteEdgeGraph类或者ConcreteVerticesGraph类来重新实现lab1中的FriendshipGraph, 由于同样是用图这个数据结构来实现, 想要复用P1中的ADT来实现人际关系图. 但需要注意的是, 人际关系图实际上是一个顶点带标签(人名), 无向无权值图这样的数据类型, 所以在复用P1中的代码时, 加边的时候需要双向加边以及将权值全部设为相同的固定值(例如1等)
3.2.1 FriendshipGraph类
对于FriendshipGraph类的实现, 我选择ConcreEdgesGraph类来实现(相比点图来说, 边图更加简单直观), 所以需要尽量复用ConcreEdgesGraph类中的方法
在这里插入图片描述
接下来针对lab1中的FriendshipGraph类的三个函数进行重写:

  1. addVertex(Person a) 函数
    该函数的作用是向graph加入点, 在ConcreEdgesGraph类中有相同作用的add函数, 调用即可
  2. addEdge(Person a, Person b) 函数
    该函数的作用是向graph加入边, 在ConcreEdgesGraph类中有能够实现加边操作的set函数, 同时不需要查重, 原因是set函数如果查到有边的话, 会更新该边并返回原来的权值, 但是在FriendshipGraph类中, 权值全部相同, 所以更新前后没有发生改变.
  3. getDistance(Person a, Person b) 函数
    该函数的想法我借鉴了学长的想法, 用Map类型来便是从起点到该Person的距离, 同样再用Map类型作为visited表示该Person是否被访问过. 其他的部分就是使用BFS搜索找到终点.
    3.2.2 Person类
    对于Person来说, 唯一有的标签应当是人的名字, 所以Person只需要一个name参数就行了, 同时由于name是不可更改的, 所以只需要Getter就够了
    3.2.3 客户端main()
    客户端在lab1的实验手册中说得十分详细, 所以不过多介绍了, 贴出运行main()的输出结果

3.2.4 测试用例
与lab1中的测试完全相同, 将lab1中的测试import过来就行, 仅仅提供测试通过的图片以及覆盖度

这里覆盖率较低的原因是FriendshipGraph类中有main()函数未能测试
3.2.5 提交至Git仓库
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
git-push
在这里给出你的项目的目录结构树状示意图。

3.3 Playing Chess
关于P3我单独写了一篇怎么讲抽象与现实联系的文章,在此先贴一下
https://blog.csdn.net/weixin_43348617/article/details/105528386

我对于P3的设计架构为:

在这里插入图片描述
每一级都是严格把控, 只有严格的上一级才能调控下一级, 不能越级处理,不能下级调控上级, 不能调用无关级. 也就是说只有Piece类能够直接调用Position类, 而越级的Player无法直接调用Position类; 只有Game类才能调用Board类, 而Player类无法调用和更改Board类. 这样的设计既保证了程序的严谨性和设计合理性, 又保证了程序的安全性, 由于每一级都是严格控制和绝对挂钩, 想要越级或者调用无关类的话会造成程序崩溃, 接下来一一介绍各个ADT
3.3.1 ADT设计/实现方案
设计了哪些ADT(接口、类),各自的rep和实现,各自的mutability/ immutability说明、AF、RI、safety from rep exposure。
必要时请使用UML class diagram(请自学)描述你设计的各ADT间的关系。

  1. Position类
    Position类是所有类中等级最低的, 也就是最底层的东西, 需要先介绍. 对于一个position(位置)来说, 需要有其横坐标与纵坐标, 所以fields参数应为x和y
    在这里插入图片描述
    同时Position类作为位置是可以出现负数的情况(在Piece类中会细讲), 而两种游戏的棋子坐标都是相同的类型, 不需要为其中某一个类型写特殊的方法. 同时x和y为private类型, 没有checkRep()
    在这里插入图片描述
    至于Getter和Setter不多作介绍, 只需要注意Position是可以更改坐标值即可
    在这里插入图片描述

  2. Piece类
    (1) Piece接口
    对于两种棋子来说, 有很多操作都是两者兼具的, 例如Getter和Setter, 以及设置棋子状态和移除棋子, Piece.java接口为两种棋子提供了共有的操作, 而两种棋子不同的方法会在两种棋子的类里介绍
    (2) GoPiece类
    作为围棋棋子来说, 必须要有颜色和坐标, 为了方便得到棋子的状态, 我还设置了一个state作为棋子的状态
    在这里插入图片描述
    这里主要讲一讲关于state. state是用于判断棋子状态的, 如果state = 1, 说明此时该棋子已经被移除, 所以其坐标也设置为(-1, -1)[在Position类中曾提到过], 如果state为0, 说明该棋子已经存在但是并没有使用(基本不存在这种情况, 具体原因在Player类中会说明, 但是仍需列出), 如果state为1, 说明该棋子存在并且已经被使用
    在这里插入图片描述
    所以checkRep()也很简单, 围棋中所有方法都是Graph接口中的方法, 唯一需要介绍的是removePiece()函数, 它将坐标设为(-1, -1), state设为-1, 就可以代表该棋子已被移除

在这里插入图片描述
(3)ChessPiece类
作为国际象棋棋子来说, 除了和围棋一样必须有颜色和坐标之外, 还需要一个棋子的名称, 我将其设为label, 所以参数有4个
在这里插入图片描述
除了label和state之外和GoPiece类基本完全相同, 所以不再介绍重复内容
先说label, label是国际象棋不同于围棋的东西, 围棋只有黑白, 但是国际象棋还有棋子名称, 分别是King, Queen, Bishop, Knight, Rook和Pawn六种棋子类型以及棋子的左右, 我觉得如果还要再设置棋子的左右之分太过于繁琐, 所以label不仅要包含棋子名称, 还需要包含棋子的左右, 这部分会在Game类里介绍, 这里就不展开讨论了
其次是state. 由于国际象棋一开始棋盘上就应该摆好棋子, 所以不存在围棋中的棋子存在但还未摆放的情况, 所以state只能为1或者-1, 至于其代表的含义与围棋中的state代表的1和-1相同, 重复内容不再介绍
在这里插入图片描述
在该类中只有一个特别的方法, 就是label的get方法, 很简单, 不多作介绍

  1. Action类
    由于围棋的操作和国际象棋的操作完全不同, 没有任何相同的地方, 同时两种棋子的类型也不相同, 所以接口没有意义, 直接分开为两类即可. 至于需要这个Action类的原因是为了防止Player直接得到棋子的信息并对棋子进行修改, 所以创建该类使得Player仅能使用Action对棋子进行Action类中给出的方法进行操作, 而不能直接修改棋子信息
    (1)GoAction类
    围棋有两种操作, 一种是将自己的棋子摆到棋盘上空的位置, 一种是将对方的棋子从棋盘上移除.
    对于GoAction来说, 没有任何的表示不变量, 也没有任何参数, 仅仅是为了给Player提供操作方法
    在这里插入图片描述
    ① 落子
    落子操作就是将一个已经存在的棋子放置到棋盘上, 但对于GoAction类来说, 只能调用Piece类, 所以设计思想就是将一个已经存在的piece设置为已经放置的状态并将其坐标改为落子的坐标
    ② 提子
    落子操作就是将一个已经放置在的棋盘上的棋子移出. 但对于GoAction类来说, 只能调用Piece类, 所以设计思想就是将一个已经放置在的棋盘上piece设置为被移除的状态并将其坐标改为(-1, -1)

(2)ChessAction类
国际象棋有两种操作, 一种是将自己仍在棋盘上的棋子移动到一个空的位置, 一种是移动自己的棋子吃掉对方的棋子.
对于ChessAction来说, 没有任何的表示不变量, 也没有任何参数, 仅仅是为了给Player提供操作方法
在这里插入图片描述
①移子
移子是将自己仍在棋盘上的棋子移动到一个空的位置. 但对于ChessAction类来说, 只能调用Piece类, 所以此处的移动也只是将一个棋子的位置改变为目标位置
②吃子
吃子是移动自己的棋子吃掉对方的棋子. 但对于ChessAction类来说, 只能调用Piece类, 所以此处的吃子是目标位置的对方棋子remove, 再将自己的棋子移动到目标位置

  1. Board类
    先讲Board类是因为相比Player类来说, Board类要简单一些, Board作为游戏中相对独立的存在, 基本上只与Piece类有关
    (1) Borad接口
    Board接口的设计也是为了为两种棋盘提供相同的操作和方法, 主要就是Getter和Setter以及检查位置合法性, 从指定位置删除棋子, 以及打印棋盘
    (2) GoBoard类
    作为围棋棋盘, 需要有棋盘大小, 我设计的是一个21×21的棋盘, 并且建立以围棋棋盘最左下角为原点(0, 0)的坐标系, 棋盘上的棋子存储我使用了一个数据类型为Piece的二维数组
    在这里插入图片描述
    在这里插入图片描述
    由于Piece类型和Chess不同, 所以从棋盘上获得棋子信息不能写入接口中, 还有就是围棋独有的落子操作(虽然国际象棋棋盘初始化时也要放棋, 但为了区别二者, 所以还是分开写). 这两个方法是专属于GoBoard类中的

(3) ChessBoard类
国际象棋棋盘是一个8×8的棋盘, 由于棋子是放在格子里而非交线上, 所以最左下角的棋子坐标应为(1, 1), 最右上角的棋子坐标应为(8, 8), 棋盘上的棋子存储与GoBoard类相同

在这里插入图片描述
在这里插入图片描述
除开棋盘上获得棋子信息和特有的移动棋子操作之外, 我还新加了一个为了初始化而向棋盘上添加棋子的操作, 为了让Player类中的Piece对象和Board中的Piece对象相同, 所以初始化棋盘我放在了Game类中, 所以此处需要这个函数为Game初始化棋盘提供方法
在这里插入图片描述

  1. Player类
    Player是游戏的核心, 一个玩家能够改变棋子状态, 记录走棋历史, 记录棋子数目等操作.
    (1) Player接口
    由于两种Player其实相差很多, 除了Getter之外么有太多共同的方法, 但我决定还是写一下接口, 能让程序简化一些总是好的, 接口中主要有得到玩家名称, 得到玩家历史, 改变历史和得到玩家剩余棋子数量等几个基本的Getter和Setter
    (2) GoPlayer类
    GoPlayer的构建参数应为其名称name, 还应该包含其剩余(仍在棋盘上)的棋子集合, 一个专属该玩家的走棋历史history, 以及只有Player才能调动的Action类
    在这里插入图片描述
    每当玩家行动的时候都会需要记录历史, 而玩家的name不能为null
    在这里插入图片描述
    专属于GoPlayer类的操作有加入棋子, 放置棋子, 以及把棋子移除. 这里我着重说下移出
    移除棋子这一操作首先由Action类里的remove来实现, 但是Action类并不能访问到属于Player的棋子集合, 所以移除操作还需要从剩余棋子的集合中删除该棋子, 即保证棋子是非法状态后再删除, 添加了一份保险
    (3) ChessPlayer类
    ChessPlayer的fields, AF, RI基本与GoPlayer相同, 不在介绍重复部分
    在这里插入图片描述

在这里插入图片描述
设计思想与GoPlayer也很相似, 略过

  1. Game类
    Game类是实现游戏的主要核心部分, 在客户端主程序中只允许调用Game里的方法, 不允许调用其他类的任何方法, 也不允许import任何一个其他类, 这样也能很好的保护数据, 防止其泄露的可能性.
    (1) Game接口
    虽然是两个不同的游戏, 但是同为棋盘游戏, 还是有几个能够共用的方法, 我设计的接口里有得到Player名字, 检查棋盘某个位置是否为空, 打印棋盘, 得到Player走棋历史和得到Player的棋子数量
    (2) GoGame类
    围棋游戏需要两名玩家, 以及玩家的操作, 为了防止数据泄露, 我决定独立设置两个String类型的name, 而不是直接再constructor里建立player. 并将棋子颜色设为immutable和static类型
    在这里插入图片描述
    围棋游戏中, 黑方玩家先落子, 白方玩家后落子, 所以将player1设定为黑子, 将player2设定为白子.
    在这里插入图片描述
    GoGame必须实现最终客户端的所有要求, 着重介绍落子和提子两个方法
    ① 落子
    我首先将两个玩家编号, player1的编号为1, player2的编号为2, 所以当player1要执行put操作时, 输入编号1; 当player2要执行put操作时, 输入编号2. 具体实现就是调用之前的Player类中的put方法和Board类型中的put方法即可.
    在这里插入图片描述
    ② 提子
    提子判断操作玩家的方法与落子相同, 但是因为是移除对方的棋子, 所以需要判断移除的棋子是否在对方的棋子集合里, 并且记录历史应当记录在操作玩家的历史中, 而不是被移除棋子的玩家历史中.
    其余的比较简单, 不多作介绍

(3) ChessGame类
国际象棋需要两名玩家, 以及玩家的操作, fields设计与GoGame基本相同
在这里插入图片描述
国际象棋中白方先行动, 白方后行动, 所以将player1设定为白子, 将player2设定为黑子, 由于国际象棋需要初始化棋盘和玩家剩余棋子, 所以在这里需要创建Piece类, 但是没有直接使用Piece类中的方法, 符合上面的规划图
在这里插入图片描述
ChessGame必须实现最终客户端的所有要求, 着重介绍初始化游戏, 移动棋子和吃子两个方法
① 初始化游戏
前文提到过, 需要初始化游戏, 最直观的就是要初始化棋盘和玩家剩余棋子, 所以我在初始化中新建了很多Piece对象, 但没有调用其中的方法
② 移动棋子
移动棋子有两个方面, 一个是更新玩家剩余棋子中所移动的那个棋子的坐标, 还有一个直观的就是更改棋子在棋盘上的位置. 所以对于棋盘来说, 需要删除原来位置上的那个棋子, 并在移动的目标位置上添加那个已经更新过的棋子
③ 吃子
吃子和移动棋子远离基本上相似, 不同的就是移动必须移动到空的位置上, 而吃子是移动到有对方棋子的位置上, 所以只需要在移动前将对方的棋子从player中删除并在棋盘上删除即可
3.3.2 主程序MyChessAndGoGame设计/实现方案
辅之以执行过程的截图,介绍主程序的设计和实现方案,特别是如何将用户在命令行输入的指令映射到各ADT的具体方法的执行。

在主程序中, 我还设计了一个用来清屏的函数, 防止指令什么的看不清楚, 原理很简单
在这里插入图片描述
首先我会让用户选择玩围棋游戏或者国际象棋游戏
在这里插入图片描述
(1) 围棋游戏
选择1之后, 我会要求用户输入两个玩家的名字, 并提示用户各自的棋子颜色和行动顺序
在这里插入图片描述
之后便会给用户一个menu, 用户可以选择数字进行操作
在这里插入图片描述
在玩家选择1, 也就是put piece的时候, 会提示用户输入信息
在这里插入图片描述
用户输入想要放置的棋子位置, 就会执行Game中的put操作并打印棋盘
在这里插入图片描述
由于用户执行的是前三项: put, remove或者skip, 所以会轮到第二名玩家进行选择, 如果玩家选择了4或者5就会返回信息后再次要求玩家选择
在这里插入图片描述
第二名玩家执行put, 又回到第一名玩家

在这里插入图片描述此时第一名玩家选择5, 查询自己的棋子数量, 同样会返回查询信息后要求用户再次选择
在这里插入图片描述
第一名玩家选择2, 提走第二名玩家的棋子
在这里插入图片描述
此时第二名玩家选择3, 跳过ta的回合

在这里插入图片描述此时第一名玩家选择6, 结束游戏, 玩家可以查看自己的行动历史
在这里插入图片描述
选择1查看第一名玩家的行动历史
在这里插入图片描述
选择2查看第二名玩家的行动历史
在这里插入图片描述
选择3结束观看历史, 关闭游戏, 此时进程结束, 右上方红框变灰
在这里插入图片描述

(2)国际象棋游戏
选择2之后, 用户也和上面围棋游戏一样要输入玩家名字, 并提示玩家的棋子颜色和行动顺序

在这里插入图片描述同样的, 会给用户一个menu, 用户可以选择数字进行操作

在这里插入图片描述
第一个玩家选择1后, 会提示用户想要移动哪个坐标上的棋子
在这里插入图片描述
输入后会提示用户想要移动到哪个位置

在这里插入图片描述输入想要移动的位置之后, 会打印棋盘, 可以看到原本在(4, 1)上的白色方的King移动到了(5, 6)这个位置
在这里插入图片描述
同样的, 由于用户执行的是前三项: put, remove或者skip, 所以会轮到第二名玩家进行选择, 如果玩家选择了4或者5就会返回信息后再次要求玩家选择
在这里插入图片描述
在这里插入图片描述
此时输入2, 会提示用户想用哪个位置的棋子来进行吃子操作
在这里插入图片描述
输入需要移动的棋子坐标之后, 会提示用户想要吃掉哪个位置的对方棋子
在这里插入图片描述
输入对方棋子位置之后会打印棋盘, 可以看到原来(5, 7)位置上的black Pawn4移动到了原来的white King的位置, 而white King从棋盘上消失了
在这里插入图片描述
此时输入5查询第一个玩家的棋子数, 可以看到从16个旗子变成15个了
在这里插入图片描述
为了测试3的功能, 这次第一名玩家选3, 跳过该轮, 可以看到本轮跳过, 轮到第二名玩家
在这里插入图片描述
第二名玩家选择6, 游戏结束, 现在可以查看双方的行动历史
在这里插入图片描述
选择1查看第一名玩家的历史

在这里插入图片描述选择2查看第二名玩家的历史
在这里插入图片描述
选择3结束观看历史, 关闭游戏, 此时进程结束, 右上方红框变灰
在这里插入图片描述
3.3.3 ADT和主程序的测试方案
介绍针对各ADT的各方法的测试方案和testing strategy。
介绍你如何对该应用进行测试用例的设计,以及具体的测试过程。
我的测试策略很简单, 就是将上一级需要调用的方法以及较为复杂的方法进行测试, 防止测试过于臃肿, 所以部分测试覆盖率较低, 但我认为其实并没有什么影响, 毕竟一些很简单的Getter之类的写测试除了浪费时间之外没有任何作用

附上每一个测试的通过图以及覆盖图

4 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。

日期 时间段 计划任务 实际完成情况
3.18 14:00-17:30 编写ConcreteEdgeGraph.java 完成
3.19 10:00-12:30 编写ConcreteVerticesGraph.java 遇到困难
未能完成
3.19 19:00-22:00 编写ConcreteVerticesGraph.java 完成大部分代码
但仍有bug
3.21 10:00-11:30 编写ConcreteVerticesGraph.java 仍有bug
3.21 19:00-21:00 完成ConcreteVerticesGraph.java 完成
3.22 14:00-16:30 完成Graph的test 完成
3.25 14:00-17:00 编写GraphPoet.java 有少部分未能完成
3.27 14:00-17:00 完成GraphPoet.java及其test 遇到困难
未能完成
3.28 14:00-16:00 完成GraphPoet.java及其test 完成
4.1 14:00-17:00 完成Friendship.java及Person.java getDistance有bug
4.1 19:00-20:30 修复bug并完成test 完成
4.2 10:00-11:30 完成部分P3设计规划 完成
4.2 14:00-16:30 完成P3设计规划 未能完成
4.2 19:40-21:50 完成P3设计规划 完成
4.3 18:40-22:00 完成规划中的Position类
Piece类和Action类 完成
4.4 10:00-11:30 完成规划中的Board类,Player类 遇到困难
未能完成
4.4 14:00-16:30 完成规划中的Board类,Player类 完成Board类
Player类遇到困难
4.8 14:00-17:00 对之前写的类进行测试验错 完成
4.8 19:00-20:30 完成规划中的Player类 完成
4.9 10:00-11:30 完成规划中的Game类
中的GoGame类 完成, 但有错误
4.9 19:00-21:30 重新思考Game类, 对Game类进行重新规划 完成
4.10 16:00-17:30 完成规划中的Game类 完成
4.10 19:00-22:30 完成主程序, 完成整个P3 完成
4.11 10:00-17:40 编写实验报告 完成
5 实验过程中遇到的困难与解决途径
遇到的难点 解决途径
不知道怎么基于点来存储图中的信息 通过同学的指导以及在QQ和Piazza上询问老师得到解答
对GraphPoet.java的作用理解错误 在和同学闲聊时聊到这个, 更正了错误
完成P3的设计规划有困难 在网上参考了往届学长的想法, 再结合自己的想法完成了规划
P3一层一层往上写的时候出现了难以解决的bug 对之前写的类进行test验错,成功找出bug并修正
P3的Game类设计思想出了问题 不断地思考和研究自己的规划图
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训
6.1.1 `针对以下方面的感受
(1) 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
(2) 使用泛型和不使用泛型的编程,对你来说有何差异?
(3) 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
(4) P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
(5) P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?
(6) 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
(7) 关于本实验的工作量、难度、deadline。
(8) 《软件构造》课程进展到目前,你对该课程有何体会和建议?
(1) 我认为面向ADT编程是一项很好的技术, 不再面向过程, 而是对一个对象进行编写, 这有很好的封装性, 而且还有继承之类的. 这种面向对象编程能够报纸外部接口不变的情况下改变内部实现, 从而减少甚至避免了外界的干扰. 以对象为核心进行编程, 能够更好更清晰地实现目标
(2) 使用泛型能够改变输入数据的类别, 从而实现不同种类的数据能复用同一个方法, 使用泛型编程能够提高代码效率
(3) 给出规约后开始写测试, 能够及时得到来自代码的反馈, 很容易就能知道自己所写代码的正确性, 我认为这是一个很好的方案
(4) 代码的复用能够减少工程量, 提升代码效率和工作效率, 在大部分场景下, 能够复用代码都能极大地减少工作量, 是一个十分完美的方案
(5) 从0开始写让我在开发的时候基本没有遇到难以解决, 必须寻求他人帮助才能解决的, P1中有很多东西是我无法理解的, 只能通过寻求他人帮助才能理解, 而P3完全由我设计, 自由度很高, 在写之前做了一个规划图, 写起来也比较轻松有趣
(6) 为了防止数据泄露以及出现难以调试的bug, 这是十分有意义的. 在今后的编程中我也会贯彻这个思想
(7) 本次实验难度相较于上次提升了不少, 但是总的来说比上次实验有趣了很多, 工作量虽然很大, 但是乐在其中, ddl给的比较宽松, 我中间有时候遇到困难会一两天不来弄, 但还是能在截至前一天全部弄完, 感觉还是很开心
(8) 对于这门课, 我认为再一次让我接触到了编程, 感受到了编程的魅力, 它给的反馈感及时而强烈, 我还记得完成P3时候的开心和满足, 但是我觉得习题课应该提早一点讲, 毕竟在实验都快要完成的时候讲习题课, 我认为是事倍功半的效果

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
计算机网络实验一是一项非常基础的实践操作,主要涉及到计算机网络的基本原理和实验技术。在这个实验中,我们将会通过模拟网络环境,使用Wireshark等网络监测工具进行数据包的捕获和分析,进而深入了解网络通信过程。 在实验中,我们需要完成以下几个主要的实验任务: 1. 配置网络拓扑:根据实验要求,搭建一个简单的局域网或广域网拓扑结构,可以使用软件模拟或者物理设备。 2. 模拟数据通信:通过配置网络设备,设置IP地址、子网掩码、网关等参数,实现两台计算机之间的数据通信。可以使用ping命令测试两台主机是否能够相互通信。 3. 数据包分析:使用Wireshark等网络抓包工具,在通信过程中捕获数据包,并进行分析。可以观察数据包的结构、协议类型、源IP地址、目的IP地址等信息。 4. 问题排查与调试:在实验中可能会遇到一些网络故障或问题,需要学会使用网络诊断工具进行故障排查和调试。比如,分析网络延迟、丢包情况,找出故障原因。 实验一是计算机网络实验课程中的第一个实践环节,通过这个实验,我们能够更加直观地了解计算机网络的工作原理和实际操作,为之后的实验打下基础。通过实践操作,我们可以更好地理解和掌握网络配置和故障排查的基本方法,为以后的网络实验和工作做好准备。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值