欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

hit软件构造lab2

程序员文章站 2022-03-10 14:53:02
...

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

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 实验环境配置
以前下载过jdk,并且下载了eclipse作为IDE。
当时主要难点是配置路径,java_home,path等。
Git很好下载,基本的命令和配置我参考了廖雪峰的git教程,很详细,几乎没有问题。
在这里给出你的GitHub Lab1仓库的URL地址(Lab1-学号)。
https://github.com/ComputerScienceHIT/Lab2-1180300108
3 实验过程
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1 Poetic Walks
在这里简要概述你对该任务的理解。
我们需要构造一个Graph,实现spec中的基本功能,并且以边和点两种方式实现接口,并且需要将ADT泛型化。编写的测试文件要有良好的测试覆盖率。
P4要根据给定的文件提取桥接词进行诗歌工程。

3.1.1 Get the code and prepare Git repository
如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。
直接从github上download and clone,在eclipse里建立Lab2project,按照实验指导书目录要求创建子文件夹和package,在lab2文件夹中右键选择打开git bash,-git init建立本地版本库。
3.1.2 Problem 1: Test Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
我选用ConcreteEdgesGraph作为Graph的具体实现。
此时运行GraphStaticTest即可得到测试结果:

GraphInstanceTest.java:
针对这个测试,我们可使用抽象类,针对边和顶点两种实现的测试类,并且我们所有的测试都与具体的实现无关。
在测试中,使用emptyInstance()方法来获取新的空图,所有的测试策略都根据需要实现的功能来设计。
具体的strategy已在test测试文件中标注,如下:
// 使用抽象类
// 针对两个测试类
// 此test与具体实现无关
// Testing strategy
//
// 测试Graph.add()方法:
// 如果已经存在该顶点,修改graph,顶点数加1
// 否则图不变
// 通过顶点个数的变化来测试
//
// 测试Graph.remove()方法:
// 如果不存在该点,图不变
// 否则删除该点
// 相邻的边也要删除
// 通过顶点个数变化来测试
//
// 测试Graph.set()方法:
// 分开测试weight的情况 : 0 , > 0
// weight为0检查边是否被删除
// weight > 0 , 检查边是否被添加或更新
// 观察source , target 的映射关系变化
//
// 测试Graph.vertices()方法:
// graph为空
// graph添加顶点后
// 观察顶点的数量
//
// 测试Graph.sources()方法
// graph为空
// set点和边
// 观察source数量 , 对应关系是否正确
//
// 测试Graph.targets()方法
// graph为空
// set点和边
// 观察targets数量 , 对应关系是否正确
3.1.3 Problem 2: Implement Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
3.1.3.1 Implement ConcreteEdgesGraph
一. ConcreteEdgesGraph部分:
(先介绍ConcreteEdgesGraph,后Edge类,最后测试类)
// Abstraction function:
// 通过对graph中边和点的抽象
// 边的类中包含了source,target,weight
// 带方向的边
// 构成有向图
//
// Representation invariant:
// 不变量在于隐含的数学关系
// n个点,最多构成n*(n-1)条有向边
//
// Safety from rep exposure:
// 在可操作的情况下,所有的变量都定义为private , final
// 顶点和边是mutable类型, 因此多处使用:
// 防御式拷贝
// 使用Collections.unmodifiableSet等方法

public ConcreteEdgesGraph()
构造函数
private void checkRep()
检查不变形函数,思路:n个点,最多构成n*(n-1)条有向边,因此存在这种不可变的数学关系
public boolean add(String vertex)思路:如果在vertices的Set集合中成功添加了顶点string,则返回true

private int findEdge(String source, String target)思路:My method findEdge() ,为了找到list是否存在一条指定的边,该边的source和target必须与传入的参数相等,返回该边的index

public int set(String source, String target, int weight)思路:根据parameter找到指定边,并调用findEdge()返回index。
当weight>0,如果index<0,没找到指定边,则添加顶点和边;如果index>0,找到了指定边,则update这条边。
当weight>0且index<0时,找到了指定边,将其删除
最后checkRep()。

