hit软件构造lab2

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分开测试

测试覆盖率结果:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值