哈工大2020軟件構造Lab2實驗報告

本項目於3.17日實驗課驗收,請放心參考

參考時文中有給出一些建議,請查看

基本更新完成

HIT

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 簡單圖測試

  • 根據題目中的社交網絡圖:

    分別測試:
  1. Rachel和Ross距離是1,Rachel和Ben距離是2
  2. Rachel和Rachel距離是0
  3. Rachel和Kramer距離是-1

3.2.4.2 複雜圖測試

  • 設計10個點、10條邊的社交網絡圖:

    分別測試:
  1. AE距離2,AD距離1,AG距離3,BF距離3,DF距離2,HJ距離2
  2. II距離0
  3. 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)。實現功能:

  1. 選擇遊戲類型:創建Game、Board
  2. 輸入玩家名字:創建Player、Piece,其中Piece屬於Player
  3. 開始遊戲,輪流選擇功能
  4. 放棋:給定player、piece、x、y
  5. 移動棋(chess):給定player、piece、x1、y1、x2、y2
  6. 提子(go):給定player、x、y
  7. 喫子(chess):給定player、x1、y1、x2、y2
  8. 查詢某個位置佔用情況:給定x、y
  9. 計算兩個玩家分別的棋子總數
  10. 跳過
  11. 結束:輸入“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個步驟:

  1. 新建遊戲和玩家
  2. 選擇遊戲功能並執行
  3. 打印遊戲記錄

3.3.2.1 遊戲流程圖

3.3.2.2 程序演示

  • 初始化:
    • 給出提示語,輸入遊戲類型、玩家雙方名字。若創建成功則顯示“Game Start”。
  • 遊戲功能執行:
    • 請用戶選擇玩家(輸入名字),給出功能菜單,在用戶選擇後要求用戶輸入參數,執行功能,返回結果(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

測試棋盤的方法。

  1. printBoard()打印出棋盤;
  2. positionXY()返回的Position對象不爲空,並且當位置有棋子是判斷棋子的名字是否和期望一樣;
  3. pieceXY()返回的棋名字一樣;
  4. 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()

測試位置的方法。

  1. 測試位置的棋子的名字;
  2. 測試查看橫縱座標、所屬玩家(當無棋子時,可能爲空);
  3. 測試更改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()

測試玩家。

  1. 測試雙方先後手的標籤;
  2. 測試player所屬遊戲;
  3. 測試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()

測試放棋操作。

  1. 當選定棋子在棋盤上/不在棋盤上時;
  2. 當棋子放的位置有/無棋子;
@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()

測試移動棋子操作。

  1. 選定格的棋子是否爲本方;
  2. 選定格的棋子是否存在
  3. 目標格的棋子是否爲空;
  4. 移動之後選定格格子是否爲空,而目標格是否不空且爲之前選定格的棋子;
@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喫子操作。

  1. 喫子的起始格是否爲本方;
  2. 喫子的起始格是否爲空;
  3. 喫子的目標格是否爲對方;
  4. 喫子的目標格是否爲空;
  5. 查看喫子之後兩個格子的棋子名字是否爲期望;
    @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日實驗課驗收,請放心參考

參考時文中有給出一些建議,請查看

基本更新完成

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章