public boolean remove(String vertex)思路:如果不含该点,返回false。否则遍历edges,如果某个edge的source或是target与vertex相等,则删除该边。最后删除vertex点。并checkRep。

public Set< String > vertices()思路:返回vertices的Set,注意做到safety from rep exposure ,使用Collections.unmodifiableSet()方法。

public Map< String, Integer> sources(L target)思路:建立一个map,遍历edges,如果某个edge的edge.getTarget()和传入参数target相等,则将该边的source和weight存入map中。

public Map< String, Integer> targets(L source)
思路:建立一个map,遍历edges,如果某个edge的edge.getSource()和传入参数source相等,则将该边的target和weight存入map中。

public String toString()
思路:graph空,则返回“This is An Empty Graph”.
graph不为空,则将每个边的toString连接起来,调用String.concat()方法。
注意连接后为了可读性,有“\n”.

实现 clss Edge :

// Abstraction function:
// 设置了边有向边必备的参数
// source , target , 和权值weight
//
// Representation invariant:
// source和target不能是null
// weight >= 0
//
// Safety from rep exposure:
// 尽量使用private和final来定义内部属性
// 使用immutable数据类型

  1. 构造函数constructor
    public Edge(L source,L target,int weight) {//构造函数Edge()
    this.source = source;
    this.target = target;
    this.weight = weight;
    checkRep();
    }3. checkRep():
    source和target不能是null,并且 weight >= 0

  2. 三个field的get函数

  3. toString()方法:
    输出形式:V1->V2:weight

  4. equals()方法:
    思路:判断两条边是否相等 ,先判断一个对象是否是另一个对象的实例 ,如果是再判断source ,target ,weight等是否都相等.

  5. hashCode()方法
    复写hashCode方法,@return hash address

测试类主要思路:
// Testing strategy for ConcreteEdgesGraph.toString()
// 当Graph为空的时候
// 当Graph不为空的时候
// 匹配string,判断是否相等
// Testing strategy for Edge
// 根据实现的功能来制定相应的test
//
// 测试getSource()方法:
// 建立特定edge
// 返回source
// 判断是否匹配
//
// 测试getTarget()方法:
// 建立特定edge
// 返回target
// 判断是否匹配
//
// 测试getWeight()方法:
// 建立特定edge
// 返回weight
// 判断是否匹配
//
// 测试Edge里的toString()方法:
// 设置指定的边
// 判断字符串是否与指定字符串相等
//
// 测试equals()方法:
// 设置多条边
// 相等:和自己,和其他边
// 不等:source,target,weight存在不相等
//
// 测试hashCode()方法:
// 相等:this和that相等
// 不相等:this和that不相等

3.1.3.2 Implement ConcreteVerticesGraph
(先介绍ConcreteVerticesGraph类,再Vertex类,最后测试类)
// Abstraction function:
// 将有向加权图描述为多个顶点
// 点之间的映射关系为边
// 边有权值weight
//
// Representation invariant:
// 每个顶点只能存在一个实例
// 因此顶点个数vertices()的大小相等
//
// Safety from rep exposure:
// 变量尽可能定义为private和final
// 防御式编程

构造函数:
public ConcreteVerticesGraph()

检查不变性
public void checkRep()
思路:每个顶点只能存在一个实例,因此顶点个数vertices()的大小相等

public boolean add(String vertex)
思路:若vertices()中已包含vertex,返回false,否则新建一个顶点将其加入vertices即可。

private int findVertex(String str)
思路:My method findVertex , 找到对应str的vertex所在的位置,返回index。

public int set(String source, String target, int weight)
思路:如果存在vertices()中找到source,使用findVertex返回对应index,从而找到源点,否则以source为string创建一个新的源点,并将它添加进vertices。对于target操作同理。
得到源点和目标点后,分别对目标点调用setSource,源点调用setTarget即可。
最后checkRep并返回previous weight。

public boolean remove(String vertex)
思路:如果vertices()不包含vertex,返回false。否则遍历所有点,如果某点和vertex存在映射关系,则将这种关系删除。最后将vertex对应的点从vertices中删除即可。

