diff --git a/out/production/sp21-cs242-assignment1/CardManager.class b/out/production/sp21-cs242-assignment1/CardManager.class
index 688ff8e76e90e2dbfd7b94fe66d7016924b47aa2..bc11383ba8d1990ad64464166c8f003d107eeb1e 100644
Binary files a/out/production/sp21-cs242-assignment1/CardManager.class and b/out/production/sp21-cs242-assignment1/CardManager.class differ
diff --git a/out/production/sp21-cs242-assignment1/CmdUI.class b/out/production/sp21-cs242-assignment1/CmdUI.class
index 77e01a48c317fef0d98274cbf4e604c9d13d93e3..ed707a5d848bfe36341a91d099a66097d3c0b42e 100644
Binary files a/out/production/sp21-cs242-assignment1/CmdUI.class and b/out/production/sp21-cs242-assignment1/CmdUI.class differ
diff --git a/out/production/sp21-cs242-assignment1/Player.class b/out/production/sp21-cs242-assignment1/Player.class
index c564c36c856a9e56c1b305b4a260500413ed55c3..d3baf62c663311ac8df80a26813c027b722e1778 100644
Binary files a/out/production/sp21-cs242-assignment1/Player.class and b/out/production/sp21-cs242-assignment1/Player.class differ
diff --git a/out/production/sp21-cs242-assignment1/RuleController.class b/out/production/sp21-cs242-assignment1/RuleController.class
index de13870341e7e8e6003d598eeecd6ecb1d335618..cfcfdeff757385315eabfaa03c3974a8e2ef7467 100644
Binary files a/out/production/sp21-cs242-assignment1/RuleController.class and b/out/production/sp21-cs242-assignment1/RuleController.class differ
diff --git a/out/test/sp21-cs242-assignment1/CardParserTest.class b/out/test/sp21-cs242-assignment1/CardParserTest.class
new file mode 100644
index 0000000000000000000000000000000000000000..794bde9fda9c4cea84edd5984070d1824519a500
Binary files /dev/null and b/out/test/sp21-cs242-assignment1/CardParserTest.class differ
diff --git a/out/test/sp21-cs242-assignment1/RuleControllerTest.class b/out/test/sp21-cs242-assignment1/RuleControllerTest.class
new file mode 100644
index 0000000000000000000000000000000000000000..da9badcd08ce0b4609ba10d8fa911d6f7329d88c
Binary files /dev/null and b/out/test/sp21-cs242-assignment1/RuleControllerTest.class differ
diff --git a/sp21-cs242-assignment1.iml b/sp21-cs242-assignment1.iml
index 1be80785894dc1b3060b760ebcc1ffc2e9ab188f..98ef08307774761d459a21dc4a8deea06f268abe 100644
--- a/sp21-cs242-assignment1.iml
+++ b/sp21-cs242-assignment1.iml
@@ -3,10 +3,45 @@
   <component name="NewModuleRootManager" inherit-compiler-output="true">
     <exclude-output />
     <content url="file://$MODULE_DIR$">
-      <sourceFolder url="file://$MODULE_DIR$/src/Test" type="java-test-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/Test" isTestSource="true" />
       <sourceFolder url="file://$MODULE_DIR$/src/UNO" isTestSource="false" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://$USER_HOME$/.jdks/external/junit-4.13.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="file://$USER_HOME$/.jdks/external" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+        <jarDirectory url="file://$USER_HOME$/.jdks/external" recursive="false" />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="JUnit5.4">
+        <CLASSES>
+          <root url="jar://$USER_HOME$/.jdks/external/junit-jupiter-5.4.2.jar!/" />
+          <root url="jar://$USER_HOME$/.jdks/external/junit-jupiter-api-5.4.2.jar!/" />
+          <root url="jar://$USER_HOME$/.jdks/external/apiguardian-api-1.0.0.jar!/" />
+          <root url="jar://$USER_HOME$/.jdks/external/opentest4j-1.1.1.jar!/" />
+          <root url="jar://$USER_HOME$/.jdks/external/junit-platform-commons-1.4.2.jar!/" />
+          <root url="jar://$USER_HOME$/.jdks/external/junit-jupiter-params-5.4.2.jar!/" />
+          <root url="jar://$USER_HOME$/.jdks/external/junit-jupiter-engine-5.4.2.jar!/" />
+          <root url="jar://$USER_HOME$/.jdks/external/junit-platform-engine-1.4.2.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
   </component>
 </module>
