本項目於3.17日實驗課驗收,請放心參考
參考時文中有給出一些建議,請查看
基本更新完成
2020春計算機學院《軟件構造》課程Lab2實驗報告
- Software Construction 2020 Spring
- Lab-2 Abstract Data Type (ADT) and Object-Oriented Programming (OOP)
- CSDN博客
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 實驗環境配置
2.1 安裝EclEmma
依據https://www.eclemma.org/installation.html內容,從更新站點進行安裝。
- 從Eclipse菜單中選擇幫助 → 安裝新軟件;
- 在“安裝”對話框中,在“ 工作日期”字段中輸入http://update.eclemma.org/;
- 檢查最新的EclEmma版本,然後按“下一步”;
- 重啓eclipse,即可在java的透視圖工具欄中找到coverage啓動器,表示安裝成功。
- 使用效果
2.2 GitHub Lab2倉庫的URL地址
略
3 實驗過程
3.1 Poetic Walks
該任務主要是實驗一個圖的模塊,並基於此使用。
- 完善Graph接口類,並運用泛型的思想,將String拓展爲泛型L類;
- 實現Graph類的方法:add、set、remove、vertices、sources、targets;
- 利用實現的Graph類,應用圖的思想,實現GraphPoet類,如果輸入的文本的兩個單詞之間存在橋接詞,則插入該橋接詞;若存在多個單一橋接詞,則選取邊權重較大者。
3.1.1 Get the code and prepare Git repository
git clone https://github.com/rainywang/Spring2020_HITCS_SC_Lab2.git
3.1.2 Problem 1: Test Graph < String >
測試靜態方法生成String類型的Graph。
3.1.3 Problem 2: Implement Graph
該部分要求重寫Graph裏的方法,分別以點爲基礎的圖和以邊爲基礎的圖。
節選部分較難實現的方法代碼
3.1.3.1 Implement ConcreteEdgesGraph
Edge實現
- Edge的功能主要爲存儲邊的3個信息。此外,爲了Graph實現方便,增加了判斷兩條邊是否相等的方法。
ConcreteEdgesGraph實現 - 該類以Edge爲基礎重寫Graph,用集合來存儲點和邊(Edge),每有Edge的增加就會影響到集合的更改,而點的刪除也需要在集合中查詢匹配。
- set()
@Override
public int set(L source, L target, int weight) {
if (weight < 0)
throw new RuntimeException("Negative weight");
if (!vertices.contains(source) || !vertices.contains(target)) {
if (!vertices.contains(source))
this.add(source);
if (!vertices.contains(target))
this.add(target);
}
if (source.equals(target)) // source is the same with target, REFUSE to set the Edge.
return 0;
// Find the same edge
Iterator<Edge<L>> it = edges.iterator();
while (it.hasNext()) {
Edge<L> edge = it.next();
if (edge.sameEdge(source, target)) {
int lastEdgeWeight = edge.weight();
it.remove();
if (weight > 0) {
Edge<L> newEdge = new Edge<L>(source, target, weight);
edges.add(newEdge);
}
checkRep();
return lastEdgeWeight;
}
}
// weight=0 means delete an edge, so it can't be before FINDING
if (weight == 0)
return 0;
// new positive edge
Edge<L> newEdge = new Edge<L>(source, target, weight);
edges.add(newEdge);
checkRep();
return 0;
}
- remove()
@Override
public boolean remove(L vertex) {
if (!vertices.contains(vertex))
return false;
edges.removeIf(edge -> edge.source().equals(vertex) || edge.target().equals(vertex));
vertices.remove(vertex);
checkRep();
return true;
}
- JUnit測試
3.1.3.2 Implement ConcreteVerticesGraph
Vertex實現
- Vertex是點的抽象類,包含3個信息:點的標識、指向該點的邊、由該點引出的邊。Vertex需要能訪問這3個信息,以及增加/刪除進邊/出邊。
- setInEdges()
public int setInEdge(L source, int weight) {
if (weight <= 0)
return 0;
Iterator<L> it =inEdges.keySet().iterator();
while (it.hasNext()) {
L key = it.next();
if (key.equals(source)) {
int lastEdgeWeight = inEdges.get(key);
it.remove();
inEdges.put(source, weight);
return lastEdgeWeight;
}
}
inEdges.put(source, weight);
checkRep();
return 0;
}
ConcreteVerticesGraph實現
- ConcreteVerticesGraph是以點爲基礎的圖,每個點通過唯一的標識進行區分,set和remove都依賴與Vertex類中的添加和刪除操作,sources和targets也調用了Vertex類的方法。
- set()
@Override
public int set(L source, L target, int weight) {
if (weight < 0)
throw new RuntimeException("Negative weight");
if (source.equals(target))
return 0;
Vertex<L> from = null, to = null;
for (Vertex<L> vertex : vertices) {
if (vertex.ThisVertex().equals(source))
from = vertex;
if (vertex.ThisVertex().equals(target))
to = vertex;
}
if (from == null || to == null)
throw new NullPointerException("Inexistent vertex");
int lastEdgeWeight;
if (weight > 0) {
lastEdgeWeight = from.setOutEdge(target, weight);
lastEdgeWeight = to.setInEdge(source, weight);
} else {
lastEdgeWeight = from.removeOutEdge(target);
lastEdgeWeight = to.removeInEdge(source);
}
checkRep();
return lastEdgeWeight;
}
- remove()
@Override
public boolean remove(L vertex) {
for (Vertex<L> THIS : vertices) {
if (THIS.ThisVertex().equals(vertex)) {
for (Vertex<L> v : vertices) {
if (THIS.sources().containsKey(v)) {
// THIS.removeInEdge(v);
v.removeOutEdge(THIS.ThisVertex());
}
if (THIS.targets().containsKey(v)) {
// THIS.removeOutEdge(v);
v.removeInEdge(THIS.ThisVertex());
}
}
vertices.remove(THIS);
checkRep();
return true;
}
}
checkRep();
return false;
}
- JUnit測試
3.1.4 Problem 3: Implement generic Graph < L >
3.1.4.1 Make the implementations generic
- 在程序中選擇“重構”或選擇“String”並選擇更改所有匹配項(要注意toString),即可實現泛化類型。
3.1.4.2 Implement Graph.empty()
- 使Graph.empty()能返回一個新的空實例。代碼如下:
public static Graph<String> empty() {
return new ConcreteEdgesGraph();
}
3.1.5 Problem 4: Poetic walks
前文指南
問題簡述:
- 給定一個語料庫corpus,根據corpus中的文本生成一個單詞圖,然後給定一條語句輸入,在圖中搜索詞之間的關係,自動補全語句中可能可以完善的部分。
- 圖的構建規則是,在corpus中,對每一個不一樣的單詞看作一個頂點,相鄰的單詞之間,建立一條有向邊,相鄰單詞對出現的次數,作爲這條有向邊的權值。在輸入信息補全時,對相鄰單詞A和B做檢查,如果存在一個單詞C,在圖中可以由前一個單詞A通過這個單詞C到達單詞B,那麼就在A和B之間補全C,補全的優先級按照權值越大者優先。
3.1.5.1 Test GraphPoet
- 在基於預設的測試用例基礎上,增加等價類劃分的多種情況。
- 等價類劃分:兩個單詞之間不存在連接詞,兩個單詞之間只有一個連接詞,兩個單詞之間有多個連接詞。
- 此外還要注意句末的句號,測試當一個句子最後一個詞是“橋”的一端。
3.1.5.2 Implement GraphPoet
1. 表示不變量和檢查不變量
- 該應用中的不變量是所有的點都不爲空。
2. 構造函數
- 用文件輸入單詞,String.split()分割爲數組,通過String.toLowerCase()小寫化。
接下來構建圖,相鄰的單詞加邊。首先要在加邊前通過Graph.add()加點,加邊時要判斷是否存在:由於Graph.set()能返回之前加的邊的值,以此來判斷是否存在,存在則在之前的值加一(之前的邊的值保存爲lastEdgeWeight)。
int lastEdgeWeight = graph.set(words[i - 1].toLowerCase(), words[i].toLowerCase(), 1);
if (lastEdgeWeight != 0) graph.set(words[i - 1].toLowerCase(), words[i].toLowerCase(), lastEdgeWeight + 1);
3. Poem(String input)
-
當相鄰兩個單詞任意一個不在之前創建的圖裏,則將後者單詞加入即可(再加個空格)當存在時,由於Bridge長度只能爲2,所以:分別求兩個單詞的sources和targets,將該Map轉換爲Set求交集;若交集爲空,則無橋,若交集不空,則在交集中找最短的橋(可以在Map的value中查詢weight)。
-
求交集
targets = graph.targets(words[i - 1].toLowerCase());
sources = graph.sources(words[i].toLowerCase());
intersection = sources.keySet();
intersection.retainAll(targets.keySet());
- 求最大值
int maxBridge = Integer.MIN_VALUE;
String bridge = "";
for (String key : intersection) {
if (sources.get(key) + targets.get(key) > maxBridge) {
maxBridge = sources.get(key) + targets.get(key);
bridge = key;
}
}
3.1.5.3 Graph poetry slam
-
樣例
“This is a test of the Mugar Omni Theater sound system.”
進行測試,測試成功。
-
修改樣例爲
“This is a the Mugar system Omni Theater sound system test of the.”
,測試成功。該樣例用於測試極端情況。
-
Junit測試
3.1.6 Before you’re done
請按照 http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done 的說明,檢查你的程序。
如何通過Git提交當前版本到GitHub上你的Lab2倉庫。
在這裏給出你的項目的目錄結構樹狀示意圖。
3.2 Re-implement the Social Network in Lab1
- 這部分任務就是用我們在3.1中寫的ADT,把第一次實驗中的FriendshipGraph重新實現一遍,圖中的節點仍然是Person類型,所以泛型L一律爲Person. 而對於已經寫好的FriendshipGraph中的方法,要用3.1中的Graph ADT中的方法來實現它們。
3.2.1 FriendshipGraph類
- Graph < Person > graph:
- 直接調用Graph的靜態方法.empty()生成一個空的圖。
- boolean addVertex():
- 直接調用graph.add()添加點。
- int addEdge():
- 調用graph.set()兩次,添加雙向邊,默認權值爲1,並記錄可能存在的舊邊的權值。
- int getDistance():
- 首先判斷起止點是否相等。再新建Map<Person, Integer> dis表示從起始點開始到該Person的距離,以及Map<Person, Boolean> vis表示該Person是否訪問過。將兩個Map初始化後,把起點標記爲已經訪問(所有涉及這兩個Map的操作均需要remove後再put,後文不再闡述)。然後開始BFS搜索,找到終點爲止。
/**
* get Distance of two of them
*
* @param sta path starting person
* @param end path ending person
* @return distance between 2 persons or -1 when unlinked
*/
public int getDistance(Person sta, Person end) {
if (sta.equals(end))
return 0;
Map<Person, Integer> dis = new HashMap<>();
Map<Person, Boolean> vis = new HashMap<>();
Queue<Person> qu = new LinkedList<Person>();
Set<Person> persons = graph.vertices();
for (Person person : persons) {
dis.put(person, 0);
vis.put(person, false);
}
vis.remove(sta);
vis.put(sta, true);
for (qu.offer(sta); !qu.isEmpty();) {
Person person = qu.poll();
for (Map.Entry<Person, Integer> edge : graph.targets(person).entrySet()) {
Person target = edge.getKey();
if (!vis.get(target)) {
qu.offer(target);
vis.remove(target);
vis.put(target, true);
dis.remove(target);
dis.put(target, dis.get(person) + 1);
if (target.equals(end))
return dis.get(target);
}
}
}
return -1;
}
3.2.2 Person類
- 該類的目標是將每一個人對應到一個Person對象,並存儲名字的信息。爲了防止泄露,我將String Name設置爲私有且不可變的。在構造函數中將Name初始化。
3.2.3 客戶端main()
public class FriendshipGraphTest {
/**
* Basic Network Test
*/
@Test
public void Test1() {
final FriendshipGraph graph = new FriendshipGraph();
final Person rachel = new Person("Rachel");
final Person ross = new Person("Ross");
final Person ben = new Person("Ben");
final Person kramer = new Person("Kramer");
assertEquals(true, graph.addVertex(rachel));
assertEquals(true, graph.addVertex(ross));
assertEquals(true, graph.addVertex(ben));
assertEquals(true, graph.addVertex(kramer));
assertEquals(0, graph.addEdge(rachel, ross));
assertEquals(1, graph.addEdge(ross, rachel));
assertEquals(0, graph.addEdge(ross, ben));
assertEquals(1, graph.addEdge(ben, ross));
assertEquals(1, graph.getDistance(rachel, ross));
assertEquals(2, graph.getDistance(rachel, ben));
assertEquals(0, graph.getDistance(rachel, rachel));
assertEquals(-1, graph.getDistance(rachel, kramer));
}
/**
* Further Test
*/
@Test
public void Test2() {
final FriendshipGraph graph = new FriendshipGraph();
final Person a = new Person("A");
final Person b = new Person("B");
final Person c = new Person("C");
final Person d = new Person("D");
final Person e = new Person("E");
final Person f = new Person("F");
final Person g = new Person("G");
final Person h = new Person("H");
final Person i = new Person("I");
final Person j = new Person("J");
assertEquals(true, graph.addVertex(a));
assertEquals(true, graph.addVertex(b));
assertEquals(true, graph.addVertex(c));
assertEquals(true, graph.addVertex(d));
assertEquals(true, graph.addVertex(e));
assertEquals(true, graph.addVertex(f));
assertEquals(true, graph.addVertex(g));
assertEquals(true, graph.addVertex(h));
assertEquals(true, graph.addVertex(i));
assertEquals(true, graph.addVertex(j));
assertEquals(0, graph.addEdge(a, b));
assertEquals(0, graph.addEdge(a, d));
assertEquals(0, graph.addEdge(b, d));
assertEquals(0, graph.addEdge(c, d));
assertEquals(0, graph.addEdge(d, e));
assertEquals(0, graph.addEdge(c, f));
assertEquals(0, graph.addEdge(e, g));
assertEquals(0, graph.addEdge(f, g));
assertEquals(0, graph.addEdge(h, i));
assertEquals(0, graph.addEdge(i, j));
assertEquals(2, graph.getDistance(a, e));
assertEquals(1, graph.getDistance(a, d));
assertEquals(3, graph.getDistance(a, g));
assertEquals(3, graph.getDistance(b, f));
assertEquals(2, graph.getDistance(d, f));
assertEquals(2, graph.getDistance(h, j));
assertEquals(0, graph.getDistance(i, i));
assertEquals(-1, graph.getDistance(d, j));
assertEquals(-1, graph.getDistance(c, i));
assertEquals(-1, graph.getDistance(f, h));
}
}
3.2.4 測試用例
3.2.4.1 簡單圖測試
- 根據題目中的社交網絡圖:
分別測試:
- Rachel和Ross距離是1,Rachel和Ben距離是2
- Rachel和Rachel距離是0
- Rachel和Kramer距離是-1
3.2.4.2 複雜圖測試
- 設計10個點、10條邊的社交網絡圖:
分別測試:
- AE距離2,AD距離1,AG距離3,BF距離3,DF距離2,HJ距離2
- II距離0
- DJ距離-1,CI距離-1,FH距離-1
3.2.4.3 Junit測試結果
- 全部正確。
3.2.5 提交至Git倉庫
- 如何通過Git提交當前版本到GitHub上你的Lab2倉庫。
- 在這裏給出你的項目的目錄結構樹狀示意圖。
3.3 Playing Chess
思路 / 簡化版實現方案
3.3 Playing Chess
問題簡述:
設計一款棋類遊戲,同時支持國際象棋(Chess)和圍棋(Go)。實現功能:
- 選擇遊戲類型:創建Game、Board
- 輸入玩家名字:創建Player、Piece,其中Piece屬於Player
- 開始遊戲,輪流選擇功能
- 放棋:給定player、piece、x、y
- 移動棋(chess):給定player、piece、x1、y1、x2、y2
- 提子(go):給定player、x、y
- 喫子(chess):給定player、x1、y1、x2、y2
- 查詢某個位置佔用情況:給定x、y
- 計算兩個玩家分別的棋子總數
- 跳過
- 結束:輸入“end”
整體架構
文件結構:
3.3.1 ADT設計/實現方案
3.3.1.1 interface Game
接口Game由chessGame和goGame實現,是Main()程序通向遊戲對象的路口,通過一個接口把兩種遊戲分開,相同的操作類型在不同遊戲中實現。
Game擁有7種操作所對應的方法,並且能支持訪問下屬的Player(先後手訪問、名字訪問)和Board,以及爲玩家產生所對應Piece的功能。
7種操作除了“end”均隸屬於Player對象進行操作,其中的“放棋”、“移動”、“喫子/提子”均在Action接口的實現類中完成,在Game接口的實現類中判斷是否執行成功即可。因此3種操作可以在Game的實現類中實現近乎標準化和統一(輸入操作類型String即可),以“喫子/提子”(capture)爲例:
@Override
public boolean capture(Player player, Position... positions) {
if (player == null)
return false;
return player.doAction("capture", null, positions) != null;
}
在兩種遊戲中,差異較大的之一就是棋子。棋子屬於玩家,但棋子是由一個特定類型的遊戲所“產生”的,因此Game的兩個實現類中差異最大的就是產生棋子的方法:
在chess中,黑白雙方棋子除了顏色都相同,因此可以用chessGame靜態成員變量預設好每個棋子的名字、數量和位置(黑白雙方可以用公式顛倒)。然後依據預設的靜態數據新建16個Piece對象,初始化Position、Player,最後加入Set中返回。goGame中大致相同。
/**
* the Map whose keys are the name of pieces, values are the numbers they are on board totally
*/
private static final Map<String, Integer> piecesSumMap = new HashMap<String, Integer>() {
private static final long serialVersionUID = 1L;
{
put("P", 8);
put("R", 2);
put("N", 2);
put("B", 2);
put("Q", 1);
put("K", 1);
}
};
/**
* the Map whose keys are the name of pieces, values are the coordinates of them
*/
private static final Map<String, int[][]> piecesPosMap = new HashMap<String, int[][]>() {
private static final long serialVersionUID = 1L;
{
put("P", new int[][] { { 0, 1, 2, 3, 4, 5, 6, 7 }, { 1, 1, 1, 1, 1, 1, 1, 1 } });
put("R", new int[][] { { 0, 7 }, { 0, 0 } });
put("N", new int[][] { { 1, 6 }, { 0, 0 } });
put("B", new int[][] { { 2, 5 }, { 0, 0 } });
put("Q", new int[][] { { 3 }, { 0 } });
put("K", new int[][] { { 4 }, { 0 } });
}
};
@Override
public Set<Piece> pieces(boolean firstFlag) {
Set<Piece> pieces = new HashSet<Piece>();
for (Map.Entry<String, Integer> entry : piecesSumMap.entrySet()) {
for (int i = 0; i < entry.getValue(); i++) {
String pieceName = (firstFlag ? "W" : "B") + entry.getKey() + i; // eg. WB1 BR2 WP3
Piece piece = new Piece(pieceName, firstFlag, (firstFlag ? player1 : player2));
// get the coordinate of a specific piece
int[] X = piecesPosMap.get(entry.getKey())[0];
int[] Y = piecesPosMap.get(entry.getKey())[1];
int x = X[i], y = (firstFlag ? Y[i] : CHESS_BOARD_SIDE - Y[i] - 1);
// put the piece on the position
piece.modifyPositionAs(board.positionXY(x, y));
board.positionXY(x, y).modifyPieceAs(piece);
// add the piece into the piece set of the player
pieces.add(piece);
}
}
return pieces;
}
Game是Board和Player的父類。Board的創建只能源於Game的構造函數,Player的創建必須後於Game且玩家的Piece依賴於Game的函數。
上圖上方三個分別是Game和Game的實現類chessGame和goGame,Board和Game隸屬於Game,在不同情況下調用兩種實現類,且這兩者無法聯繫,保護了對象的私有數據。
3.3.1.1.1 class chessGame
實現chess在Game中的功能。
3.3.1.1.2 class goGame
實現go在Game中的功能。
3.3.1.2 class Board
Board是棋盤的對象,構造依賴於Game的構造。Position的創建也依賴於Board,Board也存儲這二維Position類型數組,並且擁有final變量N記錄棋盤的邊長。
Board的主要用於查詢指定位置的Position、Piece和Player,以及打印棋盤。查詢Position可以直接訪問positions成員變量,而查詢Piece又要訪問指定位置的Position不爲空的Piece,而查詢Player又要查詢不空的Piece的Player。
在三個函數的實現中,按照調用關係,先後實現。在這裏設計Position和Piece平級且捆綁,同爲可變。
/**
* ask object of the position
* @param x the x of the asking position
* @param y the y of the asking position
* @return object of Position of the (x, y)
*/
public Position positionXY(int x, int y) {
if (x < 0 || x >= this.N || y < 0 || y >= this.N)
return null;
return board[x][y];
}
/**
* ask the piece on (x, y) if there isn't null
* @param x the x of the asking position
* @param y the y of the asking position
* @return object of Piece of the (x, y)
*/
public Piece pieceXY(int x, int y) {
if (positionXY(x, y) == null)
return null;
return positionXY(x, y).piece();
}
/**
* ask the player who owns the piece of (x, y) or null if not
* @param x the x of the asking position
* @param y the y of the asking position
* @return Player if (x, y) is occupied, null if it's free
*/
public Player XYisFree(int x, int y) {
if (pieceXY(x, y) == null)
return null;
return pieceXY(x, y).player();
}
- 此外,棋盤還具備打印功能。以下是實現方案和效果圖。
/**
* print the Board
* '.' if one position has no piece
* or the piece' name if not
*/
public void printBoard() {
for (int i = 0; i < this.N; i++) {
for (int j = 0; j < this.N; j++) {
if (this.pieceXY(i, j) != null) {
if (game.gameType().equals("chess")) {
/*
* the capital letter represents the white piece
* the little letter represents the black piece
*/
System.out.print((this.pieceXY(i, j).isFirst() ? this.pieceXY(i, j).name().charAt(1)
: this.pieceXY(i, j).name().toLowerCase().charAt(1)) + " ");
} else if (game.gameType().equals("go")) {
/*
* the 'B' represents the black pieces
* the 'W' represents the white pieces
*/
System.out.print(this.pieceXY(i, j).name().charAt(0) + " ");
}
} else {
// if there is no piece
System.out.print(". ");
}
}
System.out.println();
}
}
國際象棋:大寫代表白方,小寫代表黑方。
圍棋:B代表黑方,W代表白方。
棋盤能夠管理棋格/點,而根據要求棋盤是不能管理棋子的。因此Board是Game的子類,也是Position的父類。
3.3.1.3 class Player
Player對象代表着玩家,有這Boolean標籤區分先後手,擁有Piece並管理Action。
Player的方法除了查詢本對象的信息,還有尋找自己下屬的棋子,以及執行並記錄Action。
尋找棋子時:當以棋子名字查詢時,只需在成員變量Set pieces中遍歷,判斷棋子的名字是否相等即可;而當查詢任意一個空閒棋子時,則需判斷其position是否爲空。此外,Player還能計算本方棋盤上棋子總數。
/**
* ask the number of pieces
* @return the number of pieces
*/
public int sumPiece() {
int sum = 0;
for (Piece piece : pieces) {
// calculate the non-null piece
if (piece.position() != null) {
sum++;
}
}
return sum;
}
/**
* ask any of the free pieces
* @return a free piece belonging to the player
*/
public Piece freePiece() {
for (Piece piece : this.pieces) {
// find a random free piece
if (piece.position() == null)
return piece;
}
return null;
}
/**
* find a piece which owns the same name
* @param pieceName String of the name of the piece
* @return piece object of the piece name
*/
public Piece findPieceByName(String pieceName) {
for (Piece piece : this.pieces) {
// find the piece whose name is matched with the giving
if (piece.name().equals(pieceName))
return piece;
}
return null;
}
在執行Action的方法中,通過構造一個新的Action對象來執行。在Action對象的內部會進行完整的操作,玩家只需訪問改動作對象的成功與否,然後加入List存儲即可。
/**
* generate a new action and init the action type
* @param actionType String of the type of the action
* @param piece the putting piece when the actionType is "put", null if not
* @param positions the positions related to the action
* @return the object of the action created
*/
public Action doAction(String actionType, Piece piece, Position... positions) {
if (!actionTypes.contains(actionType))
return null;
Action action = Action.newAction(this.game.gameType(), this, actionType, piece, positions);
if (action.askSuccess())
actions.add(action);
else
action = null;
return action;
}
3.3.1.4 class Position
Position代表着國際象棋棋盤的格子以及圍棋棋盤的交叉點。
Position隸屬於Board,一個對象的x和y是不可變的,但Position記錄的Piece對象是可變的,提供了方法進行修改。
/**
* to update the Piece of the Position
* @param newPiece the new piece that is to modify it as
* @return true if the Piece updated successfully, false if the new Piece is null
*/
public boolean modifyPieceAs(Piece newPiece) {
this.piece = newPiece;
checkRep();
return true;
}
3.3.1.5 class Piece
Piece代表着棋盤上的棋子,用一個唯一標識的String來區分每一個棋子,和一個Boolean來區分先後手,這兩個信息是不可變的。
和Position相似,Piece也提供了修改Position的方法,用於移動棋子等變化操作。
/**
* to update the position of the piece
* @param newPosition the new position that is to modify it as
* @return true if the position updated successfully, false if the newPosition is null
*/
public boolean modifyPositionAs(Position newPosition) {
this.position = newPosition;
checkRep();
return true;
}
3.3.1.6 interface Action
Action代表着要求裏的若干個操作,通過一個String來區分,在構造函數中就實現這一操作(主要是放棋、喫子/提子、移動)。所需要的參數通過客戶端的輸入,再由Game傳遞,Player的方法doAction()中構造,然後在Action對象中執行,並記錄執行的成敗。Action是基於Player實現的,作用於Position。
接口Action有一個靜態函數作爲構造器:
/**
* generate a new Action of one piece
* do the action
* @param gameType String of the type of the game
* @param player the acting player
* @param actionType String of the type of the action
* @param piece the operating piece
* @param positions the positions related to the action
* @return an object of a type of Action(chessAction or goAction)
*/
public static Action newAction(String gameType, Player player, String actionType, Piece piece,
Position... positions) {
return gameType.equals("chess") ? (new chessAction(player, actionType, piece, positions))
: (new goAction(player, actionType, piece, positions));
}
創建新的對象後,依據String actionType執行操作:
/**
* create and finish the action
* @param player the operating player
* @param actionType String of the type of the action
* @param piece the operating piece
* @param positions the position related to the action
*/
chessAction(Player player, String actionType, Piece piece, Position... positions) {
this.player = player;
this.positions = positions;
this.piece = piece;
this.actionType = actionType;
switch (actionType) {
case "put":
this.actionSuccess = (piece != null) && put();
break;
case "move":
this.actionSuccess = move();
break;
case "capture":
this.actionSuccess = capture();
break;
case "AskIsFree":
this.actionSuccess = true;
break;
case "SumPiece":
this.actionSuccess = true;
break;
case "skip":
this.actionSuccess = true;
break;
default:
this.actionSuccess = false;
}
checkRep();
}
chessAction和goAction分別重寫Action中的操作,主要是put()、move()、capture()三個方法。最後通過方法的返回值將結果記錄在actionSuccess。
3.3.1.6.1 class chessAction
chessAction代表着國際象棋中的操作。
put()操作與go類型,將在goAction中舉例。move()操作和capture()操作中,國際象棋比圍棋多需要1個Position操作,因此輸入Position時就以不定項參數輸入。兩個操作大體類似,主要區別於:move的目標格要求爲空,capture需要移除目標格棋子。以capture()爲例:
public boolean capture() {
Position source = this.positions[0], target = this.positions[1];
// capture requirement: 1. the target can't be null 2. the source can't be null
// 3. the target must belong to the OPPOSITE 4. the source must belong to this player
if (target.piece() != null && source.piece() != null && (!target.piece().player().equals(player))
&& source.piece().player().equals(player)) {
target.piece().modifyPositionAs(null); // the piece capturing removed
source.piece().modifyPositionAs(target); // captured piece move to the target
target.modifyPieceAs(source.piece());// move the piece, this must be done before source's piece be null
source.modifyPieceAs(null);// set the source null
return true;
}
return false;
}
3.3.1.6.2 class goAction
goAction代表着圍棋中的操作。
put()操作在國際象棋和圍棋中類似:客戶端輸入棋的名字(類型即可、無編號),然後game尋找該棋子後輸入action對象;board將(x, y)轉換爲Position對象後輸入action對象;最後執行:(以goAction爲例)
@Override
public boolean put() {
Position target = this.positions[0];
// put requirement:
// 1. the piece of the target can't be null
// 2. the putting piece can't be null
// 3. the piece must belong to the player
if (this.piece.position() == null && target.piece() == null && player.pieces().contains(piece)) {
this.piece.modifyPositionAs(target);
target.modifyPieceAs(this.piece);
return true;
}
return false;
}
3.3.2 主程序MyChessAndGoGame設計/實現方案
Main()主要分爲3個步驟:
- 新建遊戲和玩家
- 選擇遊戲功能並執行
- 打印遊戲記錄
3.3.2.1 遊戲流程圖
3.3.2.2 程序演示
- 初始化:
- 給出提示語,輸入遊戲類型、玩家雙方名字。若創建成功則顯示“Game Start”。
- 給出提示語,輸入遊戲類型、玩家雙方名字。若創建成功則顯示“Game Start”。
- 遊戲功能執行:
- 請用戶選擇玩家(輸入名字),給出功能菜單,在用戶選擇後要求用戶輸入參數,執行功能,返回結果(true/false),打印當前棋盤。
下面給出一些操作的示例:
- 請用戶選擇玩家(輸入名字),給出功能菜單,在用戶選擇後要求用戶輸入參數,執行功能,返回結果(true/false),打印當前棋盤。
- 輸出記錄:
3.3.2.3 功能實現
- 初始化:
/**
* provide 2 choices on screen for users to choose chess or go.
* generate new Game;
* generate new Board;
*
* get 2 players' names printed on the screen.
* generate 2 new Player;
* generate new Piece belonged to Player;
*
* @param args FORMAT
*/
public static void main(String[] args) {
// scan : 3 String
System.out.println("Please choose a type of game (chess/go):");
String gameType = input.nextLine();
System.out.println("Please write the player1's name (First):");
String playerName1 = input.nextLine();
System.out.println("Please write the player2's name (Later):");
String playerName2 = input.nextLine();
// new objects
final Game game = Game.newGame(gameType);
final Player player1 = new Player(game, playerName1, true);
final Player player2 = new Player(game, playerName2, false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// gaming
GAME(game);
input.close();
// actions
printRecord(game, player1, player2);
System.out.println("That's All! See You Next Time!");
}
- 功能執行:
/**
* provide 7 choices including;
* 1. to put a piece : put();
* 2. to move a piece : move();
* 3. to capture particular piece(s) : capture();
* 4. ask whether a position is free or not : isFree();
* 5. calculate the sum of the pieces on the
* board : sumPiece();
* 6. skip : skip()
* 7. print "end" : end()
* @param game the object of the game
*/
private static void GAME(Game game) {
boolean endFlag = false;
System.out.println("Game Start!");
while (!endFlag) {
System.out.println("Please choose a player:");
String playerName = input.next();
endFlag = playerActing(game, game.choosePlayerByName(playerName));
game.board().printBoard();
}
}
/**
* The chosen player is operating one choice
* @param game the object of the game
* @param player the object of the operating player
* @return true if the player choose ending the game, false if not
*/
private static boolean playerActing(Game game, Player player) {
// menu
System.out.println("Please choose an action type:");
System.out.println("1. put");
System.out.println(game.gameType().equals("chess") ? "2. move" : "");
System.out.println("3. capture");
System.out.println("4. ask: (x, y) is free?");
System.out.println("5. ask: sum of both players' pieces");
System.out.println("6. skip the choose");
System.out.println("7. end the game");
// input information
String pieceName = "";
int x1, y1; // source
int x2, y2; // target
// catch choice
System.out.print("Your Choice is:");
int choice = input.nextInt();
while (choice > 0 && choice <= 7) { // prepare the probable wrong choice
switch (choice) {
case 1: // put
if (game.gameType().equals("chess")) {
pieceName = input.next("Piece Name (eg. WQ0 BP2): ");
}
System.out.print("The (x, y) of the target: ");
x1 = input.nextInt();
y1 = input.nextInt();
// choose a piece freely if go
// get the particular piece if chess
Piece puttingPiece = game.gameType().equals("chess") ? player.findPieceByName(pieceName)
: player.freePiece();
// print result
System.out.println(game.put(player, puttingPiece, game.board().positionXY(x1, y1)));
return false;
case 2: // move
System.out.print("The (x, y) of both source and target: ");
x1 = input.nextInt();
y1 = input.nextInt();
x2 = input.nextInt();
y2 = input.nextInt();
System.out.println(
game.move(player, game.board().positionXY(x1, y1), game.board().positionXY(x2, y2)));
return false;
case 3: // capture
if (game.gameType().equals("chess")) {
System.out.print("The (x, y) of both source and target: ");
x1 = input.nextInt();
y1 = input.nextInt();
x2 = input.nextInt();
y2 = input.nextInt();
System.out.println(
game.capture(player, game.board().positionXY(x1, y1), game.board().positionXY(x2, y2)));
} else if (game.gameType().equals("go")) {
System.out.print("The (x, y) of the target: ");
x1 = input.nextInt();
y1 = input.nextInt();
System.out.println(game.capture(player, game.board().positionXY(x1, y1)));
}
return false;
case 4: // is free?
System.out.print("The (x, y) of the questioning grid: ");
x1 = input.nextInt();
y1 = input.nextInt();
Player here = game.isFree(player, x1, y1);
System.out.println(here == null ? "Free" : here.name());
return false;
case 5: // sum of pieces
Map<Player, Integer> sumPiece = game.sumPiece(player); // two players' sum of pieces
System.out.println(game.player1().name() + ":" + sumPiece.get(game.player1()) + " pieces");
System.out.println(game.player2().name() + ":" + sumPiece.get(game.player2()) + " pieces");
return false;
case 6: // skip
game.skip(player);
System.out.println("Skip");
return false;
case 7: // end
game.end();
System.out.println("The Game is ended.");
return true;
}
System.out.println("Input WRONG, Please choose again:");
}
return false;
}
- 打印記錄:
/**
* after the game is ended, to print both players' records of the game.
* @param game the object of the game
* @param player1 the object of the first hand player
* @param player2 the object of the later hand player
*/
private static void printRecord(Game game, Player player1, Player player2) {
System.out.println("\nAll of the Actions Record are followed.");
// get the record of the actions
List<Action> actions1 = player1.actions();
List<Action> actions2 = player2.actions();
System.out.println("\n" + player1.name() + "'s Actions:");
// print their action types
for (int i = 0; i < actions1.size(); i++) {
if (actions1.get(i) != null)
System.out.println(i + ": " + actions1.get(i).actionType());
}
System.out.println("\n" + player2.name() + "'s Actions:");
for (int i = 0; i < actions2.size(); i++) {
if (actions2.get(i) != null)
System.out.println(i + ": " + actions2.get(i).actionType());
}
}
3.3.3 ADT和主程序的測試方案
分別測試國際象棋和圍棋,測試類分別爲ChessTest和GoTest。由於國際象棋的測試比圍棋困難,約束也比圍棋多,因此設計ChessTest後進行一定更改就可以測試圍棋。以下以chess爲例,設計ADT測試方案。
3.3.3.1 testInit()
測試初始化遊戲的操作:新建指定類型的game、board,新建指定名字的player,以及新建piece並將棋子賦予player。
測試game、player、piece均不爲空。
@Test
public void testInit() {
final Game game = Game.newGame("chess");
assertNotEquals(null, game);
final Player player1 = new Player(game, "p1", true);
assertNotEquals(null, player1);
final Player player2 = new Player(game, "p2", false);
assertNotEquals(null, player2);
assertEquals(true, game.setPlayers(player1, player2));
player1.pieces = game.pieces(true);
assertNotEquals(Collections.EMPTY_SET, player1.pieces());
player2.pieces = game.pieces(false);
assertNotEquals(Collections.EMPTY_SET, player2.pieces());
}
3.3.3.2 testPiece()
測試棋子是否被新建,以及是否賦予玩家、初始化位置。
@Test
public void testPiece() {
// init
final Game game = Game.newGame("chess");
final Player player1 = new Player(game, "p1", true);
final Player player2 = new Player(game, "p2", false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// Piece.modifyAsPosition
for (Piece piece : player1.pieces()) {
assertNotNull(piece.position());
assertNotNull(piece.position().piece());
assertSame(piece, piece.position().piece());
}
}
3.3.3.3 testBoard
測試棋盤的方法。
- printBoard()打印出棋盤;
- positionXY()返回的Position對象不爲空,並且當位置有棋子是判斷棋子的名字是否和期望一樣;
- pieceXY()返回的棋名字一樣;
- XYisFree()分別測試有棋子和沒棋子的情況
@Test
public void testBoard() {
// init
final Game game = Game.newGame("chess");
final Player player1 = new Player(game, "p1", true);
final Player player2 = new Player(game, "p2", false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// Board.printBoard()
assertTrue(game.move(player1, game.board().positionXY(0, 1), game.board().positionXY(0, 3)));
assertTrue(game.capture(player2, game.board().positionXY(0, 6), game.board().positionXY(0, 3)));
game.board().printBoard();
// Board.PositionXY(x, y)
assertNotNull(game.board().positionXY(0, 1));
assertNotNull(game.board().positionXY(4, 6));
assertEquals("BP4", game.board().positionXY(4, 6).piece().name());
assertNull(game.board().positionXY(3, 3).piece());
// Board.pieceXY(x, y)
assertEquals("WR0", game.board().pieceXY(0, 0).name());
assertEquals("BP5", game.board().pieceXY(5, 6).name());
// Board.XYisFree(x, y)
assertEquals(null, game.board().XYisFree(4, 5));
assertEquals(player1, game.board().XYisFree(1, 1));
assertEquals(player2, game.board().XYisFree(7, 6));
}
3.3.3.4 testPosition()
測試位置的方法。
- 測試位置的棋子的名字;
- 測試查看橫縱座標、所屬玩家(當無棋子時,可能爲空);
- 測試更改position的piece
@Test
public void testPosition() {
// init
final Game game = Game.newGame("chess");
final Player player1 = new Player(game, "p1", true);
final Player player2 = new Player(game, "p2", false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// Position.piece()
assertEquals("BQ0", game.board().positionXY(3, 7).piece().name());
assertEquals("WK0", game.board().positionXY(4, 0).piece().name());
// Position.x()
assertEquals(4, game.board().positionXY(4, 0).x());
// Position.y()
assertEquals(5, game.board().positionXY(2, 5).y());
// Position.player()
assertEquals(player2, game.board().positionXY(6, 6).player());
// Position.modify
assertEquals(player1, game.board().XYisFree(1, 1));
assertEquals(true, game.board().positionXY(1, 1).modifyPieceAs(null));
assertNull(game.board().XYisFree(1, 1));
}
3.3.3.5 testPlayer()
測試玩家。
- 測試雙方先後手的標籤;
- 測試player所屬遊戲;
- 測試player擁有的所有pieces,是否在對應座標的棋盤位置上存在;
@Test
public void testPlayer() {
// init
final Game game = Game.newGame("chess");
final Player player1 = new Player(game, "p1", true);
final Player player2 = new Player(game, "p2", false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// Player.isFirst()
assertEquals(true, player1.isFirst());
assertEquals(false, player2.isFirst());
// Player.game()
assertEquals(game, player2.game());
// Player.pieces()
for (int i = 0; i < game.board().boardLength(); i++) {
for (int j = 0; j < game.board().boardLength(); j++) {
if (game.board().pieceXY(i, j) != null)
assertEquals(true, player1.pieces().contains(game.board().pieceXY(i, j))
|| player2.pieces().contains(game.board().pieceXY(i, j)));
}
}
}
3.3.3.6 testPut()
測試放棋操作。
- 當選定棋子在棋盤上/不在棋盤上時;
- 當棋子放的位置有/無棋子;
@Test
public void testPut() {
// init
final Game game = Game.newGame("chess");
final Player player1 = new Player(game, "p1", true);
final Player player2 = new Player(game, "p2", false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// put
assertEquals(false, game.put(player1, player1.findPieceByName("WP0"), game.board().positionXY(0, 1)));
assertEquals(false, game.put(player2, player2.findPieceByName("BN1"), game.board().positionXY(5, 4)));
// After capture
assertTrue(game.capture(player2, game.board().positionXY(0, 6), game.board().positionXY(0, 1)));
assertTrue(game.put(player1, player1.findPieceByName("WP0"), game.board().positionXY(0, 6)));
}
3.3.3.7 testMove()
測試移動棋子操作。
- 選定格的棋子是否爲本方;
- 選定格的棋子是否存在
- 目標格的棋子是否爲空;
- 移動之後選定格格子是否爲空,而目標格是否不空且爲之前選定格的棋子;
@Test
public void testMove() {
// init
final Game game = Game.newGame("chess");
final Player player1 = new Player(game, "p1", true);
final Player player2 = new Player(game, "p2", false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// move
assertEquals(true, game.move(player1, game.board().positionXY(0, 1), game.board().positionXY(0, 3)));
assertNull(game.board().positionXY(0, 1).piece());
assertNotNull(game.board().positionXY(0, 3).piece());
assertEquals(false, game.move(player1, game.board().positionXY(0, 1), game.board().positionXY(0, 4)));
assertEquals(false, game.move(player2, game.board().positionXY(6, 0), game.board().positionXY(5, 2)));
assertEquals(true, game.move(player1, game.board().positionXY(1, 0), game.board().positionXY(2, 2)));
assertEquals(false, game.move(player1, game.board().positionXY(1, 0), game.board().positionXY(0, 2)));
assertNull(game.board().positionXY(0, 2).piece());
assertNull(game.board().positionXY(1, 0).piece());
assertEquals(false, game.move(player2, game.board().positionXY(3, 7), game.board().positionXY(4, 7)));
assertEquals(false, game.move(player1, game.board().positionXY(2, 2), game.board().positionXY(4, 1)));
assertNotNull(game.board().positionXY(4, 1).piece());
assertNotNull(game.board().positionXY(2, 2).piece());
assertEquals(true, game.move(player2, game.board().positionXY(0, 6), game.board().positionXY(0, 1)));
assertEquals(player2, game.board().positionXY(0, 1).piece().player());
assertEquals("BP0", game.board().positionXY(0, 1).piece().name());
assertEquals(player1, game.board().positionXY(0, 3).piece().player());
// sumPiece
assertEquals(16, player2.sumPiece());
}
3.3.3.8 testCaptureAndPut()
結合put操作測試capture喫子操作。
- 喫子的起始格是否爲本方;
- 喫子的起始格是否爲空;
- 喫子的目標格是否爲對方;
- 喫子的目標格是否爲空;
- 查看喫子之後兩個格子的棋子名字是否爲期望;
@Test
public void testCaptureAndPut() {
// init
final Game game = Game.newGame("chess");
final Player player1 = new Player(game, "p1", true);
final Player player2 = new Player(game, "p2", false);
game.setPlayers(player1, player2);
player1.pieces = game.pieces(true);
player2.pieces = game.pieces(false);
// capture
assertEquals(true, game.capture(player1, game.board().positionXY(0, 1), game.board().positionXY(0, 6)));
assertEquals(false, game.capture(player1, game.board().positionXY(1, 1), game.board().positionXY(2, 1)));
assertEquals(false, game.capture(player1, game.board().positionXY(1, 1), game.board().positionXY(1, 3)));
assertEquals(true, game.capture(player1, game.board().positionXY(0, 6), game.board().positionXY(1, 6)));
assertEquals(false, game.capture(player1, game.board().positionXY(1, 1), game.board().positionXY(1, 6)));
assertEquals("WP0", game.board().pieceXY(1, 6).name());
assertSame(player1.findPieceByName("WP0"), game.board().pieceXY(1, 6));
assertEquals(true, game.capture(player1, game.board().positionXY(2, 1), game.board().positionXY(2, 6)));
assertEquals(13, player2.sumPiece());
// put
assertEquals("BP1", player2.findPieceByName("BP1").name());
assertNull(game.board().positionXY(0, 4).piece());
assertTrue(game.put(player2, player2.findPieceByName("BP0"), game.board().positionXY(0, 4)));
assertFalse(game.put(player2, player2.findPieceByName("BP1"), game.board().positionXY(0, 4)));
assertFalse(game.put(player1, player2.findPieceByName("BP1"), game.board().positionXY(0, 4)));
assertFalse(game.put(player2, player2.findPieceByName("BP0"), game.board().positionXY(0, 4)));
assertNull(player1.findPieceByName("BP1"));
assertFalse(game.put(player2, player1.findPieceByName("BP1"), game.board().positionXY(1, 4)));
assertNull(game.board().positionXY(8, -1));
assertFalse(game.put(player2, player2.findPieceByName("BP2"), game.board().positionXY(8, -1)));
}
3.3.3.9 ChessTest測試結果
3.3.3.10 GoTest測試結果
4 實驗進度記錄
日期 | 時間段 | 計劃任務 | 實際完成情況 |
---|---|---|---|
2020-03-09 | 晚上 | 初始化項目 | 完成 |
2020-03-10 | 中午 | Problem1 3.1.1-3.1.2 | 完成 |
2020-03-10 | 晚上 | Problem1 3.1.3.1 Edge Graph | 完成 |
2020-03-11 | 晚上 | Problem1 3.1.3.2 Vertex Graph | 完成 |
2020-03-12 | 下午 | 通過Graph Instance Test | 完成 |
2020-03-12 | 晚上 | Problem1 3.1.5 Graph Poet | 完成 |
2020-03-13 | 上午 | Problem1 3.1.5 Test | 完成 |
2020-03-13 | 上午 | Problem2 3.2整體完成 | 完成 |
2020-03-13 | 晚上 | Problem3 設計框架 寫AF&RI | 完成 |
2020-03-14 | 上午 | Problem3 完成框架 | 完成 |
2020-03-14 | 下午 | 實現Action接口、Player、Board、Position、Piece具體功能 | 完成 |
2020-03-14 | 晚上 | 完善上述功能,修改bug | 完成 |
2020-03-15 | 上午 | 實現Game接口、chessAction、goAction功能 | 完成 |
2020-03-15 | 晚上 | 實現chessGame、goGame功能,調試測試用例 | 完成 |
2020-03-16 | 晚上 | 通過chessGame測試 | 完成 |
2020-03-17 | 下午 | 通過goGame測試 | 完成 |
2020-03-17 | 晚上 | 驗收完成 | 完成 |
本項目於3.17日實驗課驗收,請放心參考
參考時文中有給出一些建議,請查看
基本更新完成