public Set vertices()
思路:遍历vertices,找到每个点对应的string,添加进set即可。

public Map<L, Integer> sources(L target)
思路:如果找不到target对应的点,返回Collections.emptyMap()。否则调用getOneSource返回target对应的源点图。

public Map<L, Integer> targets(L source)
思路:如果找不到source对应的点,返回Collections.emptyMap()。否则调用getOneTarget返回source对应的目标点图。

public String toString()
思路:graph空,则返回“This is An Empty Graph”.
graph不为空,则将每个点的toString连接起来,调用String.concat()方法。
注意连接后为了可读性,有“\n”.

实现class Vertex:
// Abstraction function:
// 顶点类刻画图的关键要素
// 使用HashMap存取映射关系
// key为该点的source或target , value为该点的weight
//
// Representation invariant:
// 每个顶点的source或target不能是自身
// HashMap中的values必须不小于0
//
// Safety from rep exposure:
// 所有fields是private final
// String是imutable类型
// 防御性编程

1.fields
private L str;
private Map<L, Integer> oneSources = new HashMap<L, Integer>();
private Map<L, Integer> oneTargets = new HashMap<L, Integer>();

  1. /构造函数/
    public Vertex(L str) {
    this.str = str;
    }
    3.checkRep()
    思路:检查不变量,保证该点不会是自己的source或者target,weight不小于0即可。

4.fields的get函数
思路:使用this返回相应的field即可。

  1. public int setSource(L source, int weight)思路:设置到该点的source,处理同ConcreteVerticesGraph中set方法

    • weight = 0 , source存在 :移除
    • weight > 0 , source存在 :更新
    • weight > 0 , source不存在 :添加
  2. public int setTarget(String target, int weight)
    思路:设置到该点的target,处理同ConcreteVerticesGraph中set方法

    • weight = 0 , target存在 :移除
    • weight > 0 , target存在 :更新
    • weight > 0 , target不存在 :添加

测试类主要思路:
// Testing strategy for ConcreteVerticesGraph.toString()
// 测试toString()方法:
// 当Graph为空的时候
// 当Graph不为空的时候
// 匹配string,判断是否相等
// Testing strategy for Vertex
// 测试getString()方法:
// 设置特定点和其字符串
// 判断该点获取到的字符串和以上字符串是否相等
//
// 测试getOneSources()方法:
// 设置特定点
// 点的source为空
// 点的source不为空:sources个数,sources字符
// 判断是否相等
//
// 测试getOneTargets()方法:
// 设置特定点
// 点的target为空
// 点的target不为空:targets个数,targets字符
// 判断是否相等
//
// 测试setSource()方法:
// 分开测试weight的情况 : 0 , > 0
// weight为0检查source是否被删除
// weight > 0 , 检查边是否被添加或更新
// 若更新则边的权值会变化
// 包括previous weight的测试
//
// 测试setTarget()方法:
// 分开测试weight的情况 : 0 , > 0
// weight为0检查target是否被删除
// weight > 0 , 检查边是否被添加或更新
// 若更新则边的权值会变化
// 包括previous weight的测试
//
// 测试Vertex中toStiring()方法
// 设置source,target,weight
// 判断输出字符串与指定字符串是否相等

3.1.4 Problem 3: Implement generic Graph
3.1.4.1 Make the implementations generic
将具体类的声明更改为:
public class ConcreteEdgesGraph implements Graph { … }

class Edge { … }
和:
public class ConcreteVerticesGraph implements Graph { … }

class Vertex { … }
更新两个实现以支持任何类型的顶点标签,使用占位符L代替String。
以前,可能已声明类型为Edge或的变量List。那些将需要成为Edge和List<Edge>。
同样,可能已经调用了类似new ConcreteEdgesGraph()或的构造函数new Edge()。例如,那些将需要成为new ConcreteEdgesGraph()和new Edge()。

