1 实验目标概述
2 实验环境配置
略
3 实验过程
3.1 Poetic Walks
- 对给定的接口Graph< L >,完成两个实例类ConcretVerticesGraph和ConcreateEdgesGraph
- 1 首先分别构造两个实例类的rep类Vertex和Edge
- 2 用String作为L的特例,分别在两个实例类中实现Graph接口中的各个方法,写对应的test文件以保证其正确性
- 3 将所有的String变量替换为泛型L
- 对给定的文本文件,按照规定建立有向图,调用Graph< L >作为存储语料库的数据结构,利用该结构对输入的字符串进行扩充
3.1.1 Get the code and prepare Git repository
此步骤同Lab1,不作赘述。
3.1.2 Problem 1: Test Graph < String >
- Graph.empty()的测试在GraphStaticTest.java中
- 将Graph< L > empty()进行修改:
public static String Graph empty() {
Graph graph = new ConcreteEdgesGraph();
return graph;
}
- ConcreteEdgesGraph和ConcreteVerticesGraph等价,可以互换
3.1.3 Problem 2: Implement Graph < String >
3.1.3.1 Implement ConcreteEdgesGraph
3.1.3.1.1 Edge < String >类
private int weight;
private L sourceVertex;
private L targetVertex;
public Edge(L source,L target,int weight) {
this.weight=weight;
this.sourceVertex=source;
this.targetVertex=target;
checkRep();
}
-
checkRep
-
Function
- 三个fields的getter
- toString 函数:直观表示边,格式设置为:source->target [weight]
3.1.3.1.2 实现ConcreteEdgesGraph类
private final Set<L> vertices = new HashSet<>();
private final List<Edge<L>> edges = new ArrayList<>();
- Abstraction function
- 通过对graph中边和点的抽象
- 边的类中包含了source,target,weight
- 带方向的边
- 构成有向图
- Representation invariant
- 不变量在于隐含的数学关系
- n个点,最多构成n*(n-1)/2条有向边
- Safety from rep exposure
- 在可操作的情况下,所有的变量都定义为private , final
- 顶点和边是mutable类型, 因此多处使用防御式拷贝
- functions
函数 | 主要功能及思路 |
---|
public void checkRep() | n个点,最多构成n*(n-1)/2条有向边 |
@Override public boolean add(L vertex) | 判断vertex是否已经存在,存在返回false,否则添加这个点并返回true |
@Override public int set(L source, L target, int weight) | -遍历edges:如果这个边已经存在,替换掉它并返回旧的权值;如果不存在则加入一条新的边,返回0。 当输入权值为0,删除这条边 |
@Override public boolean remove(L vertex) | 调用set.contains判断图中是否存在该顶点:如果不存在,返回false;否则,遍历edges,删除起点或终点是该顶点的边,遍历结束后调用list.remove从vertices中删除该顶点。 |
@Override public Set< L > vertices() | 返回一个vertices的副本 |
@Override public Map<L, Integer> sources(L target) | 建立target的起点集:建立一个空Map,遍历edges,找出以target为终点的边edge;weight=0时表示该边不存在,跳过;将edge.source->edge.weight加入Map,遍历结束后返回这个Map |
@Override public Map<L, Integer> targets(L source) | 建立source的终点集:原理同上 |
@Override public String toString() | 将所有边的toString连在一起 |
3.1.3.1.3 实现测试GraphInstanceTest.java
函数 | 主要功能及思路 |
---|
public abstract Graph< String > emptyInstance() | 抽象方法创建新的Graph< String > |
@Test public void testAdd() | 如果不存在该顶点,修改graph,顶点数加1;否则图不变。通过顶点个数的变化来测试 |
@Test public void testRemove() | 分开测试weight的情况 : 0 , > 0:weight为0检查边是否被删除;weight > 0 , 检查边是否被添加或更新。观察source , target 的映射关系变化 |
@Test public void testVertices() | graph为空、graph添加顶点后:观察顶点的数量 |
@Test public void testSources() | graph为空、set点和边:观察source数量,对应关系是否正确 |
@Test public void testTargets() | 同上理 |
3.1.3.1.4 3.1.3.1.4实现测试ConcreteEdgesGraphTest.java
Graph中定义的接口已在InstanceTest中测试过了,此处不必重复测试。
函数 | 主要功能及思路 |
---|
@Override public Graph< String > emptyInstance() | 返回一个新的边图 |
@Test public void toStringTest() | graph空、不空时匹配String是否相等 |
以下为对于Edge类的测试:
函数 | 主要功能及思路 |
---|
@Test public void getSourceTest();@Test public void getVertexTest();@Test public void getWeightTest() ;@Test public void Edge_toStringTest() | 建立特定edge,返回特定内容,判断是否匹配 |
3.1.3.2 3.1.3.2Implement ConcreteVerticesGraph
3.1.3.2.1 3.1.3.2.1实现Vertex类
- fields
- private L name //顶点名
- private Map<L,Integer>sourceMap=new HashMap<>() //以当前点为终点的边集,其中key是起点名
- private Map<L,Integer>targetMap=new HashMap<>() //以当前点为起点的边集,其中key是终点名
- constructor
- public Vertex(L vertex) {this.name=vertex;}
- checkRep
- methods
- 三个fields的getter
- name、source、target的setter
- toString() //以当前点为起点的所有终点及边权值
3.1.3.2.2 3.1.3.2.2实现ConcreteVertices类
- fields
- private final List< Vertex > vertices
- Abstraction function
- 将有向加权图描述为顶点集
- 边为点之间的映射关系
- 边有权值weight
- Representation invariant
- 不允许重名顶点存在,每个顶点只能有一个存在
- 顶点总数等于vertices大小
- Safety from rep exposure
- checkRep
for(int i=0;i<vertices.size();i++) {
for(int j=i+1;j<vertices.size();j++) {
assert !(vertices.get(i).getName().equals(vertices.get(j).getName())) :"duplicate vertices";
}
assert vertices.get(i)!=null;
}
for(Vertex<L> s:vertices) {
assert s!=null;
}
函数 | 主要功能及思路 |
---|
@Override public boolean add(L vertex) | 如果有同名点存在,返回false; |
否则加入这个点返回true | |
@Override public int set(L source, L target, int weight) | 先找起点和终点,没有就添加;遍历找这个顶点,再遍历在这个顶点的终点集找这个终点,找到了则更新权值并返回旧权值(如果输入weight则删除这个边);没找到则加入一条新的边并返回0; |
注意删除的时候起点集和终点集都要进行相关的删除操作 | |
@Override public boolean remove(L vertex) | 如果vertices中不包含vertex,返回false;否则在vertices所有点的起点和终点集合中删掉vertex |
@Override public Set< L > vertices() | 返回一个vertices的副本 |
@Override public Map<L, Integer> sources(L target) | target存在返回起点的集合,不存在返回空map(Collections.emptyMap()) |
@Override public Map<L, Integer> targets(L source) | 同上理 |
@Override public String toString() | 遍历所有vertices,将他们的toString串在一起 |
3.1.3.2.3 实现测试ConcreteVerticesGraphTest.java
函数 | 主要功能及思路 |
---|
@Override public Graph< String > emptyInstance() | 返回一个新的顶点图 |
@Test public void toStringTest() | 测试边图连接情况 |
3.1.4 Problem 3: Implement generic Graph< L >
3.1.4.1 Make the implementations generic
将两个实例类中的所有String类的参数替换为泛型< L >的参数(声明、函数参数、返回值、rep)
3.1.4.2 Implement Graph.empty()
将Graph.empty中的< String >变成< L >
3.1.5 Problem 4: Poetic walks
3.1.5.1 Test GraphPoet
函数 | 主要功能及思路 |
---|
public void StrategyTest() throws IOException | 给定文件的测试:其中assertEquals(“Expected the same poem”,“test of the system.”,nimoy.poem(input)) |
public void MyTest() throws IOException;public void MyTest2() throws IOException | 两个自定义文件测试,其中包含了权值的选择问题 |
3.1.5.2 Implement GraphPoet
- fields
- private final Graph graph = Graph.empty(); //有向图
- private final List< String > words=new ArrayList< String >() //文本的单词
- Abstraction function
- 将输入的文本单词提取作为顶点
- 构建有向图
- 转化为poem
- 不区分大小写
- Representation invariant
- Safety from rep exposure
- 所有fields都是private final
- 防御式编程
- Functions
函数 | 主要功能及思路 |
---|
public GraphPoet(File corpus) throws IOException | 读取文件到words里,为了不区分大小写便于比较,全部转化成LowerCase;遍历words,将所有单词加入有向图;设weight初始为1,从头二次遍历,重复连接的weight++ |
public void checkRep() | graph和words都不为空 |
public String poem(String input) | input.split("\s")将结果储存到一个单词连接成的链表中;遍历这个链表,依次选取每相邻的两个单词a,b;看这两个单词在已有的graph中是否有a->c->b这样的连接:有的话,选取a->c权值较大的c插入链表的ab之间;将链表内单词重新连接成字符串并返回 |
public String toString() | 调用Graph.toString() |
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< Person >,根据实验指导的提示,类中需要增加一些非法情况的判断,例如人已经存在,或者边已经存在等。
函数 | 主要功能及思路 |
---|
public boolean addVertex(Person person) | 判断用户是否已经存在,调用父类this.add() |
public boolean addEdge(Person p1,Person p2) | 同上理 |
public int getDistance(Person p1,Person p2) | 和Lab1中思路完全相同,只不过当遍历个体p的朋友关系时遍历他的targets即可 |
main() | 和Lab1相同,不作改动 |