\ No newline at end of file
diff --git a/src/Test/CardParserTest.java b/src/Test/CardParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c4a698c7e06566222d006246150aec3cc378757
--- /dev/null
+++ b/src/Test/CardParserTest.java
@@ -0,0 +1,18 @@
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class CardParserTest {
+
+    @Test
+    void parseCardID() {
+    }
+
+    @Test
+    void parseCardDescription() {
+    }
+
+    @Test
+    void isWildCard() {
+    }
+}
\ No newline at end of file
diff --git a/src/Test/RuleControllerTest.java b/src/Test/RuleControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c2820484a4f1ac4a184011f438db40821811f5e
--- /dev/null
+++ b/src/Test/RuleControllerTest.java
@@ -0,0 +1,190 @@
+import com.sun.source.tree.UsesTree;
+import org.junit.Rule;
+import org.junit.jupiter.api.Test;
+
+import javax.print.attribute.standard.Finishings;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.Random;
+
+
+// Test for judging whether a play is legal
+
+class RuleControllerTest {
+    private static CardParser parser = new CardParser();
+
+    @Test  // if current allowed color is red, player with no red cards can use wild Draw 4
+    void test_Player_Red_N_WildDraw4_Y_UseWildDraw4() throws Exception {
+        Player player = player_Red_N_WildDraw4_Y();
+        RuleController ruler = new RuleController();
+
+        ruler.setNextPlayerSkiplevel(0);
+        ruler.setAllowedColor("red");
+        ruler.setAllowedSymbol("skip");
+        assert(ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("red");
+        ruler.setAllowedSymbol("reverse");
+        assert(ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("blue");
+        ruler.setAllowedSymbol("skip");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("blue");
+        ruler.setAllowedSymbol("reverse");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("green");
+        ruler.setAllowedSymbol("skip");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("green");
+        ruler.setAllowedSymbol("reverse");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("yellow");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+
+
+    }
+
+    @Test // test player with all colors cannot use wildDraw4 anyway
+    void test_Player_AllColors_CannotUseWildDraw4() throws Exception {
+        Player player = player_AllColor_Y_WildDraw4_Y();
+        RuleController ruler = new RuleController();
+
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setNextPlayerSkiplevel(0);
+        ruler.setAllowedColor("red");
+        ruler.setAllowedSymbol("skip");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("red");
+        ruler.setAllowedSymbol("reverse");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("blue");
+        ruler.setAllowedSymbol("skip");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("blue");
+        ruler.setAllowedSymbol("reverse");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("green");
+        ruler.setAllowedSymbol("skip");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("green");
+        ruler.setAllowedSymbol("reverse");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+        ruler.setAllowedColor("yellow");
+        assert(!ruler.isValidPlay(player, 105, false));
+
+
+    }
+
+
+
+//    @Test // Last round a red 8 played
+//    void testRed8() throws Exception {
+//        RuleController ruler = new RuleController();
+//
+//    }
+
+
+    /**
+     *
+     */
+    void testInitializer() {}
+
+    /**
+     *  extract all the cards that the ruler think as legal
+     */
+    private ArrayList<Integer> getAllLegalCardsByRuler(RuleController ruler, ArrayList<Integer> cards) {
+        ArrayList<Integer> validList = new ArrayList<>();
+        for (int cardID : cards) {
+            if (ruler.isValidPlay(null, cardID, false)) {
+                validList.add(cardID);
+            }
+        }
+        return validList;
+    }
+
+    /**
+     * extract all the cards that are indeed legal (ground true generator)
+     */
+    private ArrayList<Integer> groundTruthGenerator(RuleController ruler,
+                                                    ArrayList<Integer> cards,
+                                                    String color, String number,
+                                                    String symbol) {
+        for (int cardID : cards) {
+            String cardDescription = parser.parseCardID(cardID);
+            String[] result = parser.parseCardDescription(cardDescription);
+            String cardCol = result[0];
+            String cardType = result[1];
+            String cardContent = result[2];
+
+            if (ruler.getNextPlayerSkiplevel() == 3) {
+                continue; // skip card played. any card will be illegal
+            } else if (ruler.getNextPlayerSkiplevel() == 2) {
+                // only wildDraw4 if allowed
+                if (cardContent.equals("wildDraw4") || cardContent.equals("wildDraw2"));
+            } else if (ruler.getNextPlayerSkiplevel() == 1) {
+                                        // both draw 2 and wild draw 4 are allowed
+            } else {
+                // cards with any one of color, number or symbol matched are playable
+
+            }
+        }
+//        if (ruler.getSkip)
+        return null;
+    }
+
+    /**
+     * construct a player who has no red cards, but a wildDraw4 card
+     * This player should be able to play wildDraw4 card if current allowed color is red
+     * @return
+     */
+    private Player player_Red_N_WildDraw4_Y() {
+        Player player = new Player(0, null);
+        player.addOneCard(26); // green 1
+        player.addOneCard(51); // blue 1
+        player.addOneCard(76); // yellow 1
+        player.addOneCard(101); // wild
+        player.addOneCard(105); // wildDraw 4
+        return player;
+    }
+
+    /**
+     * Construct a player who owns all colors, and also a wildDraw4 card
+     * This player should not be able to play wildDraw4 card.
+     * @return
+     */
+    private Player player_AllColor_Y_WildDraw4_Y() {
+        Player player = new Player(0, null);
+        player.addOneCard(1); // red 1
+        player.addOneCard(26); // green 1
+        player.addOneCard(51); // blue 1
+        player.addOneCard(76); // yellow 1
+        player.addOneCard(101); // wild
+        player.addOneCard(105); // wildDraw 4
+        return player;
+    }
+
+    /**
+     * Construct a magic player who own ALL CARDS
+     * This player should not be able to play wildDraw4 card
+     */
+    private Player playerAllCards() {
+        Player player = new Player(0, null);
+        for (int i = 1; i <= 108; i++) {
+            player.addOneCard(i);
+        }
+        return player;
+    }
+}
\ No newline at end of file
diff --git a/src/UNO/CardManager.java b/src/UNO/CardManager.java
index 5d14b79141dd02cf6eeeeb7818cb3f8c6279528b..4dcf7bf573898c429317ecbd1b18729e98f65de7 100644
--- a/src/UNO/CardManager.java
+++ b/src/UNO/CardManager.java
@@ -83,5 +83,10 @@ public class CardManager {
      */
     public int numLeftDiscardPile() { return discardPile.size(); }
 
+    /**
+     * get all the cards currently in card pile as a Array list
+     */
+    public ArrayList<Integer> getCardPile() { return cardPile; }
+
 }
 
diff --git a/src/UNO/CmdUI.java b/src/UNO/CmdUI.java
index 9840bb7978f0e7e577c8d7c5ef848f4255475378..75d95c81f7a1498cf9dad8ce565c7fddfb66bade 100644
--- a/src/UNO/CmdUI.java
+++ b/src/UNO/CmdUI.java
@@ -8,9 +8,10 @@ public class CmdUI {
 
     public CmdUI(Player p) {
         player = p;
-        parser = player.parser;
         gameController = player.gameController;
-        ruler = gameController.ruler;
+        parser = (gameController != null) ? player.parser : null;
+
+        ruler = (gameController != null) ? gameController.ruler : null;
 
     }
 
diff --git a/src/UNO/Game.java b/src/UNO/Game.java
index 59c4676261c343600184c771f2e75d059d619818..9009612e6474858a406bc2728df8b87ba207376b 100644
--- a/src/UNO/Game.java
+++ b/src/UNO/Game.java
@@ -43,7 +43,7 @@ public class Game {
      */
     private void decideFirstPlayerID(int playerNum) {
         Random rand = new Random();
-        currentPlayerID = rand.nextInt(playerNum); // -1 to convert to index
+        currentPlayerID = rand.nextInt(playerNum);
     }
 
 
diff --git a/src/UNO/Player.java b/src/UNO/Player.java
index a8756a233349de1a0c5b14bdff36c2902cf14cfc..cbf50ab50ec8a512853f5c4067a31c4593facc3f 100644
--- a/src/UNO/Player.java
+++ b/src/UNO/Player.java
@@ -15,8 +15,8 @@ public class Player {
         playerID = ID;
         cards = new ArrayList<Integer>();
         gameController = game;
-        ruler = game.ruler;
-        parser = RuleController.parser;
+        ruler = (game != null) ? game.ruler : null;  // for testing purpose
+        parser = (game != null) ? RuleController.parser : null;
         prompterCmd = new CmdUI(this);
         prompterGUI = null;
     }
@@ -137,6 +137,11 @@ public class Player {
 
     }
 
-
+    /**
+     * For Test only. Add one card with Given card ID to the player's cards
+     */
+    public void addOneCard(int cardID) {
+        cards.add(cardID);
+    }
 
 }
\ No newline at end of file
diff --git a/src/UNO/RuleController.java b/src/UNO/RuleController.java
index 06d923b0965462749119f2f2d6124ea44858d93e..2c2a08e1b2ff61a101949a48cafbf9ae02bb274b 100644
--- a/src/UNO/RuleController.java
+++ b/src/UNO/RuleController.java
@@ -1,4 +1,5 @@
 import java.util.ArrayList;
+import java.util.Random;
 
 public class RuleController {
     public static CardParser parser = new CardParser();
@@ -6,13 +7,32 @@ public class RuleController {
     /** IMPORTANT
      * As long as one of the following three is matchable, the play will be considered as legal
      */
-    private String currentMatchableColor = "all";
-    private String currentMatchableNumber = "all";
-    private String currentMatchableSymbol = "all";
+    private String currentMatchableColor;
+    private String currentMatchableNumber;
+    private String currentMatchableSymbol = "all"; // all symbols can be played in the first round
     private int nextPlayerSkipLevel = 0;
     private int cumulativePenaltyDraw = 0;
     private boolean isClockWise = true;   // if clockwise, next player will be the element in player list
 
+    public RuleController() {
+        // initialize first matched color and number
+        boolean chosen = false;
+        while (!chosen) {
+            Random rand = new Random();
+            int cardID = rand.nextInt(100) + 1; // generate a number from 1 - 100. these are colored cards
+            String desc = parser.parseCardID(cardID);
+            String[] result = parser.parseCardDescription(desc);
+            String color = result[0];
+            String type = result[1];
+            String content = result[2];
+
+            if (type.equals("num")) {
+                currentMatchableColor = color;
+                currentMatchableNumber = content;
+                chosen = true;
+            }
+        }
+    }
 
     /**
      * judge whether a pending player should be skipped
@@ -49,7 +69,7 @@ public class RuleController {
      * @return whether the play is valid
      */
     public boolean isValidPlay(Player player, int cardID, boolean updateIfValid) {
-
+        assert(player != null);
         String cardDescription = parser.parseCardID(cardID);
         String[] result = parser.parseCardDescription(cardDescription);
         String color = result[0];
@@ -57,31 +77,37 @@ public class RuleController {
         String content = result[2];
 
         // pending skip
-        if (nextPlayerSkipLevel == 4) {
+        if (nextPlayerSkipLevel == 3) {
             return false;
-        } else if (nextPlayerSkipLevel == 3) {
-            return content.equals("wildDraw4"); // not sure - check draw 4 legal?
         } else if (nextPlayerSkipLevel == 2) {
-            return content.equals("wildDraw4") || content.equals("draw2");
+            if (content.equals("wildDraw4")) {
+                return checkDraw4IsLegal(player);
+            }
+        } else if (nextPlayerSkipLevel == 1) {
+            if (content.equals("wildDraw4")) {
+                return checkDraw4IsLegal(player);
+            }
+            return content.equals("draw2"); // draw 2 is always allowed in this case
         }
 
         boolean valid = false;
-        // if not skipped, first consider wild
+        // if not skipped, first consider wild card
         if (content.equals("wild")) {
             valid = true;  // can be used unconditionally
         }
 
-        // then consider wildDraw4
-        if (content.equals("wildDraw4")) {
-            valid = checkDraw4IsLegal(player);
-        }
-
         // then other colored cards
         if (checkAttrMatch(currentMatchableColor, color))
             valid = true;
         if (checkAttrMatch(currentMatchableNumber, content) || checkAttrMatch(currentMatchableSymbol, content))
             valid = true;
 
+        // check wildDraw4 at last
+        if (content.equals("wildDraw4")) {
+            valid = checkDraw4IsLegal(player);
+        }
+
+
         if (valid && updateIfValid) {
             updateRule(color, type, content);
         }
@@ -143,12 +169,17 @@ public class RuleController {
      * Check whether a player have currently matchable **color** when attempting to play wild draw 4
      */
     private boolean checkDraw4IsLegal(Player player) {
+
         ArrayList<Integer> cards = player.getCards();
         for (int i = 0; i < cards.size(); i++) {
             int cardID = cards.get(i);
             String cardDescription = parser.parseCardID(cardID);
             String color = parser.parseCardDescription(cardDescription)[0]; // color of wild cards are NA
-            if (color.equals(currentMatchableNumber)) return false;
+            System.out.println(color);
+
+            if (color.equals(currentMatchableColor)) {
+                return false;
+            }
         }
         return true;
     }
@@ -246,7 +277,6 @@ public class RuleController {
         System.out.println("There are " + gameController.gameCardManager.numCardLeft() + " cards in the card pile.");
         System.out.println("There are " + gameController.gameCardManager.numLeftDiscardPile() + " cards in the discard pile.");
     }
-    
 
 
 }