3.1.4.2 Implement Graph.empty()
选择ConcreteEdgesGraph来使用和实施Graph.empty()
为了确信Graph确实支持不同类型的标签,我在GraphStaticTest增加了标签类型Integer。其中调用的Graph中所提供的各种函数,全部测试通过。

3.1.5 Problem 4: Poetic walks
3.1.5.1 Test GraphPoet
// Testing strategy
// 给定一个input。从文件中读取poet,调用Graph.poem()后
// 观察输出与预期是否相等

对mugar-omni-theater.txt和自定义的test/P1/poet/myTest.txt测试分别如下:

3.1.5.2 Implement GraphPoet
fields:private final Graph graph = Graph.empty(); //有向图
private final List words = new ArrayList(); //文本的单词

// Abstraction function:
// 将输入的文本单词提取作为顶点
// 构建有向图
// 转化为poem
//
// Representation invariant:
// 输入的文本words不为空
// 有向图不为null
//
// Safety from rep exposure:
// 所有fields都是private final
// 防御式编程

方法:

  1. public GraphPoet(File corpus) throws IOException
    从语料库的图形中创建一个新的poet。
    先读文件,并把文件中的单词存在words中。使用BufferedReader读取文本文件中的数据,类Scanner用于将输入的文本分解成多个部分。

然后调用Graph类中的方法,将单词转化为图,添加顶点,set边,其中权值全部设置为1.

2 public void checkRep()
检查图和words不为空。

3.public String poem(String input)
String[] newWords = input.split("\s"); //空格回车换行等空白符
StringBuilder poem = new StringBuilder(input); //为了方便后面高效插入字符串
遍历input中所有单词,调用Graph.targets()和Graph.sources()方法。如果该单词的targets和后面一个单词的sources有交集,则添加一条bridge,并且在两个单词的bridge中随机选择一个插入到字符串中。

4 public String toString()
调用Graph.tostring()方法。
3.1.6 Before you’re done
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
在这里给出你的项目的目录结构树状示意图。
项目名称: Lab2_1180300108
src
P1
graph
….java
poet
… .java
… .txt
test
P1
graph
…Test.java
poet
… Test.java
… .txt

3.2 Re-implement the Social Network in Lab1
这个实验是基于在Poetic Walks中定义的Graph及其两种实现,重新实现Lab1中的 FriendshipGraph类。我们需要尽可能复用ConcreteEdgesGraph或 ConcreteVerticesGraph中已经实现的add()和set()方法,而不是从零开始。另外基于所选定的 ConcreteEdgesGraph 或 ConcreteVerticesGraph的rep来实现,而不能修改父类的rep。
3.2.1 FriendshipGraph类
设计和实现思路:继承ConcreteEdgesGraph,根据实验指导的提示,类中需要增加一些非法情况的判断,例如人已经存在,或者边已经存在等。
过程:
public void addVertex(Person p)
这个函数是为把参数添加到图中,作为图的一个顶点,直接调用父类的this.add()即可。

  1. public void addEdge(Person p1, Person p2)
    构建图的要素,添加了点后就该添加边了。先调用 this.targets()方法来判断边是否已经存在,然后调用this.set()方法设置边即可。

3.public int getDistance(Person p1, Person p2)
最主要的就是找两个人距离的函数,因为根据题设要求最短距离,因此采用广度遍历的方式,此处需要用到Queue的数据结构,并且设置了一个List来存放已经访问过的person。
广度遍历的主要思路是:将P1进队,设置访问过,然后出队,依次调用this.targets
()方法访问其相邻点,并且把它们进队,然后P1访问完再取队列第一个元素访问,直到某一相邻的点为P2则返回最短距离。如果所有点都访问完仍无最短距离,说明P1和P2没有关系,此时返回-1 。

测试结果:

正常输出的测试结果

注释掉rachel->ross后的输出结果
3.2.2 Person类
Person类较为简单,主要是根据FriendshipGraph类的需求编写的。它用于描述每个成员的性质,主要是实例化姓名的构造方法,getName()方法,判断姓名是否重复的nameSameWith方法。