3.2.2 Person类
- 给出name和visited(用于BFS搜索)
- getName()和判断名称相同的nameSameWith()方法
3.2.3 客户端main()
3.2.4 测试用例
根据Lab1进行即可
3.3Playing Chess
3.3.1 ADT设计/实现方案
3.3.1.1 Piece类
- Fields
- final private String belong //棋子所属玩家
- final private String type //棋子类型名称
- private int pieceState=0 //棋子状态,0未放置、1放置、-1remove
- private Position piecePosition=new Position(-1,-1 ) //棋子位置
- Constructor
- public Piece(String belong,String type)
- Function
函数 | 主要功能及思路 |
---|
四个fields的getter | |
public void setPieceState(int newState) | 更改棋子状态 |
public void setPiecePosition(int x,int y) | 更改棋子位置 |
3.3.1.2 Player类
- Fields
- final private String name //玩家名
- private Set< Piece > pieces=new HashSet< Piece >() //玩家拥有的棋子
- private List< String > historyList=new LinkedList< String >() //顺序保存玩家一局游戏里的走棋历史
- Constructor
- public Player(String name)
- Function
函数 | 主要功能及思路 |
---|
三个getter | |
public boolean addPiece(Piece newPiece) | 添加棋子:玩家已有棋子返回false,否则添加并返回true |
public boolean removePiece(Piece removePiece) | 存在则移除并返回true,否则false |
public int getNumberOfPieces() | 遍历获取棋盘上属于此玩家的棋子数 |
public void addStep(String step) | 增加一个走棋步骤 |
3.3.1.3 Position类
- Fields
- private int x;
- private int y;
- Constructor
- public Position(int x,int y)
- Function
函数 | 主要功能及思路 |
---|
横纵坐标的getter | |
position的setter | |
public boolean equals(Position that) | 判断两个棋子是否重合 |
3.3.1.4 Board类
- Fields
- final private int type //棋盘类型:0表示棋子落在0 ~ n-1的空格内,1表示棋子落在0~n的交点上
- final private int size //棋盘大小size*size
- private Piece [ ][ ] pieces //存储棋盘上某个位置的棋子
- private boolean [ ][ ] placed //判断棋盘上某个位置是否放置了棋子
- Constructor
public Board(int type,int size) {
this.type=type;
size+=type;
this.size=size;
pieces=new Piece[size][size];
placed=new boolean[size][size];
for (int i=0;i<size;i++) {
for (int j=0;j<size;j++)
placed[i][j]=false;
}
}
函数 | 主要功能及思路 |
---|
type和size的getter | |
public Piece getPiece(int x,int y) | 给出一对横纵坐标,获取该位置上的棋子,如果没有,返回null。注意超出棋盘范围的提示 |
public boolean setPiece(Piece p,int x,int y) throws Exception | 在位置(x,y)上放置piece |
public void setPlaced(int x,int y) | 将(x,y)处设置为已有棋子放置 |
public void setNotPlaced(int x,int y) | 将(x,y)处设置为无棋子放置 |
public boolean getPlaced(int x,int y) | 获取(x,y)是否有棋子放置 |
3.3.1.5 Action类
public interface Action:提供各种行为的接口
函数 | 主要功能及思路 |
---|
public static Action empty(String gameType) | 根据gameType选择游戏类型 |
public Player createPlayer1(String player1Name) | |
public Player createPlayer2(String player2Name) | |
public Board board() | 创建一个新的空棋盘 |
public void initialize() | 初始化:设置相关参数;生成一个初始化棋盘;生成一组棋子;给定两个名称,初始化两个player;将棋子分配给两个玩家 |
public boolean placePiece(Player player,Piece piece,int x,int y) | 落子:该棋子并非属于该棋手、指定的位置超出棋盘的范围、指定位置已有棋子、所指定的棋子已经在棋盘上等,返回false;否则正常落子返回true |
public boolean movePiece(Player player,int sourceX,int sourceY,int targetX,int targetY) | 移子:指定的位置超出棋盘的范围、目的地已有其他棋子、初始位置尚无可移动的棋子、两个位置相同、初始位置的棋子并非该棋手所有等,false;否则正常移动棋子返回true |
public boolean removePiece(Player player,int x,int y) | 提子:该位置超出棋盘的范围、该位置无棋子可提、所提棋子不是对方棋子等,返回false;以上情况之外,可以正常移除对方棋子则返回true |
public boolean eatPiece(Player player,int sourceX,int sourceY,int targetX,int targetY) | 吃子:指定的位置超出棋盘的范围、第一个位置上无棋子、第二个位置上无棋子、两个位置相同、第一个位置上的棋子不是自己的棋子、第二个位置上的棋子不是对方棋子,返回false;以上异常情况之外可以正常吃掉对方棋子,则返回true |
3.3.1.6 实例类ChessAction
- rep
- final protected static int boardsize=8 //国际象棋棋盘大小8*8
- final protected static int piecesize=32 //国际象棋初始棋子总数为32个
- protected Player player1
- protected Player player2
- protected Board chessBoard
- Function
- 继承Action的所有方法,按照说明完成即可
- 其中初始化时需要直接把所有棋子分配并按照相应固定位置摆放到棋盘上
- 落子和提子不属于国际象棋的操作,始终返回false即可
3.3.1.7 实例类GoAction
- rep
- 同ChessAction理
- 其中boardsize=18,piecesize=361,建立goBoard
- function
- 初始化时建立相应空棋盘即可
- 移动棋子和吃子不属于围棋操作,返回false
3.3.1.8 Game类
- rep
- private String gameName;//游戏名
- private Board gameBoard;//游戏棋盘
- private Action gameAction;//动作
- private Player player1;
- private Player player2;
- AF
- Game为一局游戏,gameBoard映射为game中的棋盘,操作者为player1和player2
- gameAction为棋手操作
- RI
- Game,gameBoard,Action不可映射为空
- Safety from rep exposure
- fields均为private和final
- immutable数据类型
- Function
函数 | 主要功能及思路 |
---|
Fields的getter | |
player1,2,gameName的setter | |
四个Action类相关方法 | |
public Piece getOccupationOfPosition(Position position) throws Exception | 从指定位置获取棋子 |
public void initByChess(String name1, String name2) | 设置国际象棋chess棋盘。设置棋盘的gameName,通过参数设置players名称,初始化棋盘 |
public void initByGo(String name1, String name2) | 设置围棋go棋盘 |
public void printBoard() | 打印当前棋盘现状。注意打印时0行在最下端,故遍历时应相应倒序 |
3.3.2 主程序MyChessAndGoGame设计/实现方案
public String player1Name;
public String player2Name;
public String gameType;
| |
---|
主程序入口 |  |
功能菜单 |  |
主函数 |   |
具体操作步骤 | public void gameStart(Game game) throws Exception 设置int turn便于计轮次,大循环中一方完成相应举动轮次++,这样就能通过%2来判断当前轮到哪一方操作;循环内部采取switch-case结构;类似落子的操作需要循环执行直到落子成功再break,这算一步,而如果落子后还要提子,则等提子结束后才算一步;每一个case内部设置好相应的循环即可 |
运行截图略
3.3.3 ADT和主程序的测试方案
由于Game的本质就是调用Action类,那么我们直接做好Game的检测就能证明Action的正确性,无需再单独测Action和两个实例类
| |
---|
ToolClassTest | 包括Player、Position、Piece、Board的相关功能检测 |
GameTest | 其中go和chess需要分别检测,并且对Game中单独出现的方法也要进行检测 |
确保所有实验指导书上的要求功能(即暴露给用户的功能)均被测试过即可。