3.2.3 客户端main()
public static void main(String[] args) throws Exception {
FriendshipGraph graph = new FriendshipGraph();
Person rachel = new Person(“Rachel”);
Person ross = new Person(“Ross”);
Person ben = new Person(“Ben”);
Person kramer = new Person(“Kramer”);

	ArrayList<Person> list = new ArrayList<Person>();
	list.add(rachel);
	list.add(ross);
	list.add(ben);		
	list.add(kramer);
	for(int i=0; i <list.size();i++) {
		for(int j=i+1 ; j < list.size();j++) {
			if(list.get(i).nameSameWith(list.get(j).getName())) {
				System.out.println("Wrong name:"+list.get(i).getName());
				throw new Exception("The name is repeated!");
			}
		}
	}
	
	graph.addVertex(rachel);
	graph.addVertex(ross);
	graph.addVertex(ben);
	graph.addVertex(kramer);
	graph.addEdge(rachel, ross);
	graph.addEdge(ross, rachel);
	graph.addEdge(ross, ben);
	graph.addEdge(ben, ross);
	System.out.println(graph.getDistance(rachel, ross));
	//should print 1
	System.out.println(graph.getDistance(rachel, ben));
	//should print 2
	System.out.println(graph.getDistance(rachel, rachel));
	//should print 0
	System.out.println(graph.getDistance(rachel, kramer));
	//should print -1
}

3.2.4 测试用例
给出你的设计和实现思路/过程/结果。
设计和实现思路:在测试包Test中创建了一个 FriendshipGrapgTest.java文件后,通过注解的方式@Test表示这是一个测试方法。
主要使用的方法:assertEquals、assertTrue和assertFalse方法。都是通过判断预期值和实际值是否相等来返回true或false。
过程:
对于addVertex()方法,主要测试了添加的点是否存在。

对于addEdge()方法,测试了边是否存在和是否有超出unconnected边。

对于getDistance方法,测试了距离实际值是否和预期值相等。

测试person类,测试了getName()和nameSameWith()方法。

结果:

3.2.5 提交至Git仓库
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
3.3 Playing Chess
3.3.1 ADT设计/实现方案
设计了哪些ADT(接口、类),各自的rep和实现,各自的mutability/ immutability说明、AF、RI、safety from rep exposure。
必要时请使用UML class diagram(请自学)描述你设计的各ADT间的关系。

一. Piece类(可变)
fields:
private String pieceName; //棋子名称
private int pieceState; //0未放置,1已放置。-1被remove
private int pieceX; //横坐标
private int pieceY; //纵坐标
// Abstraction function:
// Piece代表棋盘上的棋子 ,pieceName代表棋子的名称,pieceState代表棋子的放置状态,
// pieceX代表棋子的横坐标,pieceY代表棋子的纵坐标
// Representation invariant:
// piece不能映射为空
//
// Safety from rep exposure:
// 所有fields都是 private
// 使用immutable数据类型

/*Constructor/
public Piece(String name,int state, int x ,int y)构造函数

方法:

  1. 所有fields的get函数和set函数。分别是返回Piece的fileds和通过传递参数设置fields。

  2. public void remove()将一个点从棋盘中移除。分别设置pieceState、pieceX、pieceY为 -1 。

二. Position类(不可变)
fields:

// Abstraction function:
// Position代表棋子在棋盘上的位置,x代表位置的横坐标,y代表位置的纵坐标
// Representation invariant:
// position不能映射为空
//
// Safety from rep exposure:
// 所有fields都是 private and final
// 使用immutable数据类型
/*Constructor/
public Position(int positionX, int positionY) 构造函数

方法:

  1. 所有fields的get函数,分别返回该position 的横坐标和纵坐标。

  2. @Override
    判断两个position是否相等。如果position的x、y坐标值都相等,则两个位置相等,返回true,否则返回false。

三. Player类(不可变)
fields:
private String playerName; //棋手名
private Set remaining = new HashSet(); //玩家剩余棋子
private String history = new String(); //操作历史

// Abstraction function:
// Player映射操作pieces的玩家,playerName为玩家的名称,
// remaining代表玩家在棋盘上的棋子,history的字符串为玩家历史
// Representation invariant:
// Player不能映射为空
//
// Safety from rep exposure:
// 所有fields都是 private and final
// 使用immutable数据类型
// 使用Collections.unmodifiableSet不可变类型

/*Constructor/
public Player(String p) 构造函数

方法:

  1. 所有fields的get函数,返回Player的playerName,remaining,history

2.playerName的set函数,根据传入的name设置this.playername

  1. 这个是针对围棋go写的方法,根据传入的pieceName,从player的剩余棋子remaining中返回一个未放置的棋子。如果没有,返回null。

  2. 如果该棋手remaining有这个棋子,则返回false;否则在remaining中添加该piece并且返回true。

传入的参数是每个操作后需要添加的玩家历史,判断其为非空。再调用String.concat函数将传入的历史连接在this.history后面即可。

6
参数是棋子piece,判断remaining中是否由该棋子,有返回true,否则返回false。

计算player在棋盘上的棋子总数,初始化num = 0,遍历remaining,调用piece.getPieceState()方法,如果棋子状态为1,num++,最后返回num。

四. Board类(可变)
fields:
private int boardSize; //棋盘大小
private Piece[][] boardPosition; //棋子在棋盘的位置// Abstraction function:
// Board 代表棋子所处的棋盘,boardSize代表了棋盘的边长,
// boardPosition代表pieces在棋盘上的位置
// Representation invariant:
// Board不能映射为空
//
// Safety from rep exposure:
// 所有fields都是 private and final
// 使用immutable数据类型

方法:
1.所有fields的get、set函数,分别返回和设置棋盘的边长、pieces在棋盘上的位置。

  1. public boolean check(int x ,int y)
    检查输入数据大小的合理性,如果参数x、y的大小超出了棋盘范围,返回false,否则返回true。

  2. public Piece getBoardPiece(int x , int y)throws Exception
    获得指定坐标的棋子。如果参数坐标超出棋盘范围,抛出异常。

  3. public void setBoardPosition(Piece piece, int x , int y)throws Exception将棋子放置在指定的位置,如果参数x、y超出棋盘范围,或者该棋盘的此位置有棋子,则抛出异常。

  4. public void setBoardPositionState(int x , int y ,int newState)throws Exception
    改变指定位置棋子的pieceState。如果参数x、y超出棋盘范围,抛出异常。

五. Action类(不可变)
field:
private final Board chessBoard = new Board();

// Abstraction function:
// Action映射为棋手的动作,chessBoard是一个Board的对象
// Representation invariant:
// Action和Board不能映射为空
//
// Safety from rep exposure:
// 所有fields都是 private and final
// 使用immutable数据类型

方法:

  1. field的get和set函数。分别返回和设置this.chessBoard

将棋子放置在指定的位置。先调用Board.getBoardPosition获得该position处的棋子,设置棋子的坐标,设置pieceState为1,将棋盘的position处棋子设置为piece。添加玩家历史。
如果piece并非属于player、position超出棋盘的范围、position处已有棋子、piece已经在棋盘上,抛出异常。

3.public void movePiece(Player player , Position oldPosition , Position newPosition)throws Exception
public void movePiece(Player player , Position oldPosition , Position newPosition)throws Exception {
final Piece old_piece = chessBoard.getBoardPiece(oldPosition.getX(), oldPosition.getY());
Piece piece = new Piece(old_piece.getPieceName(), old_piece.getPieceState(),
old_piece.getPieceX(), old_piece.getPieceY());

	if(! player.judgeOwnPiece(old_piece) ){
		System.out.println("所移动的为对手棋子!");
		
		throw new Exception("所移动的为对手棋子!");
	}
	if(piece.getPieceState() == 1) {
		if(oldPosition.getX() == newPosition.getX() && oldPosition.getY()
				== newPosition.getY()) {
			System.out.println("移动的起始和目的位置相同!");
			throw new Exception("移动的起始和目的位置相同!");
		}
	chessBoard.setBoardPosition(piece, newPosition.getX(), newPosition.getY());
	chessBoard.setBoardPositionState(oldPosition.getX(), oldPosition.getY(), 0);
	
	piece.setPieceX(newPosition.getX()); //设置棋子位置
	piece.setPieceY(newPosition.getY());
	
	player.addPieces(piece);
	player.addHistory(String.format("%s move piece %s from (%d,%d) to (%d,%d)\n", 
				player.getPlayerName(),piece.getPieceName(),oldPosition.getX(),oldPosition.getY(),
				newPosition.getX(),newPosition.getY()));
	}else {
		System.out.println("该棋子不存在!");
		throw new Exception("该棋子不存在!");
	}
}将棋子移动到指定的位置。先获取oldPosition处的棋子,判断棋子归属正确,存在性等,new一个Piece对象piece,复制原棋子参数,将原位置棋子的pieceState设置为0,将newPosition处棋子设置为piece。添加玩家remaining,添加玩家操作历史。
  1. 先判断移除的是对方的且在棋盘上的棋子。将棋盘上对应的棋子pieceState设置为-1,调用Piece.remove,增加玩家历史。
    如果position超出棋盘的范围、position处无棋子可提、所提piece不是对方棋子,则抛出异常。

  2. public void eatPiece(Player player , Position position1 , Position position2) throws Exception通过position1和position2获得要吃子piece1和被吃子piece2。判断棋子归属正确,存在性,将棋盘上position1处棋子pieceState设置为0,position2处pieceState设置为-1,并且在position2处新添加一个piece1的对象。移除piece2,添加newPiece到玩家的remaining中,添加玩家历史。

如果position1、position2超出棋盘的范围、position1上无棋子、position2上无棋子、两个位置相同、position1上的棋子不是player的棋子、第二个位置上的棋子不是对方棋子,则抛出异常。
六. Game类(不可变)
fields:
private String gameName; //游戏名
private Board gameBoard = new Board(); //棋盘
private Action gameAction = new Action(); //动作
private Player player1; //棋手1
private Player player2; //棋手2
// Abstraction function:
// Game代表了一局游戏,gameBoard映射为game中的棋盘,棋盘的操作这是player1和player2.
// gameAction为棋手的操作
// Representation invariant:
// Game不能映射为空,gameBoard不能映射为空,Action不能映射为空
//
// Safety from rep exposure:
// 所有fields都是 private and final
// 使用immutable数据类型

方法:
1.所有fields的get函数

2.player1、player2、gameName的set函数

3 public void putPiece(Player player, Piece piece, Position position) throws Exception
public void movePiece(Player player, Position oldPosition, Position newPosition) throws Exception
); public void removePiece(Player player, Position position) throws Exception
} public void eatPiece(Player player, Position position1, Position position2) throws Exception
以上调用Action类中的方法即可。

4 public Piece getOccupationOfPosition(Position position) throws Exception
在指定的position处获得一个棋子。调用Board.getBoardPiece()即可。

  1. public void initByChess(String name1, String name2)设置国际象棋chess棋盘。设置棋盘的gameName,通过参数设置players名称,初始化棋盘,设置32个pieces的pieceName、pieceState、坐标为(-1,-1)。将棋子添加到棋盘上,增加玩家棋子。

  2. public void initByGo(String name1, String name2)
    设置围棋go棋盘。设置棋盘的gameName,通过参数设置players名称,初始化棋盘,增加玩家棋子,设置 pieces的pieceName、pieceState、坐标为(-1,-1)。最后将棋子添加到棋盘上。

打印棋盘。遍历gameBoard即可,需要注意的是二元数组的行和可视坐标系中的行恰好相反,因此每行的棋子倒序打印。

3.3.2 主程序MyChessAndGoGame设计/实现方案
辅之以执行过程的截图,介绍主程序的设计和实现方案,特别是如何将用户在命令行输入的指令映射到各ADT的具体方法的执行。

fields:
public Game game = new Game(); //新建游戏
public String player1Name; //玩家1名称
public String player2Name; //玩家2名称
public ArrayList players = new ArrayList(); //玩家链表

备注:设计玩家链表是为了后面while循环能通过索引值的变化,实现两个玩家的操作的切换。turn = (turn +1) % 2

为了调用非静态方法,在main函数中new了MyChessAndGoGame对象,并且调用自己创建的myMain()方法。其中主体部分在myMain之中。

打印菜单(展示了实现的功能):

  1. BufferedReader提供通用的缓冲文本读取,readLine()读取一个文本行。InputStreamReader将字节流转化字符流。
    使用以上方式读取用户的输入。

2.让用户输入棋类名称chess or go,来确定调用Game类中哪种初始化方法,再读取用户输入的两个棋手的名称,游戏即可开始。

3.放置棋子(围棋)

读取line,调用String.split()将字符串分开。white映射到棋子名称,数字1和1映射到棋盘上的位置,player则通过对players索引获得。最后调用Game.putPiece()即可。其中异常情况的处理在Game类中已介绍。

4.提子(围棋)

读取line,调用String.split()将字符串分开。数字3和4映射到棋盘上的位置,player通过对players索引获得。最后调用Game.removePiece()即可。其中异常情况的处理在Game类中已介绍。

5.移动棋子(国际象棋)

读取line,调用String.split()将字符串分开。数字1和1映射需要移动的棋子点坐标,4和5映射目的地坐标,player通过对players索引获得。最后调用Game.movePiece()即可。其中异常情况的处理在Game类中已介绍。

6.吃子(国际象棋)

读取line,调用String.split()将字符串分开。数字6和7映射需要主动吃子的棋子点坐标,2和1映射被吃棋子坐标,player通过对players索引获得。最后调用Game.eatPiece()即可。其中异常情况的处理在Game类中已介绍。

7.查询某个位置的占用情况

读取line,调用String.split()将字符串分开。数字2和1映射需要待查位置坐标,player通过对players索引获得,该棋子通过Game.getOccupationOfPosition()方法得到,棋手名players.get(i).getPlayerName(),棋子名piece.getPieceName()。其中异常情况的处理在Game类中已介绍。

  1. 查看棋盘

调用Game.printBoard()方法打印棋盘。

9.计算两个玩家分别在棋盘上的棋子总数

调用Player.getPlayerName()和Player.countQuantityOfPieceInBoard()即可。

10.结束查看玩家历史

调用Player.getPlayerName()和Player.getHistory()来打印对应玩家的操作历史。其中每次历史中包括玩家名、棋子名和棋子移动的坐标。

3.3.3 ADT和主程序的测试方案
介绍针对各ADT的各方法的测试方案和testing strategy。
介绍你如何对该应用进行测试用例的设计,以及具体的测试过程。

测试方案:对于较为简单的类,Piece类、Player类、Position类,可以在testGame中定义对象的时候,辅助进行测试。
由于Action类的方法在Game类中会被调用,因此也不必单独测试。
主要是测试Game类和Board类。

Game类:对于chess、go两种棋类需要分别初始化棋盘,分别进行测试。使用assertEquals()语句判断game类获取的gameName、gamePlayer等是否与预期值一致,使用assertTrue()确定某个判断的真假,assertFalse()则是针对错误情况进行覆盖。
主要的几个方法如:putPiece(),eatPiece()等,测试方法基本一致。逐句测试,判断棋子位置、玩家历史、棋子归属等是否与Expection一致。对于非法情况,需要单独处理和判断,由于错误情况会抛出异常,因此需要根据各个异常情况构造方法单独测试。getOccupationOfPosition()方法则是判断返回值和预期Piece是否相等即可。
事实上,当测试完Game类,Board的测试也基本覆盖到了。特殊的是,Board类中有许多对于超出棋盘范围的check,这些需要举反例单独测试。

// Testing strategy
// 因为类之间的存在的调用关系,
// 先主要测试Game类
// 然后对其他类做补充
// 合法情况,非法情况,抛出异常
// 使用断言进行检查
// chess和go分开测试

测试覆盖率结果: