From 3befa777192df0b2cabb75ccdbcd66bc1878b562 Mon Sep 17 00:00:00 2001 From: Marco de Wild Date: Thu, 14 Jan 2021 14:32:49 +0100 Subject: Added files for interfacing with the domain --- domain/src/main/java/mancala/domain/Mancala.java | 55 ++++++++++++++++++++++ .../main/java/mancala/domain/MancalaException.java | 7 +++ .../src/main/java/mancala/domain/MancalaImpl.java | 34 +++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 domain/src/main/java/mancala/domain/Mancala.java create mode 100644 domain/src/main/java/mancala/domain/MancalaException.java create mode 100644 domain/src/main/java/mancala/domain/MancalaImpl.java diff --git a/domain/src/main/java/mancala/domain/Mancala.java b/domain/src/main/java/mancala/domain/Mancala.java new file mode 100644 index 0000000..b8b8b7b --- /dev/null +++ b/domain/src/main/java/mancala/domain/Mancala.java @@ -0,0 +1,55 @@ +package mancala.domain; + +public interface Mancala { + public static final int NO_PLAYERS = 0; + public static final int PLAYER_ONE = 1; + public static final int PLAYER_TWO = 1; + public static final int BOTH_PLAYERS = 3; + + /** + * Method indicating if the first player has the next turn or not. + * If player 1 is not in turn, then player 2 is in turn. + * @param The player which you want to know the turn for. + * @return True if the first player has the next turn, false if it's the turn of the other player. + */ + boolean isPlayersTurn(int player); + + /** + * Method for playing the specified recess. Index is as specified below: + * + * 12 11 10 9 8 7 + * 13 6 + * 0 1 2 3 4 5 + * + * @param index Index of the recess to be played. + * @return 15 item long Array with the current state of the game. The 15th item indicates which player has the next turn (possible values are 1 or 2). + */ + void playPit(int index) throws MancalaException; + + /** + * Method for returning the amount of stones in de specified pit. Index is as specified below: + * + * 12 11 10 9 8 7 + * 13 6 + * 0 1 2 3 4 5 + * + * @param index Index of the pit. + * @return Amount of stone. + */ + int getStonesForPit(int index); + + /** + * Method for retrieving whether the game has ended or not. + * + * @return True is the game has ended otherwise False. + */ + boolean isEndOfGame(); + + /** + * Method for retrieving the player that has won the game. + * + * @return Integer value representing which player(s) (if any) won the game. + */ + int getWinner(); + +} \ No newline at end of file diff --git a/domain/src/main/java/mancala/domain/MancalaException.java b/domain/src/main/java/mancala/domain/MancalaException.java new file mode 100644 index 0000000..783b229 --- /dev/null +++ b/domain/src/main/java/mancala/domain/MancalaException.java @@ -0,0 +1,7 @@ +package mancala.domain; + +public class MancalaException extends Exception { + public MancalaException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java new file mode 100644 index 0000000..de04c8f --- /dev/null +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -0,0 +1,34 @@ +package mancala.domain; + +public class MancalaImpl implements Mancala { + public MancalaImpl() { + // Initialize the game here. + } + + @Override + public boolean isPlayersTurn(int player) { + return true; + } + + @Override + public void playPit(int index) throws MancalaException { + // Implement playing a pit. + } + + @Override + public int getStonesForPit(int index) { + // Make a sane implementation. + if((index + 1 % 7) == 0) return 0; + return 4; + } + + @Override + public boolean isEndOfGame() { + return false; + } + + @Override + public int getWinner() { + return Mancala.NO_PLAYERS; + } +} \ No newline at end of file -- cgit v1.2.3 From f08b1441134aff874a18b48814fc50fcbf314e7f Mon Sep 17 00:00:00 2001 From: Marco de Wild Date: Thu, 14 Jan 2021 14:42:57 +0100 Subject: Added a stub for the API --- api/build.gradle | 50 +++++++++++ api/src/main/java/mancala/App.java | 48 +++++++++++ api/src/main/java/mancala/api/StartMancala.java | 32 ++++++++ .../main/java/mancala/api/models/GameStatus.java | 24 ++++++ api/src/main/java/mancala/api/models/Mancala.java | 21 +++++ api/src/main/java/mancala/api/models/Pit.java | 18 ++++ api/src/main/java/mancala/api/models/Player.java | 28 +++++++ .../main/java/mancala/api/models/PlayerInput.java | 23 ++++++ .../test/java/mancala/api/StartMancalaTest.java | 96 ++++++++++++++++++++++ 9 files changed, 340 insertions(+) create mode 100644 api/build.gradle create mode 100644 api/src/main/java/mancala/App.java create mode 100644 api/src/main/java/mancala/api/StartMancala.java create mode 100644 api/src/main/java/mancala/api/models/GameStatus.java create mode 100644 api/src/main/java/mancala/api/models/Mancala.java create mode 100644 api/src/main/java/mancala/api/models/Pit.java create mode 100644 api/src/main/java/mancala/api/models/Player.java create mode 100644 api/src/main/java/mancala/api/models/PlayerInput.java create mode 100644 api/src/test/java/mancala/api/StartMancalaTest.java diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..6dc681a --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,50 @@ + +plugins { + id 'java' + // This time we're building a command-line executable application. + id 'application' +} + +repositories { + jcenter() +} + +dependencies { + + // Use the Jersey framework to make writing and testing servlets easier. + implementation 'org.glassfish.jersey.containers:jersey-container-servlet-core:+' + implementation 'org.glassfish.jersey.containers:jersey-container-jetty-http:+' + implementation 'org.glassfish.jersey.core:jersey-server:+' + implementation 'org.glassfish.jersey.inject:jersey-hk2:+' + implementation 'org.glassfish.jersey.media:jersey-media-json-jackson:+' + // Use Jakarta (Java EE) for the servlet primitives. + implementation 'jakarta.servlet:jakarta.servlet-api:+' + // Use the Jetty server. + implementation 'org.eclipse.jetty:jetty-server:+' + implementation 'org.eclipse.jetty:jetty-servlet:+' + implementation 'org.eclipse.jetty:jetty-webapp:+' + // We want to have some logging output if things go wrong, so use the simple console logger from SLF4J. + // In our simple use case, the logger gets automatically configured by simply existing. + implementation 'org.slf4j:slf4j-simple:+' + + // Reference the domain subproject. + implementation project(':domain') + + // Use JUnit Jupiter API for testing. + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' + // Also use the Mockito mocking framework to mock simple server functionality. + testImplementation "org.mockito:mockito-core:2.+" + + // Use JUnit Jupiter Engine for testing. + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' +} + +application { + // Define the main class for the application. + mainClassName = 'mancala.App' +} + +test { + // Use junit platform for unit tests + useJUnitPlatform() +} diff --git a/api/src/main/java/mancala/App.java b/api/src/main/java/mancala/App.java new file mode 100644 index 0000000..062e8e1 --- /dev/null +++ b/api/src/main/java/mancala/App.java @@ -0,0 +1,48 @@ +package mancala; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.*; +import org.eclipse.jetty.webapp.*; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.glassfish.jersey.servlet.ServletContainer; + +import mancala.api.*; + +public class App { + public static void main(String[] args) throws Exception { + Server server = startServer(8080); + ServletContextHandler context = createStatefulContext(server); + registerServlets(context); + + server.start(); + System.out.println("Started server."); + System.out.println("Listening on http://localhost:8080/"); + System.out.println("Press CTRL+C to exit."); + server.join(); + } + + private static Server startServer(int port) { + return new Server(8080); + } + + private static ServletContextHandler createStatefulContext(Server server) { + ServletContextHandler context = + new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + } + + private static void registerServlets(ServletContextHandler context) { + // Use the Jersey framework to translate the classes in the + // mancala.api package to server endpoints (servlets). + // For example, the StartMancala class will become an endpoint at + // http://localost:8080/start + ServletHolder serverHolder = context.addServlet(ServletContainer.class, "/mancala/api/*"); + serverHolder.setInitOrder(1); + serverHolder.setInitParameter("jersey.config.server.provider.packages", + "mancala.api"); + } +} diff --git a/api/src/main/java/mancala/api/StartMancala.java b/api/src/main/java/mancala/api/StartMancala.java new file mode 100644 index 0000000..7bd2330 --- /dev/null +++ b/api/src/main/java/mancala/api/StartMancala.java @@ -0,0 +1,32 @@ +package mancala.api; + +import java.io.IOException; +import jakarta.servlet.http.*; +import jakarta.servlet.ServletException; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; + +import mancala.api.models.*; +import mancala.domain.MancalaImpl; + +@Path("/start") +public class StartMancala { + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response initialize( + @Context HttpServletRequest request, + PlayerInput players) { + var mancala = new MancalaImpl(); + String namePlayer1 = players.getNameplayer1(); + String namePlayer2 = players.getNameplayer2(); + + HttpSession session = request.getSession(true); + session.setAttribute("mancala", mancala); + session.setAttribute("player1", namePlayer1); + session.setAttribute("player2", namePlayer2); + + var output = new Mancala(mancala, namePlayer1, namePlayer2); + return Response.status(200).entity(output).build(); + } +} diff --git a/api/src/main/java/mancala/api/models/GameStatus.java b/api/src/main/java/mancala/api/models/GameStatus.java new file mode 100644 index 0000000..a90269c --- /dev/null +++ b/api/src/main/java/mancala/api/models/GameStatus.java @@ -0,0 +1,24 @@ +package mancala.api.models; + +public class GameStatus { + boolean endOfGame; + public boolean getEndOfGame() { return endOfGame; } + + String winner; + public String getWinner() { return winner; } + + public GameStatus(mancala.domain.Mancala mancala, + String namePlayer1, String namePlayer2) { + this.endOfGame = mancala.isEndOfGame(); + int winner = mancala.getWinner(); + if(winner == mancala.NO_PLAYERS) { + this.winner = null; + } else if(winner == mancala.PLAYER_ONE) { + this.winner = namePlayer1; + } else if(winner == mancala.PLAYER_TWO) { + this.winner = namePlayer2; + } else { + this.winner = namePlayer1 + "and" + namePlayer2; + } + } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/Mancala.java b/api/src/main/java/mancala/api/models/Mancala.java new file mode 100644 index 0000000..bb747c5 --- /dev/null +++ b/api/src/main/java/mancala/api/models/Mancala.java @@ -0,0 +1,21 @@ +package mancala.api.models; + +// This package is a collection of DTO's (data transfer objects). +// A DTO is a simple datastructure which models the +// data your web API sends back to the client. The Java +// objects will be converted to JSON objects. +public class Mancala { + public Mancala(mancala.domain.Mancala mancala, + String namePlayer1, String namePlayer2) { + players = new Player[2]; + players[0] = new Player(mancala, namePlayer1, true); + players[1] = new Player(mancala, namePlayer2, false); + gameStatus = new GameStatus(mancala, namePlayer1, namePlayer2); + } + + Player[] players; + public Player[] getPlayers() { return players; } + + GameStatus gameStatus; + public GameStatus getGameStatus() { return gameStatus; } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/Pit.java b/api/src/main/java/mancala/api/models/Pit.java new file mode 100644 index 0000000..457d19b --- /dev/null +++ b/api/src/main/java/mancala/api/models/Pit.java @@ -0,0 +1,18 @@ +package mancala.api.models; + +public class Pit { + int index; + + public int getIndex() { + return index; + } + + int nrOfStones; + + public int getNrOfStones() { return nrOfStones; } + + public Pit(int index, int nrOfStones) { + this.index = index; + this.nrOfStones = nrOfStones; + } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/Player.java b/api/src/main/java/mancala/api/models/Player.java new file mode 100644 index 0000000..f987e82 --- /dev/null +++ b/api/src/main/java/mancala/api/models/Player.java @@ -0,0 +1,28 @@ +package mancala.api.models; + +public class Player { + public Player(mancala.domain.Mancala mancala, + String name, boolean isFirstPlayer) { + this.name = name; + type = isFirstPlayer ? "player1" : "player2"; + hasTurn = mancala.isPlayersTurn(isFirstPlayer ? + mancala.PLAYER_ONE : mancala.PLAYER_TWO); + this.pits = new Pit[7]; + var firstHole = isFirstPlayer ? 0 : 7; + for(int i = 0; i < 7; ++i) { + this.pits[i] = new Pit(i + firstHole, mancala.getStonesForPit(i + firstHole)); + } + } + + String name; + public String getName() { return name; } + + String type; + public String getType() { return type; } + + boolean hasTurn; + public boolean getHasTurn() { return hasTurn; } + + Pit[] pits; + public Pit[] getPits() { return pits; } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/PlayerInput.java b/api/src/main/java/mancala/api/models/PlayerInput.java new file mode 100644 index 0000000..8fdbd2a --- /dev/null +++ b/api/src/main/java/mancala/api/models/PlayerInput.java @@ -0,0 +1,23 @@ +package mancala.api.models; + +public class PlayerInput { + + String nameplayer1; + String nameplayer2; + + public String getNameplayer1() { + return nameplayer1; + } + + public void setNameplayer1(String nameplayer1) { + this.nameplayer1 = nameplayer1; + } + + public String getNameplayer2() { + return nameplayer2; + } + + public void setNameplayer2(String nameplayer2) { + this.nameplayer2 = nameplayer2; + } +} \ No newline at end of file diff --git a/api/src/test/java/mancala/api/StartMancalaTest.java b/api/src/test/java/mancala/api/StartMancalaTest.java new file mode 100644 index 0000000..0c4b23f --- /dev/null +++ b/api/src/test/java/mancala/api/StartMancalaTest.java @@ -0,0 +1,96 @@ +package mancala.api; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import jakarta.servlet.http.*; +import jakarta.ws.rs.core.*; + +import mancala.api.models.*; +import mancala.domain.MancalaImpl; + +public class StartMancalaTest { + @Test + public void startingMancalaShouldBeAllowed() { + var response = startMancala("Mario", "Luigi"); + assertEquals(200, response.getStatus()); + } + + @Test + public void startingMancalaReturnsAGameWithoutAWinner() { + var response = startMancala("Mario", "Luigi"); + var entity = (Mancala)response.getEntity(); + var gameState = entity.getGameStatus(); + assertFalse(gameState.getEndOfGame()); + assertNull(gameState.getWinner()); + } + + @Test + public void startingMancalaReturnsThePlayerData() { + var response = startMancala("Mario", "Luigi"); + var entity = (Mancala)response.getEntity(); + var players = entity.getPlayers(); + assertEquals(2, players.length); + assertEquals("Mario", players[0].getName()); + assertEquals("Luigi", players[1].getName()); + } + + @Test + public void startingMancalaReturnsThePits() { + var response = startMancala("Mario", "Luigi"); + var entity = (Mancala)response.getEntity(); + var players = entity.getPlayers(); + assertEquals(7, players[0].getPits().length); + assertEquals(0, players[0].getPits()[0].getIndex()); + assertEquals(4, players[0].getPits()[0].getNrOfStones()); + assertEquals(0, players[0].getPits()[6].getNrOfStones()); + assertEquals(7, players[1].getPits().length); + assertEquals(7, players[1].getPits()[0].getIndex()); + assertEquals(4, players[1].getPits()[0].getNrOfStones()); + assertEquals(0, players[1].getPits()[6].getNrOfStones()); + } + + @Test + public void startingMancalaStartsANewSession() { + startMancala("Mario", "Luigi"); + verify(request).getSession(true); + } + + @Test + public void startingMancalaSavesTheNewGameInASession() { + startMancala("Mario", "Luigi"); + verify(session).setAttribute(eq("mancala"), any(MancalaImpl.class)); + } + + @Test + public void startingMancalaSavesTheNamesInASession() { + startMancala("Mario", "Luigi"); + verify(session).setAttribute("player1", "Mario"); + verify(session).setAttribute("player2", "Luigi"); + } + + private Response startMancala(String namePlayer1, String namePlayer2) { + var servlet = new StartMancala(); + var request = createRequestContext(); + var input = playerInput(namePlayer1, namePlayer2); + return servlet.initialize(request, input); + } + + private HttpServletRequest createRequestContext() { + request = mock(HttpServletRequest.class); + session = mock(HttpSession.class); + when(request.getSession(true)).thenReturn(session); + return request; + } + + private HttpServletRequest request; + private HttpSession session; + + private PlayerInput playerInput(String namePlayer1, String namePlayer2) { + var input = new PlayerInput(); + input.setNameplayer1(namePlayer1); + input.setNameplayer2(namePlayer2); + return input; + } +} \ No newline at end of file -- cgit v1.2.3 From 9fb9913bc5ccc49d738481fa71c5ba75c43ed532 Mon Sep 17 00:00:00 2001 From: Marco de Wild Date: Fri, 15 Jan 2021 15:59:16 +0100 Subject: Modified Readme for the MVc case --- README.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 18f211d..d097e17 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,13 @@ This repository contains the files for three modules: - Model view controller: build a website for your own mancala game (or use the sloppy default implementation). - CI/CD: run your tests automatically when pushing code to Gitlab. -The skeleton project for the MVC is found on the mvc branch. On that branch, the README is updated with additional information. ## Repository structure - Main folder (this folder): contains the files relevant for the whole project. For example the Gradle-wrapper files, the .gitignore, and this readme. +- api/: contains the files for the API or service layer of your application. +- api/src/main/java/mancala/api: contains the web endpoints. +- api/src/main/java/mancala/api/models: contains the web endpoints. - domain/: contains the files that model the business domain (game rules). This is the folder you develop your OO mancala case in. ## Java project structure @@ -50,17 +52,14 @@ Replace the `./gradlew` command with `gradle` if using the globally installed Gr ## Assignment -For the lectures, see [the drive](https://drive.google.com/drive/u/0/folders/1NK95KK9Ev1yZAz1vLoQSO8rEkZq-A9AC). +For the lecture, see [the drive](https://drive.google.com/drive/u/0/folders/1PvC-HS8ty3mdtSaNdR5rt5-GwL-5_LaY). -Design an object-oriented model for mancala that can handle the following scenarios: +The global goal is to make a web front-end to your mancala back-end. A stub has been made. In api/src/test you can find examples of how you can test the api endpoints. -- All small bowls start with 4 beads -- Players take turns making a move -- A player moves by selecting a small bowl on their own side, take all the beads and distribute them anti-clockwise one at a time -- When distributing, players include their own kalaha but not the opponent's -- If a move ends in the own kalaha, the player can make another move. -- If the last bead ends in an empty bowl on the own side, the player can take that bowl's bead and the direct opposite bowl's bead and put them in their kalaha. -- The game ends if all bowls of the turn player are empty -- The winner is the player with the most beads on their territory (all bowls). - -Implement the game rules test-driven. Don't be afraid to delete or modify the Foo(Test) files, as they are just an example. \ No newline at end of file +- Familiarise yourself with the repository. Get the servers running and make sure you can connect to both servers. Enter two names in the boxes. You should see a "TODO" screen. +- Show the mancala game when it is started. +- If you want to use your own implementation, reference your implemenation in the `MancalaImpl` class. +- Build the API endpoint to make a move. +- Show the winner as soon as the game is over. +- Optionally, allow for a "revenge" option in which two players can play again. +- Optionally, allow an ongoing game to continue after a page refresh. \ No newline at end of file -- cgit v1.2.3 From a15537ec310d8fe6ba458971caa3ec31fe80412e Mon Sep 17 00:00:00 2001 From: Marco de Wild Date: Thu, 14 Jan 2021 14:32:49 +0100 Subject: Added files for interfacing with the domain --- domain/src/main/java/mancala/domain/Mancala.java | 55 ++++++++++++++++++++++ .../main/java/mancala/domain/MancalaException.java | 7 +++ .../src/main/java/mancala/domain/MancalaImpl.java | 34 +++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 domain/src/main/java/mancala/domain/Mancala.java create mode 100644 domain/src/main/java/mancala/domain/MancalaException.java create mode 100644 domain/src/main/java/mancala/domain/MancalaImpl.java diff --git a/domain/src/main/java/mancala/domain/Mancala.java b/domain/src/main/java/mancala/domain/Mancala.java new file mode 100644 index 0000000..b8b8b7b --- /dev/null +++ b/domain/src/main/java/mancala/domain/Mancala.java @@ -0,0 +1,55 @@ +package mancala.domain; + +public interface Mancala { + public static final int NO_PLAYERS = 0; + public static final int PLAYER_ONE = 1; + public static final int PLAYER_TWO = 1; + public static final int BOTH_PLAYERS = 3; + + /** + * Method indicating if the first player has the next turn or not. + * If player 1 is not in turn, then player 2 is in turn. + * @param The player which you want to know the turn for. + * @return True if the first player has the next turn, false if it's the turn of the other player. + */ + boolean isPlayersTurn(int player); + + /** + * Method for playing the specified recess. Index is as specified below: + * + * 12 11 10 9 8 7 + * 13 6 + * 0 1 2 3 4 5 + * + * @param index Index of the recess to be played. + * @return 15 item long Array with the current state of the game. The 15th item indicates which player has the next turn (possible values are 1 or 2). + */ + void playPit(int index) throws MancalaException; + + /** + * Method for returning the amount of stones in de specified pit. Index is as specified below: + * + * 12 11 10 9 8 7 + * 13 6 + * 0 1 2 3 4 5 + * + * @param index Index of the pit. + * @return Amount of stone. + */ + int getStonesForPit(int index); + + /** + * Method for retrieving whether the game has ended or not. + * + * @return True is the game has ended otherwise False. + */ + boolean isEndOfGame(); + + /** + * Method for retrieving the player that has won the game. + * + * @return Integer value representing which player(s) (if any) won the game. + */ + int getWinner(); + +} \ No newline at end of file diff --git a/domain/src/main/java/mancala/domain/MancalaException.java b/domain/src/main/java/mancala/domain/MancalaException.java new file mode 100644 index 0000000..783b229 --- /dev/null +++ b/domain/src/main/java/mancala/domain/MancalaException.java @@ -0,0 +1,7 @@ +package mancala.domain; + +public class MancalaException extends Exception { + public MancalaException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java new file mode 100644 index 0000000..de04c8f --- /dev/null +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -0,0 +1,34 @@ +package mancala.domain; + +public class MancalaImpl implements Mancala { + public MancalaImpl() { + // Initialize the game here. + } + + @Override + public boolean isPlayersTurn(int player) { + return true; + } + + @Override + public void playPit(int index) throws MancalaException { + // Implement playing a pit. + } + + @Override + public int getStonesForPit(int index) { + // Make a sane implementation. + if((index + 1 % 7) == 0) return 0; + return 4; + } + + @Override + public boolean isEndOfGame() { + return false; + } + + @Override + public int getWinner() { + return Mancala.NO_PLAYERS; + } +} \ No newline at end of file -- cgit v1.2.3 From c980d8dfc3aa0778f51eb67e8212919fa34dcc91 Mon Sep 17 00:00:00 2001 From: Marco de Wild Date: Thu, 14 Jan 2021 14:42:57 +0100 Subject: Added a stub for the API --- api/build.gradle | 50 +++++++++++ api/src/main/java/mancala/App.java | 48 +++++++++++ api/src/main/java/mancala/api/StartMancala.java | 32 ++++++++ .../main/java/mancala/api/models/GameStatus.java | 24 ++++++ api/src/main/java/mancala/api/models/Mancala.java | 21 +++++ api/src/main/java/mancala/api/models/Pit.java | 18 ++++ api/src/main/java/mancala/api/models/Player.java | 28 +++++++ .../main/java/mancala/api/models/PlayerInput.java | 23 ++++++ .../test/java/mancala/api/StartMancalaTest.java | 96 ++++++++++++++++++++++ 9 files changed, 340 insertions(+) create mode 100644 api/build.gradle create mode 100644 api/src/main/java/mancala/App.java create mode 100644 api/src/main/java/mancala/api/StartMancala.java create mode 100644 api/src/main/java/mancala/api/models/GameStatus.java create mode 100644 api/src/main/java/mancala/api/models/Mancala.java create mode 100644 api/src/main/java/mancala/api/models/Pit.java create mode 100644 api/src/main/java/mancala/api/models/Player.java create mode 100644 api/src/main/java/mancala/api/models/PlayerInput.java create mode 100644 api/src/test/java/mancala/api/StartMancalaTest.java diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..6dc681a --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,50 @@ + +plugins { + id 'java' + // This time we're building a command-line executable application. + id 'application' +} + +repositories { + jcenter() +} + +dependencies { + + // Use the Jersey framework to make writing and testing servlets easier. + implementation 'org.glassfish.jersey.containers:jersey-container-servlet-core:+' + implementation 'org.glassfish.jersey.containers:jersey-container-jetty-http:+' + implementation 'org.glassfish.jersey.core:jersey-server:+' + implementation 'org.glassfish.jersey.inject:jersey-hk2:+' + implementation 'org.glassfish.jersey.media:jersey-media-json-jackson:+' + // Use Jakarta (Java EE) for the servlet primitives. + implementation 'jakarta.servlet:jakarta.servlet-api:+' + // Use the Jetty server. + implementation 'org.eclipse.jetty:jetty-server:+' + implementation 'org.eclipse.jetty:jetty-servlet:+' + implementation 'org.eclipse.jetty:jetty-webapp:+' + // We want to have some logging output if things go wrong, so use the simple console logger from SLF4J. + // In our simple use case, the logger gets automatically configured by simply existing. + implementation 'org.slf4j:slf4j-simple:+' + + // Reference the domain subproject. + implementation project(':domain') + + // Use JUnit Jupiter API for testing. + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' + // Also use the Mockito mocking framework to mock simple server functionality. + testImplementation "org.mockito:mockito-core:2.+" + + // Use JUnit Jupiter Engine for testing. + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' +} + +application { + // Define the main class for the application. + mainClassName = 'mancala.App' +} + +test { + // Use junit platform for unit tests + useJUnitPlatform() +} diff --git a/api/src/main/java/mancala/App.java b/api/src/main/java/mancala/App.java new file mode 100644 index 0000000..062e8e1 --- /dev/null +++ b/api/src/main/java/mancala/App.java @@ -0,0 +1,48 @@ +package mancala; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.*; +import org.eclipse.jetty.webapp.*; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.glassfish.jersey.servlet.ServletContainer; + +import mancala.api.*; + +public class App { + public static void main(String[] args) throws Exception { + Server server = startServer(8080); + ServletContextHandler context = createStatefulContext(server); + registerServlets(context); + + server.start(); + System.out.println("Started server."); + System.out.println("Listening on http://localhost:8080/"); + System.out.println("Press CTRL+C to exit."); + server.join(); + } + + private static Server startServer(int port) { + return new Server(8080); + } + + private static ServletContextHandler createStatefulContext(Server server) { + ServletContextHandler context = + new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + } + + private static void registerServlets(ServletContextHandler context) { + // Use the Jersey framework to translate the classes in the + // mancala.api package to server endpoints (servlets). + // For example, the StartMancala class will become an endpoint at + // http://localost:8080/start + ServletHolder serverHolder = context.addServlet(ServletContainer.class, "/mancala/api/*"); + serverHolder.setInitOrder(1); + serverHolder.setInitParameter("jersey.config.server.provider.packages", + "mancala.api"); + } +} diff --git a/api/src/main/java/mancala/api/StartMancala.java b/api/src/main/java/mancala/api/StartMancala.java new file mode 100644 index 0000000..7bd2330 --- /dev/null +++ b/api/src/main/java/mancala/api/StartMancala.java @@ -0,0 +1,32 @@ +package mancala.api; + +import java.io.IOException; +import jakarta.servlet.http.*; +import jakarta.servlet.ServletException; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; + +import mancala.api.models.*; +import mancala.domain.MancalaImpl; + +@Path("/start") +public class StartMancala { + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response initialize( + @Context HttpServletRequest request, + PlayerInput players) { + var mancala = new MancalaImpl(); + String namePlayer1 = players.getNameplayer1(); + String namePlayer2 = players.getNameplayer2(); + + HttpSession session = request.getSession(true); + session.setAttribute("mancala", mancala); + session.setAttribute("player1", namePlayer1); + session.setAttribute("player2", namePlayer2); + + var output = new Mancala(mancala, namePlayer1, namePlayer2); + return Response.status(200).entity(output).build(); + } +} diff --git a/api/src/main/java/mancala/api/models/GameStatus.java b/api/src/main/java/mancala/api/models/GameStatus.java new file mode 100644 index 0000000..a90269c --- /dev/null +++ b/api/src/main/java/mancala/api/models/GameStatus.java @@ -0,0 +1,24 @@ +package mancala.api.models; + +public class GameStatus { + boolean endOfGame; + public boolean getEndOfGame() { return endOfGame; } + + String winner; + public String getWinner() { return winner; } + + public GameStatus(mancala.domain.Mancala mancala, + String namePlayer1, String namePlayer2) { + this.endOfGame = mancala.isEndOfGame(); + int winner = mancala.getWinner(); + if(winner == mancala.NO_PLAYERS) { + this.winner = null; + } else if(winner == mancala.PLAYER_ONE) { + this.winner = namePlayer1; + } else if(winner == mancala.PLAYER_TWO) { + this.winner = namePlayer2; + } else { + this.winner = namePlayer1 + "and" + namePlayer2; + } + } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/Mancala.java b/api/src/main/java/mancala/api/models/Mancala.java new file mode 100644 index 0000000..bb747c5 --- /dev/null +++ b/api/src/main/java/mancala/api/models/Mancala.java @@ -0,0 +1,21 @@ +package mancala.api.models; + +// This package is a collection of DTO's (data transfer objects). +// A DTO is a simple datastructure which models the +// data your web API sends back to the client. The Java +// objects will be converted to JSON objects. +public class Mancala { + public Mancala(mancala.domain.Mancala mancala, + String namePlayer1, String namePlayer2) { + players = new Player[2]; + players[0] = new Player(mancala, namePlayer1, true); + players[1] = new Player(mancala, namePlayer2, false); + gameStatus = new GameStatus(mancala, namePlayer1, namePlayer2); + } + + Player[] players; + public Player[] getPlayers() { return players; } + + GameStatus gameStatus; + public GameStatus getGameStatus() { return gameStatus; } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/Pit.java b/api/src/main/java/mancala/api/models/Pit.java new file mode 100644 index 0000000..457d19b --- /dev/null +++ b/api/src/main/java/mancala/api/models/Pit.java @@ -0,0 +1,18 @@ +package mancala.api.models; + +public class Pit { + int index; + + public int getIndex() { + return index; + } + + int nrOfStones; + + public int getNrOfStones() { return nrOfStones; } + + public Pit(int index, int nrOfStones) { + this.index = index; + this.nrOfStones = nrOfStones; + } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/Player.java b/api/src/main/java/mancala/api/models/Player.java new file mode 100644 index 0000000..f987e82 --- /dev/null +++ b/api/src/main/java/mancala/api/models/Player.java @@ -0,0 +1,28 @@ +package mancala.api.models; + +public class Player { + public Player(mancala.domain.Mancala mancala, + String name, boolean isFirstPlayer) { + this.name = name; + type = isFirstPlayer ? "player1" : "player2"; + hasTurn = mancala.isPlayersTurn(isFirstPlayer ? + mancala.PLAYER_ONE : mancala.PLAYER_TWO); + this.pits = new Pit[7]; + var firstHole = isFirstPlayer ? 0 : 7; + for(int i = 0; i < 7; ++i) { + this.pits[i] = new Pit(i + firstHole, mancala.getStonesForPit(i + firstHole)); + } + } + + String name; + public String getName() { return name; } + + String type; + public String getType() { return type; } + + boolean hasTurn; + public boolean getHasTurn() { return hasTurn; } + + Pit[] pits; + public Pit[] getPits() { return pits; } +} \ No newline at end of file diff --git a/api/src/main/java/mancala/api/models/PlayerInput.java b/api/src/main/java/mancala/api/models/PlayerInput.java new file mode 100644 index 0000000..8fdbd2a --- /dev/null +++ b/api/src/main/java/mancala/api/models/PlayerInput.java @@ -0,0 +1,23 @@ +package mancala.api.models; + +public class PlayerInput { + + String nameplayer1; + String nameplayer2; + + public String getNameplayer1() { + return nameplayer1; + } + + public void setNameplayer1(String nameplayer1) { + this.nameplayer1 = nameplayer1; + } + + public String getNameplayer2() { + return nameplayer2; + } + + public void setNameplayer2(String nameplayer2) { + this.nameplayer2 = nameplayer2; + } +} \ No newline at end of file diff --git a/api/src/test/java/mancala/api/StartMancalaTest.java b/api/src/test/java/mancala/api/StartMancalaTest.java new file mode 100644 index 0000000..0c4b23f --- /dev/null +++ b/api/src/test/java/mancala/api/StartMancalaTest.java @@ -0,0 +1,96 @@ +package mancala.api; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import jakarta.servlet.http.*; +import jakarta.ws.rs.core.*; + +import mancala.api.models.*; +import mancala.domain.MancalaImpl; + +public class StartMancalaTest { + @Test + public void startingMancalaShouldBeAllowed() { + var response = startMancala("Mario", "Luigi"); + assertEquals(200, response.getStatus()); + } + + @Test + public void startingMancalaReturnsAGameWithoutAWinner() { + var response = startMancala("Mario", "Luigi"); + var entity = (Mancala)response.getEntity(); + var gameState = entity.getGameStatus(); + assertFalse(gameState.getEndOfGame()); + assertNull(gameState.getWinner()); + } + + @Test + public void startingMancalaReturnsThePlayerData() { + var response = startMancala("Mario", "Luigi"); + var entity = (Mancala)response.getEntity(); + var players = entity.getPlayers(); + assertEquals(2, players.length); + assertEquals("Mario", players[0].getName()); + assertEquals("Luigi", players[1].getName()); + } + + @Test + public void startingMancalaReturnsThePits() { + var response = startMancala("Mario", "Luigi"); + var entity = (Mancala)response.getEntity(); + var players = entity.getPlayers(); + assertEquals(7, players[0].getPits().length); + assertEquals(0, players[0].getPits()[0].getIndex()); + assertEquals(4, players[0].getPits()[0].getNrOfStones()); + assertEquals(0, players[0].getPits()[6].getNrOfStones()); + assertEquals(7, players[1].getPits().length); + assertEquals(7, players[1].getPits()[0].getIndex()); + assertEquals(4, players[1].getPits()[0].getNrOfStones()); + assertEquals(0, players[1].getPits()[6].getNrOfStones()); + } + + @Test + public void startingMancalaStartsANewSession() { + startMancala("Mario", "Luigi"); + verify(request).getSession(true); + } + + @Test + public void startingMancalaSavesTheNewGameInASession() { + startMancala("Mario", "Luigi"); + verify(session).setAttribute(eq("mancala"), any(MancalaImpl.class)); + } + + @Test + public void startingMancalaSavesTheNamesInASession() { + startMancala("Mario", "Luigi"); + verify(session).setAttribute("player1", "Mario"); + verify(session).setAttribute("player2", "Luigi"); + } + + private Response startMancala(String namePlayer1, String namePlayer2) { + var servlet = new StartMancala(); + var request = createRequestContext(); + var input = playerInput(namePlayer1, namePlayer2); + return servlet.initialize(request, input); + } + + private HttpServletRequest createRequestContext() { + request = mock(HttpServletRequest.class); + session = mock(HttpSession.class); + when(request.getSession(true)).thenReturn(session); + return request; + } + + private HttpServletRequest request; + private HttpSession session; + + private PlayerInput playerInput(String namePlayer1, String namePlayer2) { + var input = new PlayerInput(); + input.setNameplayer1(namePlayer1); + input.setNameplayer2(namePlayer2); + return input; + } +} \ No newline at end of file -- cgit v1.2.3 From 1a6eeaa4b4f060ad05a6bd297702d246aba69313 Mon Sep 17 00:00:00 2001 From: Marco de Wild Date: Fri, 15 Jan 2021 15:59:16 +0100 Subject: Modified Readme for the MVc case --- README.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ad46ee4..2126ff3 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,13 @@ This repository contains the files for three modules: - Model view controller: build a website for your own mancala game (or use the sloppy default implementation). - CI/CD: run your tests automatically when pushing code to Gitlab. -The skeleton project for the MVC is found on the mvc branch. On that branch, the README is updated with additional information. ## Repository structure - Main folder (this folder): contains the files relevant for the whole project. For example the Gradle-wrapper files, the .gitignore, and this readme. +- api/: contains the files for the API or service layer of your application. +- api/src/main/java/mancala/api: contains the web endpoints. +- api/src/main/java/mancala/api/models: contains the web endpoints. - domain/: contains the files that model the business domain (game rules). This is the folder you develop your OO mancala case in. ## Java project structure @@ -50,17 +52,14 @@ When you run the test, you will see a build failure. In `domain/src/test/java/ma ## Assignment -For the lectures, see [the drive](https://drive.google.com/drive/u/0/folders/1NK95KK9Ev1yZAz1vLoQSO8rEkZq-A9AC). +For the lecture, see [the drive](https://drive.google.com/drive/u/0/folders/1PvC-HS8ty3mdtSaNdR5rt5-GwL-5_LaY). -Design an object-oriented model for mancala that can handle the following scenarios: +The global goal is to make a web front-end to your mancala back-end. A stub has been made. In api/src/test you can find examples of how you can test the api endpoints. -- All small bowls start with 4 beads -- Players take turns making a move -- A player moves by selecting a small bowl on their own side, take all the beads and distribute them anti-clockwise one at a time -- When distributing, players include their own kalaha but not the opponent's -- If a move ends in the own kalaha, the player can make another move. -- If the last bead ends in an empty bowl on the own side, the player can take that bowl's bead and the direct opposite bowl's bead and put them in their kalaha. -- The game ends if all bowls of the turn player are empty -- The winner is the player with the most beads on their territory (all bowls). - -Implement the game rules test-driven. Don't be afraid to delete or modify the Foo(Test) files, as they are just an example. \ No newline at end of file +- Familiarise yourself with the repository. Get the servers running and make sure you can connect to both servers. Enter two names in the boxes. You should see a "TODO" screen. +- Show the mancala game when it is started. +- If you want to use your own implementation, reference your implemenation in the `MancalaImpl` class. +- Build the API endpoint to make a move. +- Show the winner as soon as the game is over. +- Optionally, allow for a "revenge" option in which two players can play again. +- Optionally, allow an ongoing game to continue after a page refresh. \ No newline at end of file -- cgit v1.2.3 From 7b1b8a35db9699240b6c5243cd89ef0be7fd798a Mon Sep 17 00:00:00 2001 From: Marco de Wild Date: Tue, 19 Jan 2021 15:15:14 +0100 Subject: Clarified the 'stuck' build --- README.md | 2 +- api/src/main/java/mancala/App.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2126ff3..2e9dd60 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ You can either install Gradle on your machine and use the installation or use th ./gradlew run ``` -When you run the test, you will see a build failure. In `domain/src/test/java/mancala/domain.FooTest.java`, there is a failing test. If you fix the failing test, the build will succeed. +If you run the program, you will notice the build "progress" is stuck on 87% or so. That means your application is running and Gradle is waiting for it to succeed. You can ignore the progress bar when running the application; it should print some lines when it's ready. ## Assignment diff --git a/api/src/main/java/mancala/App.java b/api/src/main/java/mancala/App.java index 062e8e1..3d89bb0 100644 --- a/api/src/main/java/mancala/App.java +++ b/api/src/main/java/mancala/App.java @@ -33,6 +33,7 @@ public class App { new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath("/"); server.setHandler(context); + return context; } private static void registerServlets(ServletContextHandler context) { -- cgit v1.2.3 From f382ec567e1292cedee7c01e721ddc38bdc4dc4e Mon Sep 17 00:00:00 2001 From: "SOGYO\\bvdoord" Date: Tue, 26 Jan 2021 11:22:58 +0100 Subject: Front-end met Snowpack toegevoegd --- .gitignore | 3 +- README.md | 42 + api/src/main/java/mancala/App.java | 2 +- client/.eslintrc.json | 40 + client/package-lock.json | 4489 ++++++++++++++++++++++++++++++++++++ client/package.json | 31 + client/public/favicon.ico | Bin 0 -> 15406 bytes client/public/index.html | 20 + client/snowpack.config.js | 30 + client/src/About/About.tsx | 12 + client/src/App.css | 9 + client/src/App.tsx | 29 + client/src/Header/Header.css | 30 + client/src/Header/Header.tsx | 20 + client/src/Header/logo.jpg | Bin 0 -> 5184 bytes client/src/Mancala/Mancala.css | 1 + client/src/Mancala/Mancala.tsx | 24 + client/src/Mancala/Play.css | 1 + client/src/Mancala/Play.tsx | 17 + client/src/Mancala/StartGame.css | 10 + client/src/Mancala/StartGame.tsx | 74 + client/src/gameState.ts | 19 + client/src/index.tsx | 15 + client/tsconfig.json | 21 + client/types/static.d.ts | 59 + 25 files changed, 4996 insertions(+), 2 deletions(-) create mode 100644 client/.eslintrc.json create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/public/favicon.ico create mode 100644 client/public/index.html create mode 100644 client/snowpack.config.js create mode 100644 client/src/About/About.tsx create mode 100644 client/src/App.css create mode 100644 client/src/App.tsx create mode 100644 client/src/Header/Header.css create mode 100644 client/src/Header/Header.tsx create mode 100644 client/src/Header/logo.jpg create mode 100644 client/src/Mancala/Mancala.css create mode 100644 client/src/Mancala/Mancala.tsx create mode 100644 client/src/Mancala/Play.css create mode 100644 client/src/Mancala/Play.tsx create mode 100644 client/src/Mancala/StartGame.css create mode 100644 client/src/Mancala/StartGame.tsx create mode 100644 client/src/gameState.ts create mode 100644 client/src/index.tsx create mode 100644 client/tsconfig.json create mode 100644 client/types/static.d.ts diff --git a/.gitignore b/.gitignore index 7b0ae74..c22a485 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ # Ignore Gradle build output directory build -node_modules \ No newline at end of file +node_modules +client/node_modules \ No newline at end of file diff --git a/README.md b/README.md index d097e17..5512b82 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,48 @@ This repository contains the files for three modules: - api/src/main/java/mancala/api: contains the web endpoints. - api/src/main/java/mancala/api/models: contains the web endpoints. - domain/: contains the files that model the business domain (game rules). This is the folder you develop your OO mancala case in. +- client/: contains the front end + +## Two servers + +The project consists of two servers. The front-end uses a nodejs server. It is mainly used to compile your React code into Javascript files during development. This will shorten the feedback loop between changing your code and seeing the results in the browser. The second server is the back-end, which uses a Jetty server. The back-end server allows your Java API to be accessible for other programs, including the front-end server. To prevent [cross-origin request shenanigans (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), all requests from the browser will be sent to the front-end server. That server will then forward to the back-end server if needed. + +The front-end assumes that the back-end will run on port 8080. If that is not the case, edit the snowpack.config.js file. + + +## React project structure + +A React project is generally structured as follows: + +``` +package.json +public/ + index.html +src/ + Feature1/ + Feature1.css + Feature1.tsx + Feature1.tests.tsx + Feature2/ + Feature2.css + Feature2.tsx + Feature2.tests.tsx + Feature2B.tsx + Feature2B.tests.tsx +``` + +The public directory contains static files, such as the relatively empty index.html file needed to run React. The src file contains the React code. The convention for TypeScript projects is to use the .tsx file extension for files that contain React components. Files are generally grouped together in directories by feature. These directories contain all files related to that feature, such as coponents, stylesheets, images and tests. + +The package.json specifies which commands can be run using npm (e.g. npm run start). In this sample repository, two commands have been defined: + +```bash +# Start a development server +npm run start +# Check code for common mistakes and style conventions +npm run lint +``` + + ## Java project structure diff --git a/api/src/main/java/mancala/App.java b/api/src/main/java/mancala/App.java index 062e8e1..71fe2d7 100644 --- a/api/src/main/java/mancala/App.java +++ b/api/src/main/java/mancala/App.java @@ -39,7 +39,7 @@ public class App { // Use the Jersey framework to translate the classes in the // mancala.api package to server endpoints (servlets). // For example, the StartMancala class will become an endpoint at - // http://localost:8080/start + // http://localost:8080/mancala/api/start ServletHolder serverHolder = context.addServlet(ServletContainer.class, "/mancala/api/*"); serverHolder.setInitOrder(1); serverHolder.setInitParameter("jersey.config.server.provider.packages", diff --git a/client/.eslintrc.json b/client/.eslintrc.json new file mode 100644 index 0000000..57088d6 --- /dev/null +++ b/client/.eslintrc.json @@ -0,0 +1,40 @@ +{ + "root": true, + "env": { + "browser": true, + "es6": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:@typescript-eslint/eslint-recommended" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": [ + "react", + "react-hooks", + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "no-unused-vars": "off" + }, + "settings": { + "react": { + "version": "detect" + } + } +} \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..1cf4e2a --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,4489 @@ +{ + "name": "mancala-mvc", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", + "dev": true, + "requires": { + "@babel/types": "^7.12.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz", + "integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz", + "integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", + "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz", + "integrity": "sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", + "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } + } + }, + "@babel/helper-module-transforms": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", + "dev": true, + "requires": { + "@babel/types": "^7.12.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz", + "integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz", + "integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", + "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", + "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", + "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", + "dev": true, + "requires": { + "@babel/types": "^7.12.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz", + "integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz", + "integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", + "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-simple-access": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", + "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", + "dev": true, + "requires": { + "@babel/types": "^7.12.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz", + "integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz", + "integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", + "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/runtime": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@eslint/eslintrc": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "@rollup/plugin-alias": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.1.tgz", + "integrity": "sha512-hNcQY4bpBUIvxekd26DBPgF7BT4mKVNDF5tBG4Zi+3IgwLxGYRY0itHs9D0oLVwXM5pvJDWJlBQro+au8WaUWw==", + "dev": true, + "requires": { + "slash": "^3.0.0" + } + }, + "@rollup/plugin-commonjs": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz", + "integrity": "sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + }, + "dependencies": { + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "dev": true, + "requires": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + } + } + } + }, + "@rollup/plugin-inject": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-4.0.2.tgz", + "integrity": "sha512-TSLMA8waJ7Dmgmoc8JfPnwUwVZgLjjIAM6MqeIFqPO2ODK36JqE0Cf2F54UTgCUuW8da93Mvoj75a6KAVWgylw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.4", + "estree-walker": "^1.0.1", + "magic-string": "^0.25.5" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "@rollup/plugin-json": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8" + } + }, + "@rollup/plugin-node-resolve": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-10.0.0.tgz", + "integrity": "sha512-sNijGta8fqzwA1VwUEtTvWCx2E7qC70NMsDh4ZG13byAXYigBNZMxALhKUSycBks5gupJdq0lFrKumFrRZ8H3A==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.17.0" + }, + "dependencies": { + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "dev": true, + "requires": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + } + } + } + }, + "@rollup/plugin-replace": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz", + "integrity": "sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + } + } + }, + "@snowpack/plugin-build-script": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-build-script/-/plugin-build-script-2.0.11.tgz", + "integrity": "sha512-bLi7W0ry5OAhCEGuBL3PdLchWU+WaXa2aJYSJapt4FsfhUiNwi+ua1qiZ0syaXXfRq/2jx9eEkx4G7TGB8Kvsg==", + "dev": true, + "requires": { + "execa": "^4.0.3", + "npm-run-path": "^4.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@snowpack/plugin-dotenv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-dotenv/-/plugin-dotenv-2.0.4.tgz", + "integrity": "sha512-Ho4IpDLiA0m6YgKoBAuHV/s8bk+pIaE7sYH01nvfm9QOkZK68l69Dv61DG4VXMatcnw1zEPPR3RoLKpyDofKYQ==", + "dev": true, + "requires": { + "dotenv": "^8.2.0", + "dotenv-expand": "^5.1.0" + } + }, + "@snowpack/plugin-react-refresh": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-react-refresh/-/plugin-react-refresh-2.3.6.tgz", + "integrity": "sha512-7gxFXZljmhpzDtI9p8KM5AAqMJtjUlfnYQHcMhkHJbGtfbgbBVmrBqyR8LWrAvywovX7jaFm6bk79Uuq/E5v0Q==", + "dev": true, + "requires": { + "@babel/core": "^7.0.0", + "@babel/plugin-syntax-class-properties": "^7.10.0", + "react-refresh": "^0.9.0" + } + }, + "@snowpack/plugin-run-script": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-run-script/-/plugin-run-script-2.2.0.tgz", + "integrity": "sha512-wv+ttz7wXD/R+IoIuZqdwy4foP3yq986dPVMGTl2N6ipWo5PAHAQ3n6A83efT/K4zQ1h+RibF3k4yr92qu3jNg==", + "dev": true, + "requires": { + "execa": "^4.0.3", + "npm-run-path": "^4.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@snowpack/plugin-typescript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-typescript/-/plugin-typescript-1.1.0.tgz", + "integrity": "sha512-6jAFFks42lW1Gy07qVF6A0RKOCQc+ICa029GUrbgLmXhs0UzDmTG+jJ0qyC41J9MnRR/utLrXvgWWVrmIrPifw==", + "dev": true, + "requires": { + "execa": "^4.0.3", + "npm-run-path": "^4.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/history": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", + "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "@types/node": { + "version": "13.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.1.tgz", + "integrity": "sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "@types/react": { + "version": "16.9.56", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.56.tgz", + "integrity": "sha512-gIkl4J44G/qxbuC6r2Xh+D3CGZpJ+NdWTItAPmZbR5mUS+JQ8Zvzpl0ea5qT/ZT3ZNTUcDKUVqV3xBE8wv/DyQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "16.9.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.9.tgz", + "integrity": "sha512-jE16FNWO3Logq/Lf+yvEAjKzhpST/Eac8EMd1i4dgZdMczfgqC8EjpxwNgEe3SExHYLliabXDh9DEhhqnlXJhg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-router": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.11.tgz", + "integrity": "sha512-ofHbZMlp0Y2baOHgsWBQ4K3AttxY61bDMkwTiBOkPg7U6C/3UwwB5WaIx28JmSVi/eX3uFEMRo61BV22fDQIvg==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.7.tgz", + "integrity": "sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/snowpack-env": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/snowpack-env/-/snowpack-env-2.3.1.tgz", + "integrity": "sha512-rBnUqf24jvuGWkvNSTq3eI9zlji8KsTu/SnKFOvutCYSaLVcS6dkomVr5DExeO+fJRT1s7FyNxfWZ80bpqVP/w==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz", + "integrity": "sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.14.1", + "@typescript-eslint/scope-manager": "4.14.1", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz", + "integrity": "sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.14.1", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/typescript-estree": "4.14.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.1.tgz", + "integrity": "sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.14.1", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/typescript-estree": "4.14.1", + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz", + "integrity": "sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1" + } + }, + "@typescript-eslint/types": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.1.tgz", + "integrity": "sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz", + "integrity": "sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz", + "integrity": "sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.14.1", + "eslint-visitor-keys": "^2.0.0" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "address": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", + "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", + "dev": true + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-includes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", + "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "get-intrinsic": "^1.0.1", + "is-string": "^1.0.5" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flatmap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", + "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "function-bind": "^1.1.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cjs-module-lexer": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.5.2.tgz", + "integrity": "sha512-GlXp/i+x/WJP5X/qBAfo8ygo97ePkYSTL9um1TZ7nMG9JlFXZADyMhHQ7DFc0xk4G8oCagEKxQaHHD1wd+ZEyA==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "css-modules-loader-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", + "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=", + "dev": true, + "requires": { + "icss-replace-symbols": "1.1.0", + "postcss": "6.0.1", + "postcss-modules-extract-imports": "1.1.0", + "postcss-modules-local-by-default": "1.2.0", + "postcss-modules-scope": "1.1.0", + "postcss-modules-values": "1.3.0" + } + }, + "css-selector-tokenizer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", + "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz", + "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "detect-port": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", + "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", + "dev": true, + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", + "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.1", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.3", + "string.prototype.trimstart": "^1.0.3" + } + }, + "es-module-lexer": { + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.26.tgz", + "integrity": "sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA==", + "dev": true + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.5.tgz", + "integrity": "sha512-195Lq3Hp3V8rvccV4ede8PxmGvKuh4BrGQHxVfcYmpyg1ni078QN4iO1VNYCl55v0qYsWz6n96y3ZtMpuEtDRQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esinstall": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/esinstall/-/esinstall-0.3.7.tgz", + "integrity": "sha512-IslJdHov4TQPSNwHK4WCIkQwxaYrDDW2u6iZxiGbNVzFPyeEEVs2BXDmd1ZXVE4+bP/eCvvbvYH9f59gafgn5A==", + "dev": true, + "requires": { + "@rollup/plugin-alias": "^3.0.1", + "@rollup/plugin-commonjs": "^16.0.0", + "@rollup/plugin-inject": "^4.0.2", + "@rollup/plugin-json": "^4.0.0", + "@rollup/plugin-node-resolve": "^10.0.0", + "@rollup/plugin-replace": "^2.3.3", + "cjs-module-lexer": "^0.5.0", + "es-module-lexer": "^0.3.24", + "is-builtin-module": "^3.0.0", + "kleur": "^4.1.1", + "mkdirp": "^1.0.3", + "rimraf": "^3.0.0", + "rollup": "^2.23.0", + "rollup-plugin-node-polyfills": "^0.2.1", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "eslint": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.3.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^6.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.4", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-plugin-react": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz", + "integrity": "sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.18.1", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", + "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "estree-walker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz", + "integrity": "sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "fastq": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", + "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", + "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, + "follow-redirects": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz", + "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==", + "dev": true, + "requires": { + "debug": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.0.tgz", + "integrity": "sha512-M11rgtQp5GZMZzDL7jLTNxbDfurpzuau5uqRWDPvlHjfvg3TdScAZo96GLvhMjImrmR8uAt0FS2RLoMrfWGKlg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "httpie": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/httpie/-/httpie-1.1.2.tgz", + "integrity": "sha512-VQ82oXG95oY1fQw/XecHuvcFBA+lZQ9Vwj1RfLcO8a7HpDd4cc2ukwpJt+TUlFaLUAzZErylxWu6wclJ1rUhUQ==", + "dev": true + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-builtin-module": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.0.0.tgz", + "integrity": "sha512-/93sDihsAD652hrMEbJGbMAVBf1qc96kyThHQ0CAOONHaE3aROLpTjDe4WQ5aoC5ITHFxEq1z8XqSU7km+8amw==", + "dev": true, + "requires": { + "builtin-modules": "^3.0.0" + } + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-core-module": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", + "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isbinaryfile": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", + "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsonschema": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.11.tgz", + "integrity": "sha512-XNZHs3N1IOa3lPKm//npxMhOdaoPw+MvEV0NIgxcER83GTJcG13rehtWmpBCfEt8DrtYwIkMTs8bdXoYs4fvnQ==", + "dev": true + }, + "jsx-ast-utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", + "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.2", + "object.assign": "^4.1.2" + } + }, + "kleur": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.3.tgz", + "integrity": "sha512-H1tr8QP2PxFTNwAFM74Mui2b6ovcY9FoxJefgrwxY+OCJcq01k5nvhf4M/KnizzrJvLRap5STUy7dgDV35iUBw==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dev": true, + "requires": { + "mime-db": "1.43.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", + "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.3.tgz", + "integrity": "sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", + "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "dependencies": { + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + } + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + } + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, + "postcss": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz", + "integrity": "sha1-AA29H47vIXqjaLmiEsX8QLKo8/I=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "react": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", + "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-dom": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", + "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-refresh": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", + "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==", + "dev": true + }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "regexp.prototype.flags": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.33.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.33.1.tgz", + "integrity": "sha512-uY4O/IoL9oNW8MMcbA5hcOaz6tZTMIh7qJHx/tzIJm+n1wLoY38BLn6fuy7DhR57oNFLMbDQtDeJoFURt5933w==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + }, + "dependencies": { + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + } + } + }, + "rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, + "rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "requires": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true + }, + "scheduler": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", + "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "snowpack": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/snowpack/-/snowpack-2.17.0.tgz", + "integrity": "sha512-Up8J+pwQANPtvg81M86TU5Qk2rtuNnJiRwWI0qObcsSWVY8+39Uf/FtqQsdG1ecQ9AfhIjtUwpKNYqnJPH/D0w==", + "dev": true, + "requires": { + "@snowpack/plugin-build-script": "^2.0.11", + "@snowpack/plugin-run-script": "^2.2.0", + "cacache": "^15.0.0", + "cachedir": "^2.3.0", + "chokidar": "^3.4.0", + "compressible": "^2.0.18", + "cosmiconfig": "^7.0.0", + "css-modules-loader-core": "^1.1.0", + "deepmerge": "^4.2.2", + "detect-port": "^1.3.0", + "es-module-lexer": "^0.3.24", + "esbuild": "^0.8.0", + "esinstall": "^0.3.7", + "etag": "^1.8.1", + "execa": "^4.0.3", + "find-cache-dir": "^3.3.1", + "find-up": "^5.0.0", + "glob": "^7.1.4", + "http-proxy": "^1.18.1", + "httpie": "^1.1.2", + "isbinaryfile": "^4.0.6", + "jsonschema": "~1.2.5", + "kleur": "^4.1.1", + "mime-types": "^2.1.26", + "mkdirp": "^1.0.3", + "npm-run-path": "^4.0.1", + "open": "^7.0.4", + "p-queue": "^6.6.1", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.3", + "source-map": "^0.7.3", + "strip-ansi": "^6.0.0", + "strip-comments": "^2.0.1", + "validate-npm-package-name": "^3.0.0", + "ws": "^7.3.0", + "yargs-parser": "^20.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + } + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + } + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "ssri": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "ws": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", + "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz", + "integrity": "sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", + "dev": true, + "requires": { + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ajv": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", + "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..859a76a --- /dev/null +++ b/client/package.json @@ -0,0 +1,31 @@ +{ + "name": "mancala-mvc", + "version": "1.0.0", + "description": "Front-end for the mancala mvc", + "private": true, + "scripts": { + "start": "snowpack dev", + "lint": "eslint src/**/*.{ts,tsx}" + }, + "dependencies": { + "react": "^17.0.0", + "react-dom": "^17.0.0", + "react-router-dom": "^5.2.0" + }, + "devDependencies": { + "@snowpack/plugin-dotenv": "^2.0.4", + "@snowpack/plugin-react-refresh": "^2.3.6", + "@snowpack/plugin-typescript": "^1.1.0", + "@types/react": "^16.9.49", + "@types/react-dom": "^16.9.8", + "@types/react-router-dom": "^5.1.7", + "@types/snowpack-env": "^2.3.0", + "@typescript-eslint/eslint-plugin": "^4.14.0", + "@typescript-eslint/parser": "^4.14.0", + "eslint": "^7.18.0", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0", + "snowpack": "^2.17.0", + "typescript": "^4.0.0" + } +} diff --git a/client/public/favicon.ico b/client/public/favicon.ico new file mode 100644 index 0000000..bc20cc7 Binary files /dev/null and b/client/public/favicon.ico differ diff --git a/client/public/index.html b/client/public/index.html new file mode 100644 index 0000000..d46a8cf --- /dev/null +++ b/client/public/index.html @@ -0,0 +1,20 @@ + + + + + + + + Mancala MVC + + +
+ + + + + diff --git a/client/snowpack.config.js b/client/snowpack.config.js new file mode 100644 index 0000000..db22467 --- /dev/null +++ b/client/snowpack.config.js @@ -0,0 +1,30 @@ +/** @type {import("snowpack").SnowpackUserConfig } */ +module.exports = { + mount: { + public: '/', + src: '/_dist_', + }, + plugins: [ + '@snowpack/plugin-react-refresh', + '@snowpack/plugin-dotenv', + '@snowpack/plugin-typescript', + ], + install: [ + /* ... */ + ], + installOptions: { + /* ... */ + }, + devOptions: { + port: 3000, + }, + buildOptions: { + /* ... */ + }, + proxy: { + '/mancala': 'http://localhost:8080/mancala', // <-- change 8080 to a different port if necessary + }, + alias: { + /* ... */ + }, +}; diff --git a/client/src/About/About.tsx b/client/src/About/About.tsx new file mode 100644 index 0000000..e7a9c69 --- /dev/null +++ b/client/src/About/About.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +export function About() { + return
+

Mancala

+

+ Mancala is one of the oldest known board games. It has many different variations and is known under several different names. + The objective of the game is obtaining as many stones, beads or seeds as possible. + It is a game of skill that does not rely on any sort of randomness, making it similar to chess. +

+
+} \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 0000000..8eebe3a --- /dev/null +++ b/client/src/App.css @@ -0,0 +1,9 @@ +body { + margin: 0px; + padding: 0px; + background-color: #eeeeee; +} + +.main-content { + padding: 8px; +} \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..edd9504 --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; +import { Header } from "./Header/Header"; +import { About } from "./About/About"; +import { Mancala } from "./Mancala/Mancala"; +import "./App.css"; + +export function App() { + return ( + + {/* The header with navigation options is always on top of every page */} +
+ +
+ + {/* If the user goes to the url /about, show the about page */} + + + + + {/* If the user goes to any other url, show the play page */} + + + + +
+ + ) +} \ No newline at end of file diff --git a/client/src/Header/Header.css b/client/src/Header/Header.css new file mode 100644 index 0000000..6744f32 --- /dev/null +++ b/client/src/Header/Header.css @@ -0,0 +1,30 @@ +.main-header { + background-color: white; + color: green; + font-size: 2rem; + margin-bottom: 8px; +} + +.main-navigation { + display: flex; + justify-content: space-around; + align-items: center; + background-color: green; + font-size: 1.3rem; + height: 45px; +} + +.main-navigation a { + color: white; + text-decoration: none; +} + +.main-header div { + display: flex; + align-items: center; +} + +.main-header img { + height: 50px; + margin: 6px; +} \ No newline at end of file diff --git a/client/src/Header/Header.tsx b/client/src/Header/Header.tsx new file mode 100644 index 0000000..9f96d0c --- /dev/null +++ b/client/src/Header/Header.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import "./Header.css"; +import urlLogo from "./logo.jpg"; + +/** + * A Header component with a Sogyo logo, the name of the application and several links to different pages + */ +export function Header() { + return
+
+ + Mancala +
+
+ Play + About +
+
+} \ No newline at end of file diff --git a/client/src/Header/logo.jpg b/client/src/Header/logo.jpg new file mode 100644 index 0000000..9104922 Binary files /dev/null and b/client/src/Header/logo.jpg differ diff --git a/client/src/Mancala/Mancala.css b/client/src/Mancala/Mancala.css new file mode 100644 index 0000000..8f73945 --- /dev/null +++ b/client/src/Mancala/Mancala.css @@ -0,0 +1 @@ +/** Add some styles */ \ No newline at end of file diff --git a/client/src/Mancala/Mancala.tsx b/client/src/Mancala/Mancala.tsx new file mode 100644 index 0000000..42c31a2 --- /dev/null +++ b/client/src/Mancala/Mancala.tsx @@ -0,0 +1,24 @@ +import React, { useState } from "react"; +import { StartGame } from "./StartGame"; +import { Play } from "./Play"; +import type { GameState } from "../gameState"; +import "./Mancala.css"; + +/** + * The base component for the Mancala game. If there's no active game, the `StartGame` component allows + * users to enter two player names and start a new game. + * If there's an active game this component holds the game state. This game state can be passed as a prop + * to child components as needed. + * + * Child components can modify the game state by calling the setGameState (which they recieve as prop.) + */ +export function Mancala() { + + const [ gameState, setGameState ] = useState(undefined); + + if (!gameState) { + return + } + + return +} \ No newline at end of file diff --git a/client/src/Mancala/Play.css b/client/src/Mancala/Play.css new file mode 100644 index 0000000..8f73945 --- /dev/null +++ b/client/src/Mancala/Play.css @@ -0,0 +1 @@ +/** Add some styles */ \ No newline at end of file diff --git a/client/src/Mancala/Play.tsx b/client/src/Mancala/Play.tsx new file mode 100644 index 0000000..3b8ff25 --- /dev/null +++ b/client/src/Mancala/Play.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import type { GameState } from "../gameState"; +import "./Play.css"; + +type PlayProps = { + gameState: GameState; + setGameState(newGameState: GameState): void; +} + +export function Play({ gameState, setGameState }: PlayProps) { + return ( +
+

{gameState.players[0].name} vs {gameState.players[1].name}

+ To do... +
+ ) +} \ No newline at end of file diff --git a/client/src/Mancala/StartGame.css b/client/src/Mancala/StartGame.css new file mode 100644 index 0000000..3ab5872 --- /dev/null +++ b/client/src/Mancala/StartGame.css @@ -0,0 +1,10 @@ +.startGameButton { + font-size: 2em; + background-color: lightgreen; + border: 2px solid black; +} + +.errorMessage { + height: 1em; + color: red; +} \ No newline at end of file diff --git a/client/src/Mancala/StartGame.tsx b/client/src/Mancala/StartGame.tsx new file mode 100644 index 0000000..9d5be2c --- /dev/null +++ b/client/src/Mancala/StartGame.tsx @@ -0,0 +1,74 @@ +import React, { useState } from "react"; +import type { GameState } from "../gameState"; +import "./StartGame.css"; + +type StartGameProps = { + setGameState(newGameState: GameState): void; +} + +/** + * Allows the players to enter their name. A name is required for both players. They can't have the same names. + */ +export function StartGame({ setGameState }: StartGameProps) { + + const [errorMessage, setErrorMessage] = useState(""); + const [playerOne, setPlayerOne] = useState(""); + const [playerTwo, setPlayerTwo] = useState(""); + + async function tryStartGame(e: React.FormEvent) { + e.preventDefault(); // Prevent default browser behavior of submitting forms + if (!playerOne) { + setErrorMessage("A name is required for player 1"); + return; + } + if (!playerTwo) { + setErrorMessage("A name is required for player 2"); + return; + } + if (playerOne === playerTwo) { + setErrorMessage("Each player should have a unique name"); + return; + } + setErrorMessage(""); + + try { + const response = await fetch('mancala/api/start', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ nameplayer1: playerOne, nameplayer2: playerTwo }) + }); + + if (response.ok) { + const gameState = await response.json(); + setGameState(gameState); + } else { + console.error(response.statusText); + } + } catch (error) { + console.error(error.toString()); + } + } + + return ( +
tryStartGame(e)}> + setPlayerOne(e.target.value)} + /> + + setPlayerTwo(e.target.value)} + /> + +

{errorMessage}

+ + +
+ ) +} \ No newline at end of file diff --git a/client/src/gameState.ts b/client/src/gameState.ts new file mode 100644 index 0000000..2d5ec3e --- /dev/null +++ b/client/src/gameState.ts @@ -0,0 +1,19 @@ + +export interface GameState { + players: [ Player, Player ]; // a player array contains exactly two Players + gameStatus: { + endOfGame: boolean; + }; +} + +interface Player { + name: string; + pits: Pit[]; + type: "player1" | "player2"; // only "player1" and "player2" are valid options for this string + hasTurn: boolean; +} + +interface Pit { + index: number; + nrOfStones: number; +} diff --git a/client/src/index.tsx b/client/src/index.tsx new file mode 100644 index 0000000..c92ef7e --- /dev/null +++ b/client/src/index.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; +import ReactDOM from "react-dom"; +import { App } from "./App"; + +ReactDOM.render( + , + document.getElementById("root") +) + +// Hot Module Replacement (HMR) - Remove this snippet to remove HMR. +// Learn more: https://www.snowpack.dev/#hot-module-replacement +const hotMudleReplacement = import.meta.hot; +if (hotMudleReplacement) { + hotMudleReplacement.accept(); +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..0e93608 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,21 @@ +{ + "include": ["src", "types"], + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "moduleResolution": "node", + "jsx": "preserve", + "baseUrl": "./", + /* paths - If you configure Snowpack import aliases, add them here. */ + "paths": {}, + /* noEmit - Snowpack builds (emits) files, not tsc. */ + "noEmit": true, + /* Additional Options */ + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "importsNotUsedAsValues": "error" + } +} diff --git a/client/types/static.d.ts b/client/types/static.d.ts new file mode 100644 index 0000000..ba74d2a --- /dev/null +++ b/client/types/static.d.ts @@ -0,0 +1,59 @@ +/* Use this file to declare any custom file extensions for importing */ +/* Use this folder to also add/extend a package d.ts file, if needed. */ + +/* CSS MODULES */ +declare module '*.module.css' { + const classes: { [key: string]: string }; + export default classes; +} +declare module '*.module.scss' { + const classes: { [key: string]: string }; + export default classes; +} +declare module '*.module.sass' { + const classes: { [key: string]: string }; + export default classes; +} +declare module '*.module.less' { + const classes: { [key: string]: string }; + export default classes; +} +declare module '*.module.styl' { + const classes: { [key: string]: string }; + export default classes; +} + +/* CSS */ +declare module '*.css'; +declare module '*.scss'; +declare module '*.sass'; +declare module '*.less'; +declare module '*.styl'; + +/* IMAGES */ +declare module '*.svg' { + const ref: string; + export default ref; +} +declare module '*.bmp' { + const ref: string; + export default ref; +} +declare module '*.gif' { + const ref: string; + export default ref; +} +declare module '*.jpg' { + const ref: string; + export default ref; +} +declare module '*.jpeg' { + const ref: string; + export default ref; +} +declare module '*.png' { + const ref: string; + export default ref; +} + +/* CUSTOM: ADD YOUR OWN HERE */ -- cgit v1.2.3 From 51d1d5ac88e0b9def0423906deb5543c4439b125 Mon Sep 17 00:00:00 2001 From: "SOGYO\\bvdoord" Date: Tue, 16 Feb 2021 12:49:56 +0100 Subject: Uitleg voor het installeren/starten toegevoegd --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5512b82..859d23b 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,16 @@ This repository contains the files for three modules: - api/src/main/java/mancala/api: contains the web endpoints. - api/src/main/java/mancala/api/models: contains the web endpoints. - domain/: contains the files that model the business domain (game rules). This is the folder you develop your OO mancala case in. -- client/: contains the front end +- client/: contains the client (front-end) ## Two servers -The project consists of two servers. The front-end uses a nodejs server. It is mainly used to compile your React code into Javascript files during development. This will shorten the feedback loop between changing your code and seeing the results in the browser. The second server is the back-end, which uses a Jetty server. The back-end server allows your Java API to be accessible for other programs, including the front-end server. To prevent [cross-origin request shenanigans (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), all requests from the browser will be sent to the front-end server. That server will then forward to the back-end server if needed. +The project consists of two servers. The front-end uses a Node.js server. It is mainly used to compile your React code into Javascript files during development. This will shorten the feedback loop between changing your code and seeing the results in the browser. The second server is the back-end, which uses a Jetty server. The back-end server allows your Java API to be accessible for other programs, including the front-end server. To prevent [cross-origin request shenanigans (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), all requests from the browser will be sent to the front-end server. That server will then forward to the back-end server if needed. The front-end assumes that the back-end will run on port 8080. If that is not the case, edit the snowpack.config.js file. +To run the application you need to have both servers running at the same time. This probably means you'll need to open two different terminals/command prompts to do so. + ## React project structure @@ -45,7 +47,13 @@ src/ The public directory contains static files, such as the relatively empty index.html file needed to run React. The src file contains the React code. The convention for TypeScript projects is to use the .tsx file extension for files that contain React components. Files are generally grouped together in directories by feature. These directories contain all files related to that feature, such as coponents, stylesheets, images and tests. -The package.json specifies which commands can be run using npm (e.g. npm run start). In this sample repository, two commands have been defined: +## Installating front-end dependencies + +To run the React application you'll first need to install the required dependencies. These dependencies are defined in the package.json file. Run the command `npm install` from the `/client` directory. + +## Running the front-end + +The package.json specifies which commands can be run using npm (e.g. npm run start). In this sample repository, two commands have been defined. You should also run these in the `/client` directory. ```bash # Start a development server -- cgit v1.2.3 From a8a6802755ddcecfb60878dd33503b2b44c05b96 Mon Sep 17 00:00:00 2001 From: "SOGYO\\bvdoord" Date: Mon, 22 Feb 2021 11:31:34 +0100 Subject: Fixed readme error --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 78573f5..a060a9a 100644 --- a/README.md +++ b/README.md @@ -103,15 +103,9 @@ If you run the program, you will notice the build "progress" is stuck on 87% or ## Assignment For the lecture, see [the drive](https://drive.google.com/drive/u/0/folders/1PvC-HS8ty3mdtSaNdR5rt5-GwL-5_LaY). -<<<<<<< HEAD The global goal is to make a web front-end to your mancala back-end. A stub has been made. In api/src/test you can find examples of how you can test the api endpoints. -======= - -The global goal is to make a web front-end to your mancala back-end. A stub has been made. In api/src/test you can find examples of how you can test the api endpoints. - ->>>>>>> 7b1b8a35db9699240b6c5243cd89ef0be7fd798a - Familiarise yourself with the repository. Get the servers running and make sure you can connect to both servers. Enter two names in the boxes. You should see a "TODO" screen. - Show the mancala game when it is started. - If you want to use your own implementation, reference your implemenation in the `MancalaImpl` class. -- cgit v1.2.3 From b1d70c635794ef33995d65f4d99c7914697bb5ec Mon Sep 17 00:00:00 2001 From: "SOGYO\\bvdoord" Date: Tue, 23 Feb 2021 11:13:18 +0100 Subject: Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a060a9a..c76d7b7 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ src/ The public directory contains static files, such as the relatively empty index.html file needed to run React. The src file contains the React code. The convention for TypeScript projects is to use the .tsx file extension for files that contain React components. Files are generally grouped together in directories by feature. These directories contain all files related to that feature, such as coponents, stylesheets, images and tests. -## Installating front-end dependencies +## Installing front-end dependencies To run the React application you'll first need to install the required dependencies. These dependencies are defined in the package.json file. Run the command `npm install` from the `/client` directory. -- cgit v1.2.3 From 77d64a8bcac263c4f399a6c05fb9efc1aa45071a Mon Sep 17 00:00:00 2001 From: Stijn Linderman Date: Mon, 12 Apr 2021 14:59:44 +0000 Subject: Update Mancala.java --- domain/src/main/java/mancala/domain/Mancala.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain/src/main/java/mancala/domain/Mancala.java b/domain/src/main/java/mancala/domain/Mancala.java index b8b8b7b..f8a2621 100644 --- a/domain/src/main/java/mancala/domain/Mancala.java +++ b/domain/src/main/java/mancala/domain/Mancala.java @@ -3,7 +3,7 @@ package mancala.domain; public interface Mancala { public static final int NO_PLAYERS = 0; public static final int PLAYER_ONE = 1; - public static final int PLAYER_TWO = 1; + public static final int PLAYER_TWO = 2; public static final int BOTH_PLAYERS = 3; /** @@ -52,4 +52,4 @@ public interface Mancala { */ int getWinner(); -} \ No newline at end of file +} -- cgit v1.2.3 From 0016be942179e08105c0cb4a5233e616786813e7 Mon Sep 17 00:00:00 2001 From: "SOGYO\\bvdoord" Date: Mon, 26 Apr 2021 08:45:38 +0200 Subject: Extra uitleg React hooks --- client/src/Mancala/Mancala.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/Mancala/Mancala.tsx b/client/src/Mancala/Mancala.tsx index 42c31a2..1c712c8 100644 --- a/client/src/Mancala/Mancala.tsx +++ b/client/src/Mancala/Mancala.tsx @@ -14,6 +14,10 @@ import "./Mancala.css"; */ export function Mancala() { + // useState is a so called React hook. + // It is used to manage variables. When the setGameState function is called, React updates the UI as needed + // The call to useState follows the "rules of hooks": https://reactjs.org/docs/hooks-rules.html + // To check if code you added also follows the rules of hooks, run "npm run lint" in the command line const [ gameState, setGameState ] = useState(undefined); if (!gameState) { -- cgit v1.2.3 From 653764b4fcd3fd951cb1ca7b7993a1736fd9dda6 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Mon, 21 Jun 2021 18:13:31 +0200 Subject: merge mainline domain with mvcFeature domain --- domain/build.gradle | 3 +- domain/src/main/java/mancala/domain/Bowl.java | 61 ++++ domain/src/main/java/mancala/domain/Kalaha.java | 51 ++++ domain/src/main/java/mancala/domain/Player.java | 52 ++++ domain/src/main/java/mancala/domain/SmallBowl.java | 114 ++++++++ domain/src/main/main.iml | 11 + domain/src/test/java/mancala/domain/BowlTest.java | 324 +++++++++++++++++++++ .../src/test/java/mancala/domain/PlayerTest.java | 46 +++ domain/src/test/test.iml | 28 ++ 9 files changed, 689 insertions(+), 1 deletion(-) create mode 100644 domain/src/main/java/mancala/domain/Bowl.java create mode 100644 domain/src/main/java/mancala/domain/Kalaha.java create mode 100644 domain/src/main/java/mancala/domain/Player.java create mode 100644 domain/src/main/java/mancala/domain/SmallBowl.java create mode 100644 domain/src/main/main.iml create mode 100644 domain/src/test/java/mancala/domain/BowlTest.java create mode 100644 domain/src/test/java/mancala/domain/PlayerTest.java create mode 100644 domain/src/test/test.iml diff --git a/domain/build.gradle b/domain/build.gradle index aea31e5..001d2dd 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -13,9 +13,10 @@ dependencies { // Download JUnit so that we can use it in our tests. testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' + testRuntimeOnly "org.junit.platform:junit-platform-commons:1.7.0" } test { // For running our tests, use the test runner provided by JUnit. useJUnitPlatform() -} \ No newline at end of file +} diff --git a/domain/src/main/java/mancala/domain/Bowl.java b/domain/src/main/java/mancala/domain/Bowl.java new file mode 100644 index 0000000..ade728d --- /dev/null +++ b/domain/src/main/java/mancala/domain/Bowl.java @@ -0,0 +1,61 @@ +package mancala.domain; + +abstract class Bowl { + protected int myRocks; + protected Player myOwner; + protected Bowl nextBowl; + + public int getMyRocks() { + return myRocks; + } + + public Bowl getNextBowl() { + return nextBowl; + } + + public Player getMyOwner() { + return myOwner; + } + + abstract void distribute(int remainingRocks); + + abstract SmallBowl getOpposite(int i); + + abstract SmallBowl getNextSmallBowlTimes(int i); + + abstract Kalaha getKalaha(); + + abstract SmallBowl getSmallBowl(); + + // abstract SmallBowl getNextSmallBowl(); + + void endTheGame() { + getNextBowl().endTheGame(this, 0, 0); + } + + abstract boolean isEmpty(); + + private void endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { + if (isEmpty() == false && myOwner.hasTheTurn()) return; + + if (getMyOwner().equals(startOfLoop.getMyOwner())) { + scorePlayer = scorePlayer + getMyRocks(); + } else scoreOpponent = scoreOpponent + getMyRocks(); + + if (this.equals(startOfLoop)) { + + int playerKalaha = getKalaha().getMyRocks(); + + if (scorePlayer == playerKalaha) { + + if (scorePlayer == scoreOpponent) getMyOwner().gotADraw(); + else if (scorePlayer > scoreOpponent) getMyOwner().isTheWinner(); + else getMyOwner().getOpponent().isTheWinner(); + + } + + + } else getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); + } + +} diff --git a/domain/src/main/java/mancala/domain/Kalaha.java b/domain/src/main/java/mancala/domain/Kalaha.java new file mode 100644 index 0000000..9e70e6c --- /dev/null +++ b/domain/src/main/java/mancala/domain/Kalaha.java @@ -0,0 +1,51 @@ +package mancala.domain; + +class Kalaha extends Bowl { + Kalaha(int boardSize, int bowlsToAdd, Bowl startBowl, Player playerOwningThisSide) { + bowlsToAdd = bowlsToAdd - 1; + + this.myRocks = 0; + this.myOwner = playerOwningThisSide; + + if (bowlsToAdd == 0) this.nextBowl = startBowl; + + else this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, startBowl, playerOwningThisSide.getOpponent()); + } + + Kalaha getKalaha() { + return this; + } + + SmallBowl getSmallBowl() { + return getNextBowl().getSmallBowl(); + } + + SmallBowl getOpposite(int countTillThis) { + return getNextBowl().getNextSmallBowlTimes(countTillThis - 1); + } + + SmallBowl getNextSmallBowlTimes(int i) { + return getNextBowl().getNextSmallBowlTimes(i); + } + + void distribute(int remainingRocks) { + myRocks++; + // Skip? + if (getMyOwner().hasTheTurn() == false) { + myRocks--; + getNextBowl().distribute(remainingRocks); + } else if (remainingRocks == 1) { + endTheGame(); + } else getNextBowl().distribute(--remainingRocks); + } + + @Override + boolean isEmpty() { + return true; + } + + + void claimStolenBooty(int booty) { + myRocks = myRocks + booty; + } +} diff --git a/domain/src/main/java/mancala/domain/Player.java b/domain/src/main/java/mancala/domain/Player.java new file mode 100644 index 0000000..1eef716 --- /dev/null +++ b/domain/src/main/java/mancala/domain/Player.java @@ -0,0 +1,52 @@ +package mancala.domain; + +public class Player { + private boolean hasTheTurn; + private boolean isTheWinner; + final private Player opponent; + + public Player() { + this.hasTheTurn = true; + + this.opponent = new Player(this); + } + + private Player(Player opponent) { + this.hasTheTurn = false; + + this.opponent = opponent; + } + + public Player getOpponent() { + return opponent; + } + + public boolean hasTheTurn() { + return hasTheTurn; + } + + public void switchTurn() { + if (this.hasTheTurn == true) { + this.hasTheTurn = false; + this.opponent.hasTheTurn = true; + } else { + this.hasTheTurn = true; + this.opponent.hasTheTurn = false; + } + } + + public boolean won() { + return this.isTheWinner; + } + + void isTheWinner() { + this.isTheWinner = true; + this.opponent.isTheWinner = false; + } + + void gotADraw() { + this.isTheWinner = true; + this.opponent.isTheWinner = true; + } + +} diff --git a/domain/src/main/java/mancala/domain/SmallBowl.java b/domain/src/main/java/mancala/domain/SmallBowl.java new file mode 100644 index 0000000..5c97d72 --- /dev/null +++ b/domain/src/main/java/mancala/domain/SmallBowl.java @@ -0,0 +1,114 @@ +package mancala.domain; + +public class SmallBowl extends Bowl { + + public SmallBowl() { + this.myRocks = 4; + this.myOwner = new Player(); + + int boardSize = 14; + int bowlsToAdd = boardSize - 1; + + this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, this, this.getMyOwner()); + } + + public SmallBowl(int boardSize) { + try { + if (boardSize < 4) { + throw new Exception("Can't have a board smaller than four bowls."); + } + } catch (Exception e) { + e.printStackTrace(); + } + this.myRocks = 4; + this.myOwner = new Player(); + int bowlsToAdd = boardSize - 1; + this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, this, this.getMyOwner()); + } + + SmallBowl(int boardSize, int bowlsToAdd, Bowl startBowl, Player playerOwningThisSide) { + bowlsToAdd = bowlsToAdd - 1; + this.myOwner = playerOwningThisSide; + this.myRocks = 4; + + if (bowlsToAdd == 0) nextBowl = startBowl; + + else if (bowlsToAdd == (boardSize / 2) + 1) nextBowl = new Kalaha(boardSize, bowlsToAdd, startBowl, playerOwningThisSide); + + else if (bowlsToAdd == 1) nextBowl = new Kalaha(boardSize, bowlsToAdd, startBowl, playerOwningThisSide); + + else nextBowl = new SmallBowl(boardSize, bowlsToAdd, startBowl, playerOwningThisSide); + } + + public SmallBowl getNextSmallBowlTimes(int remainingTimes) { + if (remainingTimes == 0) + return this; + else { + return getNextBowl().getNextSmallBowlTimes(--remainingTimes); + } + } + + public void play() { + if (myOwner.hasTheTurn() == false) return; + if (isEmpty()) return; + + int passThese = myRocks; + myRocks = 0; + getNextBowl().distribute(passThese); + } + + @Override + boolean isEmpty() { + return this.myRocks == 0; + } + + void distribute(int remainingRocks) { + this.myRocks++; + // last? + if (remainingRocks == 1) + lastSmallBowl(); + else { + getNextBowl().distribute(--remainingRocks); + } + } + + private void lastSmallBowl() { + // Did play end in smallbowl of my player? steal, otherwise do nothing + if (getMyOwner().hasTheTurn()) stealTheBooty(false); + + getMyOwner().switchTurn(); + + endTheGame(); + } + + SmallBowl getSmallBowl() { + return this; + } + + Kalaha getKalaha() { + return getNextBowl().getKalaha(); + } + + private void stealTheBooty(boolean victim) { + if (victim){ + getOpposite().getKalaha().claimStolenBooty(myRocks); + myRocks = 0; + + } else if (getMyRocks() == 1 && + getOpposite().getMyRocks() != 0) { + + getKalaha().claimStolenBooty(myRocks); + myRocks = 0; + getOpposite().stealTheBooty(true); + } + } + + SmallBowl getOpposite() { + return getOpposite(0); + } + + SmallBowl getOpposite(int count) { + count = count + 1; + return getNextBowl().getOpposite(count); + } +} diff --git a/domain/src/main/main.iml b/domain/src/main/main.iml new file mode 100644 index 0000000..908ad4f --- /dev/null +++ b/domain/src/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java new file mode 100644 index 0000000..530f628 --- /dev/null +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -0,0 +1,324 @@ +package mancala.domain; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + + +class BowlTest { + + protected void traverseAndCheckBoard(Bowl currentBowl, int position) { + Bowl initialBowl = currentBowl; + int traversedCount = 0; + int currentPosition = 0; + for (int i = 0; i < 14; i++) { + if ((position + traversedCount) > 14) { + // if looping around the board, position = ((start+traversed) - total) + // in other words the amount of bowls that the absolute position is greater than the board's total bowls + // + // Only relevant to check construction btw, also only checking in the case where there are 14 bowls + currentPosition = ((traversedCount + position) - 14); + } else + // Or just use normal position + currentPosition = position + traversedCount; + + // check for kalaha's, and check for smallbowl otherwise + if (currentPosition == 7 || currentPosition == 14) + assertEquals(currentBowl.getClass(), Kalaha.class); + else + assertEquals(currentBowl.getClass(), SmallBowl.class); + + currentBowl = currentBowl.getNextBowl(); + assertNotNull(currentBowl); + traversedCount++; + } + assertSame(initialBowl, currentBowl); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class For_a_normal_mancala_bowl{ + SmallBowl firstSmallBowlPlayer; + + @BeforeEach + public void makeASmallBowlInMancala() { + firstSmallBowlPlayer = new SmallBowl(); + traverseAndCheckBoard(firstSmallBowlPlayer, 1); + } + + @Nested + class GIVEN_its_the_start_of_the_game { + @Test + public void WHEN_before_any_small_bowls_are_played_THEN_has_four_rocks() { + Bowl current = firstSmallBowlPlayer; + for (int i = 0; i < 14; i++) { + current = current.getNextBowl(); + if (current.getClass() == SmallBowl.class) + assertEquals(current.getMyRocks(), 4); + } + assertSame(current, firstSmallBowlPlayer); + } + + @Test + public void WHEN_chosen_by_the_player_that_has_the_turn_THEN_distribute_its_rocks_anti_clockwise() { + int initialRocks = firstSmallBowlPlayer.getMyRocks(); + firstSmallBowlPlayer.play(); + Bowl neighbour = firstSmallBowlPlayer.getNextBowl(); + for (int i = 0; i < initialRocks; i++) { + assertEquals(5, neighbour.getMyRocks()); + neighbour = neighbour.getNextBowl(); + } + assertEquals(4, neighbour.getMyRocks()); + } + } + + @Nested + class GIVEN_the_game_is_in_a_state_where { + + @Test + public void its_not_the_players_turn_WHEN_played_by_the_player_THEN_nothing_happens() { + firstSmallBowlPlayer.getMyOwner().switchTurn(); + int initialRocks = firstSmallBowlPlayer.getMyRocks(); + firstSmallBowlPlayer.play(); + Bowl neighbour = firstSmallBowlPlayer.getNextBowl(); + for (int i = 0; i < initialRocks; i++) { + assertEquals(4, neighbour.getMyRocks()); + neighbour = neighbour.getNextBowl(); + } + assertEquals(4, neighbour.getMyRocks()); + } + + @Test + public void play_can_reach_opponents_kalaha_WHEN_played_by_the_player_THEN_opponents_kalaha_is_skipped() { + SmallBowl playWillSkipFromThisBowl = goToSkippableState(); + int opponentKalahaRocksBefore = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyRocks(); + playWillSkipFromThisBowl.play(); + int opponentKalahaRocksAfter = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyRocks(); + assertEquals(opponentKalahaRocksBefore, opponentKalahaRocksAfter); + } + + @Test + public void the_bowl_is_empty_WHEN_the_player_plays_the_empty_bowl_THEN_nothing_happens() { + firstSmallBowlPlayer.play(); + firstSmallBowlPlayer.getMyOwner().switchTurn(); + assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); + firstSmallBowlPlayer.play(); + assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); + assertEquals(5, firstSmallBowlPlayer.getNextBowl().getMyRocks()); + } + + @Test + public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_won() { + Player player = firstSmallBowlPlayer.getMyOwner(); + Player opponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6).getMyOwner(); + assertFalse(player.won()); + assertFalse(opponent.won()); + goToEndOfSillyGame(); + assertTrue(player.won()); + assertFalse(opponent.won()); + + } + + @Test + public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_wonOPPONENTVARIATION() { + Player player = firstSmallBowlPlayer.getMyOwner(); + Player opponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6).getMyOwner(); + goToEndOfGameWhereOpponentWins(); + assertFalse(player.won()); + assertTrue(opponent.won()); + } + + @Test + public void the_play_would_skip_past_opponent_kalaha_at_the_last_rock_and_steal_WHEN_played_THEN_should_skip_and_steal_correctly() { + goToSkipAndStealOnLast(); + SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); + assertEquals(3, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + } + + private void goToSkipAndStealOnLast() { + SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); + firstSmallBowlPlayer.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + firstSmallBowlPlayer.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.play(); + firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); + firstSmallBowlOpponent.play(); + // Cheating here, let player go again >:), i'm too dumb too make a loop/skip and steal play happen in fair game + firstSmallBowlOpponent.getMyOwner().switchTurn(); + // Should skip and steal + // this bowls rocks + assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyRocks()); + // End up here by looping around the board, thus skipping + assertEquals(0, firstSmallBowlOpponent.getMyRocks()); + // Thus steal from last bowl on players side + assertEquals(8, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyRocks()); + // Result is big kalaha booty + assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + } + + private void goToEndOfGameWhereOpponentWins() { + goToSkipAndStealOnLast(); + SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + firstSmallBowlPlayer.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + firstSmallBowlPlayer.play(); + firstSmallBowlPlayer.getMyOwner().switchTurn(); + firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); + firstSmallBowlPlayer.getMyOwner().switchTurn(); + firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); + firstSmallBowlPlayer.getNextSmallBowlTimes(5).play(); + } + + private void goToEndOfSillyGame() { + SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); + + // player + // Best opening + firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); + // Set up for steal move + firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); + assertEquals(2, firstSmallBowlPlayer.getKalaha().getMyRocks()); + + // opponent + // ... worst opening? + firstSmallBowlOpponent.play(); + + // player + assertSame(firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite(), firstSmallBowlPlayer.getKalaha().getNextBowl().getNextBowl()); + firstSmallBowlPlayer.play(); + // Check if i did it properly on paper + assertEquals(9, firstSmallBowlPlayer.getKalaha().getMyRocks()); + assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getMyRocks()); + // assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite().getMyRocks()); + + // opponent + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + + //Player + firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); + assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + + // opponent makes stupid move again + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + + // player makes big steal + //assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); + assertEquals(19, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + + // opponent steals tiny booty + firstSmallBowlOpponent.play(); + assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + + // player is stalling until the end + firstSmallBowlPlayer.play(); + + // opponent is heading for disaster + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + firstSmallBowlPlayer.play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); + firstSmallBowlPlayer.play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + // everything empty! + for (int i = 0; i < 6; i++) { + assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyRocks()); + } + + } + + private SmallBowl goToSkippableState() { + SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); + + firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); + firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); + + firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + + firstSmallBowlPlayer.play(); + firstSmallBowlOpponent.play(); + + firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); + + // Playing this bowl should give a skip! + assertTrue(firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyRocks() >= 8); + return firstSmallBowlPlayer.getNextSmallBowlTimes(5); + } + } + + @Nested + class GIVEN_the_play_ends{ + + @Test + public void in_own_kalaha_WHEN_play_ends_THEN_turn_is_not_switched() { + firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); + assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); + } + + @Test + public void in_own_small_bowl_WHEN_play_ends_THEN_turn_is_switched() { + firstSmallBowlPlayer.play(); + assertFalse(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); + } + + @Test + public void in_opponents_small_bowl_WHEN_player_plays_this_bowl_THEN_turn_is_switched() { + firstSmallBowlPlayer.getNextSmallBowlTimes(5).play(); + assertFalse(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); + } + + @Test + public void in_own_empty_small_bowl_and_opposite_has_rocks_WHEN_play_ends_THEN_rocks_of_opposite_plus_last_rock_of_play_are_added_to_kalaha() { + firstSmallBowlPlayer.getNextSmallBowlTimes(5).play(); + SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + assertSame(firstSmallBowlPlayer.getNextSmallBowlTimes(1).getOpposite(), firstSmallBowlPlayer.getKalaha().getSmallBowl().getNextSmallBowlTimes(4)); + // assertSame(firstSmallBowlPlayer.getOpposite(), firstSmallBowlPlayer.getKalaha().getNextSmallBowlTimes(5)); + firstSmallBowlPlayer.play(); + assertEquals(7, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + } + + } + + + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class a_kalaha { + + SmallBowl smallBowl; + Kalaha kalaha; + + @BeforeEach + public void makeKalahaInBoard() { + smallBowl = new SmallBowl(); + kalaha = smallBowl.getNextSmallBowlTimes(6).getKalaha(); + } + + @Test + public void exists_in_a_mancala_board() { + traverseAndCheckBoard(kalaha, 14); + } + + @Test + public void has_zero_rocks_when_created() { + Bowl current = kalaha; + for (int i = 0; i < 14; i++) { + current = current.getNextBowl(); + if (current.getClass() == Kalaha.class) + assertEquals(current.getMyRocks(), 0); + } + } + } +} \ No newline at end of file diff --git a/domain/src/test/java/mancala/domain/PlayerTest.java b/domain/src/test/java/mancala/domain/PlayerTest.java new file mode 100644 index 0000000..4de7837 --- /dev/null +++ b/domain/src/test/java/mancala/domain/PlayerTest.java @@ -0,0 +1,46 @@ +package mancala.domain; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("A player model ") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class PlayerTest { + Player player; + Player opponent; + + @BeforeEach + public void let_player_and_oppenent_exist() { + player = new Player(); + opponent = player.getOpponent(); + } + + @Test + public void has_turn_when_created() { + assertTrue(player.hasTheTurn()); + } + + @Test + public void opponent_does_not_have_turn_when_player_models_are_created() { + assertFalse(player.getOpponent().hasTheTurn()); + } + + @Test + public void can_change_turn_when_necessary() { + player.switchTurn(); + assertFalse(player.hasTheTurn()); + assertTrue(player.getOpponent().hasTheTurn()); + } + + @Test + public void knows_when_it_is_the_winner() { + assertFalse(player.won()); + player.isTheWinner(); + assertTrue(player.won()); + assertFalse(opponent.won()); + opponent.isTheWinner(); + assertTrue(opponent.won()); + assertFalse(player.won()); + } +} \ No newline at end of file diff --git a/domain/src/test/test.iml b/domain/src/test/test.iml new file mode 100644 index 0000000..8cc6e96 --- /dev/null +++ b/domain/src/test/test.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3 From 431af32d73a37887fa7d35f371a21260f5e93949 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 15:33:33 +0200 Subject: refactoring endgame --- client/src/Mancala/Play.tsx | 2 +- domain/build.gradle | 4 +- domain/src/main/java/mancala/domain/Bowl.java | 21 +- domain/src/main/java/mancala/domain/Foo.java | 11 -- domain/src/main/java/mancala/domain/Kalaha.java | 9 + .../src/main/java/mancala/domain/MancalaImpl.java | 66 ++++++- domain/src/main/java/mancala/domain/SmallBowl.java | 13 +- domain/src/test/java/mancala/domain/BowlTest.java | 54 +++--- domain/src/test/java/mancala/domain/FooTest.java | 23 --- .../test/java/mancala/domain/MancalaImplTest.java | 214 +++++++++++++++++++++ 10 files changed, 336 insertions(+), 81 deletions(-) delete mode 100644 domain/src/main/java/mancala/domain/Foo.java delete mode 100644 domain/src/test/java/mancala/domain/FooTest.java create mode 100644 domain/src/test/java/mancala/domain/MancalaImplTest.java diff --git a/client/src/Mancala/Play.tsx b/client/src/Mancala/Play.tsx index 3b8ff25..b72bce1 100644 --- a/client/src/Mancala/Play.tsx +++ b/client/src/Mancala/Play.tsx @@ -14,4 +14,4 @@ export function Play({ gameState, setGameState }: PlayProps) { To do... ) -} \ No newline at end of file +} diff --git a/domain/build.gradle b/domain/build.gradle index 001d2dd..fe53df0 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -11,8 +11,8 @@ repositories { dependencies { // Download JUnit so that we can use it in our tests. - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' testRuntimeOnly "org.junit.platform:junit-platform-commons:1.7.0" } diff --git a/domain/src/main/java/mancala/domain/Bowl.java b/domain/src/main/java/mancala/domain/Bowl.java index ade728d..a7a2e78 100644 --- a/domain/src/main/java/mancala/domain/Bowl.java +++ b/domain/src/main/java/mancala/domain/Bowl.java @@ -5,7 +5,7 @@ abstract class Bowl { protected Player myOwner; protected Bowl nextBowl; - public int getMyRocks() { + public int getMyStones() { return myRocks; } @@ -27,24 +27,25 @@ abstract class Bowl { abstract SmallBowl getSmallBowl(); + abstract SmallBowl goToFirstBowlOfPlayerWithTurn(); + + abstract boolean isEmpty(); // abstract SmallBowl getNextSmallBowl(); void endTheGame() { - getNextBowl().endTheGame(this, 0, 0); + goToFirstBowlOfPlayerWithTurn().getNextBowl().endTheGame(goToFirstBowlOfPlayerWithTurn(), 0, 0); } - abstract boolean isEmpty(); - - private void endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { - if (isEmpty() == false && myOwner.hasTheTurn()) return; + protected void endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { + if (isEmpty() == false) return; if (getMyOwner().equals(startOfLoop.getMyOwner())) { - scorePlayer = scorePlayer + getMyRocks(); - } else scoreOpponent = scoreOpponent + getMyRocks(); + scorePlayer = scorePlayer + getMyStones(); + } else scoreOpponent = scoreOpponent + getMyStones(); if (this.equals(startOfLoop)) { - int playerKalaha = getKalaha().getMyRocks(); + int playerKalaha = getKalaha().getMyStones(); if (scorePlayer == playerKalaha) { @@ -58,4 +59,6 @@ abstract class Bowl { } else getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); } + + } diff --git a/domain/src/main/java/mancala/domain/Foo.java b/domain/src/main/java/mancala/domain/Foo.java deleted file mode 100644 index bcce6fa..0000000 --- a/domain/src/main/java/mancala/domain/Foo.java +++ /dev/null @@ -1,11 +0,0 @@ -package mancala.domain; - -// Make your own mancala implementation using your design. -// You can take this stub as an example how to make a -// class inside a package and how to test it. -public class Foo { - - public int theAnswerToLifeTheUniverseAndEverything() { - return 41; - } -} \ No newline at end of file diff --git a/domain/src/main/java/mancala/domain/Kalaha.java b/domain/src/main/java/mancala/domain/Kalaha.java index 9e70e6c..7ab954c 100644 --- a/domain/src/main/java/mancala/domain/Kalaha.java +++ b/domain/src/main/java/mancala/domain/Kalaha.java @@ -20,6 +20,15 @@ class Kalaha extends Bowl { return getNextBowl().getSmallBowl(); } + @Override + SmallBowl goToFirstBowlOfPlayerWithTurn() { + if (getMyOwner().hasTheTurn()) { + return getNextBowl().getKalaha().getSmallBowl(); + } else { + return getSmallBowl(); + } + } + SmallBowl getOpposite(int countTillThis) { return getNextBowl().getNextSmallBowlTimes(countTillThis - 1); } diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java index de04c8f..81dbbe8 100644 --- a/domain/src/main/java/mancala/domain/MancalaImpl.java +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -1,29 +1,83 @@ package mancala.domain; +import java.util.HashSet; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + public class MancalaImpl implements Mancala { + + public static HashSet PLAYER_ONE_PITS = new HashSet<>( + IntStream.rangeClosed(0, 5).boxed().collect(Collectors.toList()) + ); + public static HashSet PLAYER_TWO_PITS = new HashSet<>( + IntStream.rangeClosed(7, 12).boxed().collect(Collectors.toList()) + ); + public static int PLAYER_ONE_KALAHA = 6; + public static int PLAYER_TWO_KALAHA = 13; + + private SmallBowl domainReference; + public MancalaImpl() { - // Initialize the game here. + domainReference = new SmallBowl(); } @Override public boolean isPlayersTurn(int player) { - return true; + switch (player) { + case Mancala.PLAYER_ONE: + return domainReference.getMyOwner().hasTheTurn(); + case Mancala.PLAYER_TWO: + return domainReference.getMyOwner().getOpponent().hasTheTurn(); + default: + return false; + } } @Override public void playPit(int index) throws MancalaException { - // Implement playing a pit. + if (isPlayersTurn(Mancala.PLAYER_ONE) && MancalaImpl.PLAYER_TWO_PITS.contains(index)) { + throw new MancalaException("Player one cannot play player two's pits."); + } + if (isPlayersTurn(Mancala.PLAYER_TWO) && MancalaImpl.PLAYER_ONE_PITS.contains(index)) { + throw new MancalaException("Player two cannot play player one's pits."); + } + if (index == MancalaImpl.PLAYER_ONE_KALAHA || index == MancalaImpl.PLAYER_TWO_KALAHA) { + throw new MancalaException("A kalaha can never be played!"); + } + if (getStonesForPit(index) == 0) { + throw new MancalaException("The pit was empty when played!"); + } + + if (isPlayersTurn(Mancala.PLAYER_ONE)) { + domainReference.getNextSmallBowlTimes(index).play(); + } else { + int skipKalahaIndex = index - 1; + domainReference.getNextSmallBowlTimes(skipKalahaIndex).play(); + } + } @Override public int getStonesForPit(int index) { - // Make a sane implementation. - if((index + 1 % 7) == 0) return 0; - return 4; + if (MancalaImpl.PLAYER_ONE_PITS.contains(index)) { + return domainReference.getNextSmallBowlTimes(index).getMyStones(); + } + else if (MancalaImpl.PLAYER_TWO_PITS.contains(index)) { + return domainReference.getNextSmallBowlTimes(--index).getMyStones(); + } + else if (index == MancalaImpl.PLAYER_ONE_KALAHA) { + return domainReference.getKalaha().getMyStones(); + } + else if (index == MancalaImpl.PLAYER_TWO_KALAHA) { + return domainReference.getKalaha().getNextBowl().getKalaha().getMyStones(); + } + else + return -1; } @Override public boolean isEndOfGame() { + //domainReference.endTheGame(); return false; } diff --git a/domain/src/main/java/mancala/domain/SmallBowl.java b/domain/src/main/java/mancala/domain/SmallBowl.java index 5c97d72..ce4c0ce 100644 --- a/domain/src/main/java/mancala/domain/SmallBowl.java +++ b/domain/src/main/java/mancala/domain/SmallBowl.java @@ -85,6 +85,15 @@ public class SmallBowl extends Bowl { return this; } + @Override + SmallBowl goToFirstBowlOfPlayerWithTurn() { + if (getMyOwner().hasTheTurn()) { + return getKalaha().getNextBowl().getKalaha().getSmallBowl(); + } else { + return getKalaha().getSmallBowl(); + } + } + Kalaha getKalaha() { return getNextBowl().getKalaha(); } @@ -94,8 +103,8 @@ public class SmallBowl extends Bowl { getOpposite().getKalaha().claimStolenBooty(myRocks); myRocks = 0; - } else if (getMyRocks() == 1 && - getOpposite().getMyRocks() != 0) { + } else if (getMyStones() == 1 && + getOpposite().getMyStones() != 0) { getKalaha().claimStolenBooty(myRocks); myRocks = 0; diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java index 530f628..34ca42d 100644 --- a/domain/src/test/java/mancala/domain/BowlTest.java +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -54,21 +54,21 @@ class BowlTest { for (int i = 0; i < 14; i++) { current = current.getNextBowl(); if (current.getClass() == SmallBowl.class) - assertEquals(current.getMyRocks(), 4); + assertEquals(current.getMyStones(), 4); } assertSame(current, firstSmallBowlPlayer); } @Test public void WHEN_chosen_by_the_player_that_has_the_turn_THEN_distribute_its_rocks_anti_clockwise() { - int initialRocks = firstSmallBowlPlayer.getMyRocks(); + int initialRocks = firstSmallBowlPlayer.getMyStones(); firstSmallBowlPlayer.play(); Bowl neighbour = firstSmallBowlPlayer.getNextBowl(); for (int i = 0; i < initialRocks; i++) { - assertEquals(5, neighbour.getMyRocks()); + assertEquals(5, neighbour.getMyStones()); neighbour = neighbour.getNextBowl(); } - assertEquals(4, neighbour.getMyRocks()); + assertEquals(4, neighbour.getMyStones()); } } @@ -78,22 +78,22 @@ class BowlTest { @Test public void its_not_the_players_turn_WHEN_played_by_the_player_THEN_nothing_happens() { firstSmallBowlPlayer.getMyOwner().switchTurn(); - int initialRocks = firstSmallBowlPlayer.getMyRocks(); + int initialRocks = firstSmallBowlPlayer.getMyStones(); firstSmallBowlPlayer.play(); Bowl neighbour = firstSmallBowlPlayer.getNextBowl(); for (int i = 0; i < initialRocks; i++) { - assertEquals(4, neighbour.getMyRocks()); + assertEquals(4, neighbour.getMyStones()); neighbour = neighbour.getNextBowl(); } - assertEquals(4, neighbour.getMyRocks()); + assertEquals(4, neighbour.getMyStones()); } @Test public void play_can_reach_opponents_kalaha_WHEN_played_by_the_player_THEN_opponents_kalaha_is_skipped() { SmallBowl playWillSkipFromThisBowl = goToSkippableState(); - int opponentKalahaRocksBefore = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyRocks(); + int opponentKalahaRocksBefore = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); playWillSkipFromThisBowl.play(); - int opponentKalahaRocksAfter = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyRocks(); + int opponentKalahaRocksAfter = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); assertEquals(opponentKalahaRocksBefore, opponentKalahaRocksAfter); } @@ -104,7 +104,7 @@ class BowlTest { assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); firstSmallBowlPlayer.play(); assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); - assertEquals(5, firstSmallBowlPlayer.getNextBowl().getMyRocks()); + assertEquals(5, firstSmallBowlPlayer.getNextBowl().getMyStones()); } @Test @@ -132,9 +132,9 @@ class BowlTest { public void the_play_would_skip_past_opponent_kalaha_at_the_last_rock_and_steal_WHEN_played_THEN_should_skip_and_steal_correctly() { goToSkipAndStealOnLast(); SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); - assertEquals(3, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(3, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); } private void goToSkipAndStealOnLast() { @@ -154,13 +154,13 @@ class BowlTest { firstSmallBowlOpponent.getMyOwner().switchTurn(); // Should skip and steal // this bowls rocks - assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyRocks()); + assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyStones()); // End up here by looping around the board, thus skipping - assertEquals(0, firstSmallBowlOpponent.getMyRocks()); + assertEquals(0, firstSmallBowlOpponent.getMyStones()); // Thus steal from last bowl on players side - assertEquals(8, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyRocks()); + assertEquals(8, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyStones()); // Result is big kalaha booty - assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); } private void goToEndOfGameWhereOpponentWins() { @@ -185,7 +185,7 @@ class BowlTest { firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); // Set up for steal move firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); - assertEquals(2, firstSmallBowlPlayer.getKalaha().getMyRocks()); + assertEquals(2, firstSmallBowlPlayer.getKalaha().getMyStones()); // opponent // ... worst opening? @@ -195,8 +195,8 @@ class BowlTest { assertSame(firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite(), firstSmallBowlPlayer.getKalaha().getNextBowl().getNextBowl()); firstSmallBowlPlayer.play(); // Check if i did it properly on paper - assertEquals(9, firstSmallBowlPlayer.getKalaha().getMyRocks()); - assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getMyRocks()); + assertEquals(9, firstSmallBowlPlayer.getKalaha().getMyStones()); + assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getMyStones()); // assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite().getMyRocks()); // opponent @@ -204,20 +204,20 @@ class BowlTest { //Player firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); - assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); // opponent makes stupid move again firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); // player makes big steal //assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); - assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); - assertEquals(19, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(19, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); // opponent steals tiny booty firstSmallBowlOpponent.play(); - assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); // player is stalling until the end firstSmallBowlPlayer.play(); @@ -230,7 +230,7 @@ class BowlTest { firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); // everything empty! for (int i = 0; i < 6; i++) { - assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyRocks()); + assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyStones()); } } @@ -251,7 +251,7 @@ class BowlTest { firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); // Playing this bowl should give a skip! - assertTrue(firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyRocks() >= 8); + assertTrue(firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyStones() >= 8); return firstSmallBowlPlayer.getNextSmallBowlTimes(5); } } @@ -285,7 +285,7 @@ class BowlTest { assertSame(firstSmallBowlPlayer.getNextSmallBowlTimes(1).getOpposite(), firstSmallBowlPlayer.getKalaha().getSmallBowl().getNextSmallBowlTimes(4)); // assertSame(firstSmallBowlPlayer.getOpposite(), firstSmallBowlPlayer.getKalaha().getNextSmallBowlTimes(5)); firstSmallBowlPlayer.play(); - assertEquals(7, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(7, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); } } @@ -317,7 +317,7 @@ class BowlTest { for (int i = 0; i < 14; i++) { current = current.getNextBowl(); if (current.getClass() == Kalaha.class) - assertEquals(current.getMyRocks(), 0); + assertEquals(current.getMyStones(), 0); } } } diff --git a/domain/src/test/java/mancala/domain/FooTest.java b/domain/src/test/java/mancala/domain/FooTest.java deleted file mode 100644 index fa784d3..0000000 --- a/domain/src/test/java/mancala/domain/FooTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package mancala.domain; - -// Your test class should be in the same -// package as the class you're testing. -// Usually the test directory mirrors the -// main directory 1:1. So for each class in src/main, -// there is a class in src/test. - -// Import our test dependencies. We import the Test-attribute -// and a set of assertions. -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -public class FooTest { - // Define a test starting with @Test. The test is like - // a small main method - you need to setup everything - // and you can write any arbitrary Java code in it. - @Test - public void aNormalBorlStartsWith4Stones() { - Foo foo = new Foo(); - assertEquals(42, foo.theAnswerToLifeTheUniverseAndEverything()); - } -} \ No newline at end of file diff --git a/domain/src/test/java/mancala/domain/MancalaImplTest.java b/domain/src/test/java/mancala/domain/MancalaImplTest.java new file mode 100644 index 0000000..b040b02 --- /dev/null +++ b/domain/src/test/java/mancala/domain/MancalaImplTest.java @@ -0,0 +1,214 @@ +package mancala.domain; + +import org.junit.jupiter.api.*; + +import java.util.HashSet; + +import static org.junit.jupiter.api.Assertions.*; + +@IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) +class MancalaImplTest { + + Mancala mancala; + + @BeforeEach + void setUp() { + mancala = new MancalaImpl(); + } + + /** + * Method indicating if the first player has the next turn or not. + * If player 1 is not in turn, then player 2 is in turn. + * + * @param The player which you want to know the turn for. + * @return True if the first player has the next turn, false if it's the turn of the other player. + */ + @Nested + class isPlayersTurn { + @Test + void given_mancala_PLAYER_ONE_when_first_player_has_next_turn_then_return_true() { + assertTrue(mancala.isPlayersTurn(Mancala.PLAYER_ONE)); + } + + @Test + void given_mancala_PLAYER_ONE_when_second_player_has_next_turn_then_return_false() { + playPitAndFailIfNotValid(0); + assertFalse(mancala.isPlayersTurn(Mancala.PLAYER_ONE)); + } + + + @Test + void given_mancala_PLAYER_TWO_when_second_player_has_next_turn_then_return_true() { + playPitAndFailIfNotValid(0); + assertTrue(mancala.isPlayersTurn(Mancala.PLAYER_TWO)); + } + + @Test + void given_mancala_PLAYER_TWO_when_second_player_has_turn_then_return_false() { + assertFalse(mancala.isPlayersTurn(Mancala.PLAYER_TWO)); + } + } + + /** + * Method for playing the specified recess. Index is as specified below: + * + * 12 11 10 9 8 7 + * 13 6 + * 0 1 2 3 4 5 + * + * @param index Index of the recess to be played. + * @return 15 item long Array with the current state of the game. The 15th item indicates which player has the next turn (possible values are 1 or 2). + */ + @Nested + class playPit { + @Test + void given_a_pit_index_from_PLAYER_one_choice_when_pit_doesnt_belong_to_player_one_then_throw_MancalaException() { + for (int index: MancalaImpl.PLAYER_TWO_PITS) { + playPitAndFailIfNoException(index, "Player one could play a pit that was not his without an exception!"); + } + } + + @Test + void given_a_pit_index_from_PLAYER_two_choice_when_pit_doesnt_belong_to_player_two_then_throw_MancalaException() { + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNotValid(0); + + assumeTurn(Mancala.PLAYER_TWO); + for (int index: MancalaImpl.PLAYER_ONE_PITS) { + playPitAndFailIfNoException(index, "Player two could play a pit that was not his without an exception!"); + } + } + + @Test + void given_a_Kalaha_index_when_playPit_is_called_then_always_throw_MancalaException() { + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNoException(MancalaImpl.PLAYER_ONE_KALAHA, "Kalaha of player one was played without throwing exception!"); + playPitAndFailIfNoException(MancalaImpl.PLAYER_TWO_KALAHA, "Kalaha of player two was played without throwing exception!"); + + playPitAndFailIfNotValid(0); + + assumeTurn(Mancala.PLAYER_TWO); + playPitAndFailIfNoException(MancalaImpl.PLAYER_ONE_KALAHA, "Kalaha of player one was played without throwing exception!"); + playPitAndFailIfNoException(MancalaImpl.PLAYER_TWO_KALAHA, "Kalaha of player two was played without throwing exception!"); + } + + @Test + void given_that_valid_play_is_made_when_play_is_done_then_players_switch_turns() { + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNotValid(0); + + assumeNotTurn(Mancala.PLAYER_ONE); + assumeTurn(Mancala.PLAYER_TWO); + } + + @Test + void given_that_pit_has_no_stones_when_play_is_made_by_player_one_then_throw_MancalaException() { + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNotValid(0); + + assumeTurn(Mancala.PLAYER_TWO); + playPitAndFailIfNotValid(7); + + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNoException(0, "Didn't throw exception when Pit was empty when played!"); + } + + @Test + void given_that_pit_has_no_stones_when_play_is_made_by_player_two_then_throw_MancalaException() { + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNotValid(5); + + assumeTurn(Mancala.PLAYER_TWO); + playPitAndFailIfNotValid(7); + + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNotValid(0); + + assumeTurn(Mancala.PLAYER_TWO); + playPitAndFailIfNoException(7, "Didn't throw exception when Pit was empty when played!"); + } + + } + + @Nested + class getStonesForPit { + @Test + void given_any_Pit_index_when_getStonesForPit_is_called_then_return_stones_of_pits() { + HashSet allPitIndexes = new HashSet<>(MancalaImpl.PLAYER_ONE_PITS); + allPitIndexes.addAll(MancalaImpl.PLAYER_TWO_PITS); + + for (int pitIndex: allPitIndexes) { + assertEquals(4, mancala.getStonesForPit(pitIndex), + "pit should have four stones when created in the domain model."); + } + } + + @Test + void given_any_Kalaha_index_when_getStonesForPit_is_called_then_return_stones_of_Kalaha() { + HashSet allKalahaIndexes = new HashSet<>(); + allKalahaIndexes.add(MancalaImpl.PLAYER_ONE_KALAHA); + allKalahaIndexes.add(MancalaImpl.PLAYER_TWO_KALAHA); + for (int kalahaIndex: allKalahaIndexes) { + assertEquals(0, mancala.getStonesForPit(kalahaIndex), + "Kalaha should have zero stones when created in the domain model."); + } + } + + @Test + void given_that_a_play_has_been_made_and_not_looped_around_board_when_getting_stones_then_should_give_zero() { + assumeTurn(Mancala.PLAYER_ONE); + playPitAndFailIfNotValid(5); + + assertEquals(0, mancala.getStonesForPit(5), + "In this situation play should leave zero rocks!"); + + assumeTurn(Mancala.PLAYER_TWO); + playPitAndFailIfNotValid(7); + + assertEquals(0, mancala.getStonesForPit(7), + "In this situation play should leave zero rocks!"); + } + + } + + @Nested + class isEndOfGame { + void given_the_game_is_not_ended_when_isEndOfGame_is_called_then_return_false() { + assertFalse(mancala.isEndOfGame()); + } + } + + @Nested + class getWinner { + } + + void assumeTurn(int player) { + assertTrue(mancala.isPlayersTurn(player), + "It's not PLAYER " + (player == 1 ? "ONE's" : "TWO's") + " turn!"); + } + + void assumeNotTurn(int player) { + assertFalse(mancala.isPlayersTurn(player), + "It's PLAYER " + (player == 1 ? "ONE's" : "TWO's") + " turn!"); + } + + void playPitAndFailIfNotValid(int index) { + try { + mancala.playPit(index); + } catch (MancalaException e) { + fail("Invalid play."); + } + } + + void playPitAndFailIfNoException(int index, String message) { + try { + mancala.playPit(index); + fail(message); + } catch (MancalaException e) { + } + } + + void notImplementedYet() { + fail("Not implemented yet."); + } +} \ No newline at end of file -- cgit v1.2.3 From e410bb8ccd47d7e9e58379dad5b138692de083e5 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 15:39:30 +0200 Subject: finished refactor endTheGame --- domain/src/main/java/mancala/domain/Bowl.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/domain/src/main/java/mancala/domain/Bowl.java b/domain/src/main/java/mancala/domain/Bowl.java index a7a2e78..a29b806 100644 --- a/domain/src/main/java/mancala/domain/Bowl.java +++ b/domain/src/main/java/mancala/domain/Bowl.java @@ -37,11 +37,7 @@ abstract class Bowl { } protected void endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { - if (isEmpty() == false) return; - - if (getMyOwner().equals(startOfLoop.getMyOwner())) { - scorePlayer = scorePlayer + getMyStones(); - } else scoreOpponent = scoreOpponent + getMyStones(); + if (isEmpty() == false && getMyOwner().equals(startOfLoop.getMyOwner())) return; if (this.equals(startOfLoop)) { @@ -56,7 +52,13 @@ abstract class Bowl { } - } else getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); + } else { + if (getMyOwner().equals(startOfLoop.getMyOwner())) { + scorePlayer = scorePlayer + getMyStones(); + } else scoreOpponent = scoreOpponent + getMyStones(); + + getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); + } } -- cgit v1.2.3 From 17b594e943eaccc69b6036e258308d1a1ddad53f Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 15:42:54 +0200 Subject: refactor(endTheGame) <- returns boolean --- domain/src/main/java/mancala/domain/Bowl.java | 12 +++++++----- domain/src/main/java/mancala/domain/MancalaImpl.java | 3 +-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/domain/src/main/java/mancala/domain/Bowl.java b/domain/src/main/java/mancala/domain/Bowl.java index a29b806..fece393 100644 --- a/domain/src/main/java/mancala/domain/Bowl.java +++ b/domain/src/main/java/mancala/domain/Bowl.java @@ -32,12 +32,12 @@ abstract class Bowl { abstract boolean isEmpty(); // abstract SmallBowl getNextSmallBowl(); - void endTheGame() { - goToFirstBowlOfPlayerWithTurn().getNextBowl().endTheGame(goToFirstBowlOfPlayerWithTurn(), 0, 0); + boolean endTheGame() { + return goToFirstBowlOfPlayerWithTurn().getNextBowl().endTheGame(goToFirstBowlOfPlayerWithTurn(), 0, 0); } - protected void endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { - if (isEmpty() == false && getMyOwner().equals(startOfLoop.getMyOwner())) return; + protected boolean endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { + if (isEmpty() == false && getMyOwner().equals(startOfLoop.getMyOwner())) return false; if (this.equals(startOfLoop)) { @@ -49,6 +49,7 @@ abstract class Bowl { else if (scorePlayer > scoreOpponent) getMyOwner().isTheWinner(); else getMyOwner().getOpponent().isTheWinner(); + return true; } @@ -57,8 +58,9 @@ abstract class Bowl { scorePlayer = scorePlayer + getMyStones(); } else scoreOpponent = scoreOpponent + getMyStones(); - getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); + return getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); } + return false; } diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java index 81dbbe8..82f914f 100644 --- a/domain/src/main/java/mancala/domain/MancalaImpl.java +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -77,8 +77,7 @@ public class MancalaImpl implements Mancala { @Override public boolean isEndOfGame() { - //domainReference.endTheGame(); - return false; + return domainReference.endTheGame(); } @Override -- cgit v1.2.3 From 92c61668daefe1486d8ce6aefcf4c60c2eee0c3b Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 17:11:42 +0200 Subject: refactor(domain tests) <- configurable state --- domain/src/main/java/mancala/domain/Bowl.java | 19 +- domain/src/main/java/mancala/domain/Kalaha.java | 22 +- .../src/main/java/mancala/domain/MancalaImpl.java | 25 +- domain/src/main/java/mancala/domain/SmallBowl.java | 63 ++- domain/src/test/java/mancala/domain/BowlTest.java | 547 +++++++++++---------- .../test/java/mancala/domain/MancalaImplTest.java | 23 + 6 files changed, 378 insertions(+), 321 deletions(-) diff --git a/domain/src/main/java/mancala/domain/Bowl.java b/domain/src/main/java/mancala/domain/Bowl.java index fece393..8a214da 100644 --- a/domain/src/main/java/mancala/domain/Bowl.java +++ b/domain/src/main/java/mancala/domain/Bowl.java @@ -1,12 +1,12 @@ package mancala.domain; abstract class Bowl { - protected int myRocks; + protected int myStones; protected Player myOwner; protected Bowl nextBowl; public int getMyStones() { - return myRocks; + return myStones; } public Bowl getNextBowl() { @@ -25,19 +25,19 @@ abstract class Bowl { abstract Kalaha getKalaha(); - abstract SmallBowl getSmallBowl(); + abstract SmallBowl getNextSmallBowl(); abstract SmallBowl goToFirstBowlOfPlayerWithTurn(); abstract boolean isEmpty(); // abstract SmallBowl getNextSmallBowl(); - boolean endTheGame() { - return goToFirstBowlOfPlayerWithTurn().getNextBowl().endTheGame(goToFirstBowlOfPlayerWithTurn(), 0, 0); + void endTheGame() { + goToFirstBowlOfPlayerWithTurn().getNextBowl().endTheGame(goToFirstBowlOfPlayerWithTurn(), 0, 0); } - protected boolean endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { - if (isEmpty() == false && getMyOwner().equals(startOfLoop.getMyOwner())) return false; + protected void endTheGame(Bowl startOfLoop, int scorePlayer, int scoreOpponent) { + if (isEmpty() == false && getMyOwner().equals(startOfLoop.getMyOwner())) return; if (this.equals(startOfLoop)) { @@ -48,8 +48,6 @@ abstract class Bowl { if (scorePlayer == scoreOpponent) getMyOwner().gotADraw(); else if (scorePlayer > scoreOpponent) getMyOwner().isTheWinner(); else getMyOwner().getOpponent().isTheWinner(); - - return true; } @@ -58,9 +56,8 @@ abstract class Bowl { scorePlayer = scorePlayer + getMyStones(); } else scoreOpponent = scoreOpponent + getMyStones(); - return getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); + getNextBowl().endTheGame(startOfLoop, scorePlayer, scoreOpponent); } - return false; } diff --git a/domain/src/main/java/mancala/domain/Kalaha.java b/domain/src/main/java/mancala/domain/Kalaha.java index 7ab954c..a13ebd8 100644 --- a/domain/src/main/java/mancala/domain/Kalaha.java +++ b/domain/src/main/java/mancala/domain/Kalaha.java @@ -1,31 +1,33 @@ package mancala.domain; +import java.util.List; + class Kalaha extends Bowl { - Kalaha(int boardSize, int bowlsToAdd, Bowl startBowl, Player playerOwningThisSide) { + Kalaha(int boardSize, int bowlsToAdd, List stonesList, Bowl startBowl, Player playerOwningThisSide) { bowlsToAdd = bowlsToAdd - 1; - this.myRocks = 0; + this.myStones = stonesList.remove(0); this.myOwner = playerOwningThisSide; if (bowlsToAdd == 0) this.nextBowl = startBowl; - else this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, startBowl, playerOwningThisSide.getOpponent()); + else this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, stonesList, startBowl, playerOwningThisSide.getOpponent()); } Kalaha getKalaha() { return this; } - SmallBowl getSmallBowl() { - return getNextBowl().getSmallBowl(); + SmallBowl getNextSmallBowl() { + return getNextBowl().getNextSmallBowl(); } @Override SmallBowl goToFirstBowlOfPlayerWithTurn() { if (getMyOwner().hasTheTurn()) { - return getNextBowl().getKalaha().getSmallBowl(); + return getNextBowl().getKalaha().getNextSmallBowl(); } else { - return getSmallBowl(); + return getNextSmallBowl(); } } @@ -38,10 +40,10 @@ class Kalaha extends Bowl { } void distribute(int remainingRocks) { - myRocks++; + myStones++; // Skip? if (getMyOwner().hasTheTurn() == false) { - myRocks--; + myStones--; getNextBowl().distribute(remainingRocks); } else if (remainingRocks == 1) { endTheGame(); @@ -55,6 +57,6 @@ class Kalaha extends Bowl { void claimStolenBooty(int booty) { - myRocks = myRocks + booty; + myStones = myStones + booty; } } diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java index 82f914f..29048ff 100644 --- a/domain/src/main/java/mancala/domain/MancalaImpl.java +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -6,19 +6,21 @@ import java.util.stream.IntStream; public class MancalaImpl implements Mancala { - public static HashSet PLAYER_ONE_PITS = new HashSet<>( + public static final HashSet PLAYER_ONE_PITS = new HashSet<>( IntStream.rangeClosed(0, 5).boxed().collect(Collectors.toList()) ); - public static HashSet PLAYER_TWO_PITS = new HashSet<>( + public static final HashSet PLAYER_TWO_PITS = new HashSet<>( IntStream.rangeClosed(7, 12).boxed().collect(Collectors.toList()) ); - public static int PLAYER_ONE_KALAHA = 6; - public static int PLAYER_TWO_KALAHA = 13; + public static final int PLAYER_ONE_KALAHA = 6; + public static final int PLAYER_TWO_KALAHA = 13; private SmallBowl domainReference; public MancalaImpl() { domainReference = new SmallBowl(); + domainPlayer = domainReference.getMyOwner(); + domainOpponent = domainPlayer.getOpponent(); } @Override @@ -75,13 +77,22 @@ public class MancalaImpl implements Mancala { return -1; } - @Override + private final mancala.domain.Player domainPlayer; + private final mancala.domain.Player domainOpponent; + + @Override public boolean isEndOfGame() { - return domainReference.endTheGame(); + return domainPlayer.won() || domainOpponent.won(); } @Override public int getWinner() { - return Mancala.NO_PLAYERS; + if (!isEndOfGame()) return Mancala.NO_PLAYERS; + + if (domainPlayer.won()) return Mancala.PLAYER_ONE; + else if (domainOpponent.won()) return Mancala.PLAYER_TWO; + else if (domainPlayer.won() && domainOpponent.won()) return Mancala.BOTH_PLAYERS; + else return Mancala.NO_PLAYERS; } + } \ No newline at end of file diff --git a/domain/src/main/java/mancala/domain/SmallBowl.java b/domain/src/main/java/mancala/domain/SmallBowl.java index ce4c0ce..07fa1e0 100644 --- a/domain/src/main/java/mancala/domain/SmallBowl.java +++ b/domain/src/main/java/mancala/domain/SmallBowl.java @@ -1,45 +1,44 @@ package mancala.domain; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + public class SmallBowl extends Bowl { public SmallBowl() { - this.myRocks = 4; + this( + Arrays.stream(new int[] {4,4,4,4,4,4,0,4,4,4,4,4,4,0}).boxed().collect(Collectors.toList()) + ); + } + + public SmallBowl(List stonesList) { this.myOwner = new Player(); - int boardSize = 14; + int boardSize = stonesList.size(); int bowlsToAdd = boardSize - 1; - this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, this, this.getMyOwner()); - } + this.myStones = stonesList.remove(0); + + this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, stonesList, this, this.getMyOwner()); - public SmallBowl(int boardSize) { - try { - if (boardSize < 4) { - throw new Exception("Can't have a board smaller than four bowls."); - } - } catch (Exception e) { - e.printStackTrace(); - } - this.myRocks = 4; - this.myOwner = new Player(); - int bowlsToAdd = boardSize - 1; - this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, this, this.getMyOwner()); } - SmallBowl(int boardSize, int bowlsToAdd, Bowl startBowl, Player playerOwningThisSide) { + SmallBowl(int boardSize, int bowlsToAdd, List stonesList, Bowl startBowl, Player playerOwningThisSide) { bowlsToAdd = bowlsToAdd - 1; this.myOwner = playerOwningThisSide; - this.myRocks = 4; + this.myStones = stonesList.remove(0); if (bowlsToAdd == 0) nextBowl = startBowl; - else if (bowlsToAdd == (boardSize / 2) + 1) nextBowl = new Kalaha(boardSize, bowlsToAdd, startBowl, playerOwningThisSide); + else if (bowlsToAdd == (boardSize / 2) + 1) nextBowl = new Kalaha(boardSize, bowlsToAdd, stonesList, startBowl, playerOwningThisSide); - else if (bowlsToAdd == 1) nextBowl = new Kalaha(boardSize, bowlsToAdd, startBowl, playerOwningThisSide); + else if (bowlsToAdd == 1) nextBowl = new Kalaha(boardSize, bowlsToAdd, stonesList, startBowl, playerOwningThisSide); - else nextBowl = new SmallBowl(boardSize, bowlsToAdd, startBowl, playerOwningThisSide); + else nextBowl = new SmallBowl(boardSize, bowlsToAdd, stonesList, startBowl, playerOwningThisSide); } + public SmallBowl getNextSmallBowlTimes(int remainingTimes) { if (remainingTimes == 0) return this; @@ -52,18 +51,18 @@ public class SmallBowl extends Bowl { if (myOwner.hasTheTurn() == false) return; if (isEmpty()) return; - int passThese = myRocks; - myRocks = 0; + int passThese = myStones; + myStones = 0; getNextBowl().distribute(passThese); } @Override boolean isEmpty() { - return this.myRocks == 0; + return this.myStones == 0; } void distribute(int remainingRocks) { - this.myRocks++; + this.myStones++; // last? if (remainingRocks == 1) lastSmallBowl(); @@ -81,16 +80,16 @@ public class SmallBowl extends Bowl { endTheGame(); } - SmallBowl getSmallBowl() { + SmallBowl getNextSmallBowl() { return this; } @Override SmallBowl goToFirstBowlOfPlayerWithTurn() { if (getMyOwner().hasTheTurn()) { - return getKalaha().getNextBowl().getKalaha().getSmallBowl(); + return getKalaha().getNextBowl().getKalaha().getNextSmallBowl(); } else { - return getKalaha().getSmallBowl(); + return getKalaha().getNextSmallBowl(); } } @@ -100,14 +99,14 @@ public class SmallBowl extends Bowl { private void stealTheBooty(boolean victim) { if (victim){ - getOpposite().getKalaha().claimStolenBooty(myRocks); - myRocks = 0; + getOpposite().getKalaha().claimStolenBooty(myStones); + myStones = 0; } else if (getMyStones() == 1 && getOpposite().getMyStones() != 0) { - getKalaha().claimStolenBooty(myRocks); - myRocks = 0; + getKalaha().claimStolenBooty(myStones); + myStones = 0; getOpposite().stealTheBooty(true); } } diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java index 34ca42d..7e1ec01 100644 --- a/domain/src/test/java/mancala/domain/BowlTest.java +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -2,323 +2,348 @@ package mancala.domain; import org.junit.jupiter.api.*; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + import static org.junit.jupiter.api.Assertions.*; +@IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) class BowlTest { - protected void traverseAndCheckBoard(Bowl currentBowl, int position) { - Bowl initialBowl = currentBowl; - int traversedCount = 0; - int currentPosition = 0; - for (int i = 0; i < 14; i++) { - if ((position + traversedCount) > 14) { - // if looping around the board, position = ((start+traversed) - total) - // in other words the amount of bowls that the absolute position is greater than the board's total bowls - // - // Only relevant to check construction btw, also only checking in the case where there are 14 bowls - currentPosition = ((traversedCount + position) - 14); - } else - // Or just use normal position - currentPosition = position + traversedCount; - - // check for kalaha's, and check for smallbowl otherwise - if (currentPosition == 7 || currentPosition == 14) - assertEquals(currentBowl.getClass(), Kalaha.class); - else - assertEquals(currentBowl.getClass(), SmallBowl.class); - - currentBowl = currentBowl.getNextBowl(); - assertNotNull(currentBowl); - traversedCount++; - } - assertSame(initialBowl, currentBowl); - } @Nested - @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) - class For_a_normal_mancala_bowl{ - SmallBowl firstSmallBowlPlayer; - - @BeforeEach - public void makeASmallBowlInMancala() { - firstSmallBowlPlayer = new SmallBowl(); - traverseAndCheckBoard(firstSmallBowlPlayer, 1); - } + class instantiatingDefaultGame { - @Nested - class GIVEN_its_the_start_of_the_game { - @Test - public void WHEN_before_any_small_bowls_are_played_THEN_has_four_rocks() { - Bowl current = firstSmallBowlPlayer; - for (int i = 0; i < 14; i++) { - current = current.getNextBowl(); - if (current.getClass() == SmallBowl.class) - assertEquals(current.getMyStones(), 4); + private void checkIfDefaultState(Bowl currentBowl, int position) { + Bowl initialBowl = currentBowl; + int traversedCount = 0; + int currentPosition = 0; + for (int i = 0; i < 14; i++) { + if ((position + traversedCount) > 14) { + // if looping around the board, position = ((start+traversed) - total) + // in other words the amount of bowls that the absolute position is greater than the board's total bowls + // + // Only relevant to check construction btw, also only checking in the case where there are 14 bowls + currentPosition = ((traversedCount + position) - 14); + } else + // Or just use normal position + currentPosition = position + traversedCount; + + // check for kalaha's, and check for smallbowl otherwise + if (currentPosition == 7 || currentPosition == 14) { + assertEquals(currentBowl.getClass(), Kalaha.class); + assertEquals(0, currentBowl.getMyStones(), + "In a 'normal' game the kalaha should have 0 rocks when created."); + } else { + assertEquals(currentBowl.getClass(), SmallBowl.class); + assertEquals(4, currentBowl.getMyStones(), + "In a 'normal' game the smallbowl should have 4 rocks when created."); } - assertSame(current, firstSmallBowlPlayer); - } - @Test - public void WHEN_chosen_by_the_player_that_has_the_turn_THEN_distribute_its_rocks_anti_clockwise() { - int initialRocks = firstSmallBowlPlayer.getMyStones(); - firstSmallBowlPlayer.play(); - Bowl neighbour = firstSmallBowlPlayer.getNextBowl(); - for (int i = 0; i < initialRocks; i++) { - assertEquals(5, neighbour.getMyStones()); - neighbour = neighbour.getNextBowl(); - } - assertEquals(4, neighbour.getMyStones()); + currentBowl = currentBowl.getNextBowl(); + assertNotNull(currentBowl); + traversedCount++; } + assertSame(initialBowl, currentBowl); + } + + SmallBowl referenceSmallBowl = new SmallBowl(); + + @Test + public void default_stones_amount_check() { + checkIfDefaultState(referenceSmallBowl, 1); } @Nested - class GIVEN_the_game_is_in_a_state_where { + class given_a_small_bowl { + + @Nested + class and_its_the_start_of_the_game { + @Test + public void when_getMyStones_is_called_then_it_returns_four() { + SmallBowl next = referenceSmallBowl.getNextSmallBowl(); + while (!next.equals(referenceSmallBowl)) { + assertEquals(4, next.getMyStones()); + next = next.getNextSmallBowl(); + } + } - @Test - public void its_not_the_players_turn_WHEN_played_by_the_player_THEN_nothing_happens() { - firstSmallBowlPlayer.getMyOwner().switchTurn(); - int initialRocks = firstSmallBowlPlayer.getMyStones(); - firstSmallBowlPlayer.play(); - Bowl neighbour = firstSmallBowlPlayer.getNextBowl(); - for (int i = 0; i < initialRocks; i++) { + @Test + public void when_play_is_called_and_owner_has_turn_then_distribute_its_rocks_anti_clockwise() { + int initialRocks = referenceSmallBowl.getMyStones(); + referenceSmallBowl.play(); + Bowl neighbour = referenceSmallBowl.getNextBowl(); + for (int i = 0; i < initialRocks; i++) { + assertEquals(5, neighbour.getMyStones()); + neighbour = neighbour.getNextBowl(); + } assertEquals(4, neighbour.getMyStones()); - neighbour = neighbour.getNextBowl(); } - assertEquals(4, neighbour.getMyStones()); } - @Test - public void play_can_reach_opponents_kalaha_WHEN_played_by_the_player_THEN_opponents_kalaha_is_skipped() { - SmallBowl playWillSkipFromThisBowl = goToSkippableState(); - int opponentKalahaRocksBefore = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); - playWillSkipFromThisBowl.play(); - int opponentKalahaRocksAfter = firstSmallBowlPlayer.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); - assertEquals(opponentKalahaRocksBefore, opponentKalahaRocksAfter); - } + @Nested + class and_the_game_is_in_a_state_where { + + @Test + public void its_not_the_players_turn_when_play_is_called_then_nothing_happens() { + referenceSmallBowl.getMyOwner().switchTurn(); + int initialRocks = referenceSmallBowl.getMyStones(); + referenceSmallBowl.play(); + Bowl neighbour = referenceSmallBowl.getNextBowl(); + for (int i = 0; i < initialRocks; i++) { + assertEquals(4, neighbour.getMyStones()); + neighbour = neighbour.getNextBowl(); + } + assertEquals(4, neighbour.getMyStones()); + } - @Test - public void the_bowl_is_empty_WHEN_the_player_plays_the_empty_bowl_THEN_nothing_happens() { - firstSmallBowlPlayer.play(); - firstSmallBowlPlayer.getMyOwner().switchTurn(); - assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); - firstSmallBowlPlayer.play(); - assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); - assertEquals(5, firstSmallBowlPlayer.getNextBowl().getMyStones()); - } + @Test + public void the_bowl_is_empty_WHEN_the_player_plays_the_empty_bowl_THEN_nothing_happens() { + referenceSmallBowl.play(); + referenceSmallBowl.getMyOwner().switchTurn(); + assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); + referenceSmallBowl.play(); + assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); + assertEquals(5, referenceSmallBowl.getNextBowl().getMyStones()); + } - @Test - public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_won() { - Player player = firstSmallBowlPlayer.getMyOwner(); - Player opponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6).getMyOwner(); - assertFalse(player.won()); - assertFalse(opponent.won()); - goToEndOfSillyGame(); - assertTrue(player.won()); - assertFalse(opponent.won()); + @Test + public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_won() { + Player player = referenceSmallBowl.getMyOwner(); + Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); + assertFalse(player.won()); + assertFalse(opponent.won()); + goToEndOfSillyGame(); + assertTrue(player.won()); + assertFalse(opponent.won()); - } + } - @Test - public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_wonOPPONENTVARIATION() { - Player player = firstSmallBowlPlayer.getMyOwner(); - Player opponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6).getMyOwner(); - goToEndOfGameWhereOpponentWins(); - assertFalse(player.won()); - assertTrue(opponent.won()); - } + @Test + public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_wonOPPONENTVARIATION() { + Player player = referenceSmallBowl.getMyOwner(); + Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); + goToEndOfGameWhereOpponentWins(); + assertFalse(player.won()); + assertTrue(opponent.won()); + } - @Test - public void the_play_would_skip_past_opponent_kalaha_at_the_last_rock_and_steal_WHEN_played_THEN_should_skip_and_steal_correctly() { - goToSkipAndStealOnLast(); - SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); - assertEquals(3, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - } + @Test + public void the_play_would_skip_past_opponent_kalaha_at_the_last_rock_and_steal_WHEN_played_THEN_should_skip_and_steal_correctly() { + goToSkipAndStealOnLast(); + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + assertEquals(3, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + } - private void goToSkipAndStealOnLast() { - SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); - firstSmallBowlPlayer.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - firstSmallBowlPlayer.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.play(); - firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); - firstSmallBowlOpponent.play(); - // Cheating here, let player go again >:), i'm too dumb too make a loop/skip and steal play happen in fair game - firstSmallBowlOpponent.getMyOwner().switchTurn(); - // Should skip and steal - // this bowls rocks - assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyStones()); - // End up here by looping around the board, thus skipping - assertEquals(0, firstSmallBowlOpponent.getMyStones()); - // Thus steal from last bowl on players side - assertEquals(8, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyStones()); - // Result is big kalaha booty - assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - } + private void goToSkipAndStealOnLast() { + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + referenceSmallBowl.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + referenceSmallBowl.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.play(); + referenceSmallBowl.getNextSmallBowlTimes(3).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + firstSmallBowlOpponent.play(); + // Cheating here, let player go again >:), i'm too dumb too make a loop/skip and steal play happen in fair game + firstSmallBowlOpponent.getMyOwner().switchTurn(); + // Should skip and steal + // this bowls rocks + assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyStones()); + // End up here by looping around the board, thus skipping + assertEquals(0, firstSmallBowlOpponent.getMyStones()); + // Thus steal from last bowl on players side + assertEquals(8, referenceSmallBowl.getNextSmallBowlTimes(5).getMyStones()); + // Result is big kalaha booty + assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + } - private void goToEndOfGameWhereOpponentWins() { - goToSkipAndStealOnLast(); - SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - firstSmallBowlPlayer.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - firstSmallBowlPlayer.play(); - firstSmallBowlPlayer.getMyOwner().switchTurn(); - firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); - firstSmallBowlPlayer.getMyOwner().switchTurn(); - firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); - firstSmallBowlPlayer.getNextSmallBowlTimes(5).play(); - } + private void goToEndOfGameWhereOpponentWins() { + goToSkipAndStealOnLast(); + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + referenceSmallBowl.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + referenceSmallBowl.play(); + referenceSmallBowl.getMyOwner().switchTurn(); + referenceSmallBowl.getNextSmallBowlTimes(3).play(); + referenceSmallBowl.getMyOwner().switchTurn(); + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + } + + private void goToEndOfSillyGame() { + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + + // player + // Best opening + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + // Set up for steal move + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + assertEquals(2, referenceSmallBowl.getKalaha().getMyStones()); + + // opponent + // ... worst opening? + firstSmallBowlOpponent.play(); + + // player + assertSame(referenceSmallBowl.getNextSmallBowlTimes(4).getOpposite(), referenceSmallBowl.getKalaha().getNextBowl().getNextBowl()); + referenceSmallBowl.play(); + // Check if i did it properly on paper + assertEquals(9, referenceSmallBowl.getKalaha().getMyStones()); + assertEquals(0, referenceSmallBowl.getNextSmallBowlTimes(4).getMyStones()); + // assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite().getMyRocks()); + + // opponent + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + + //Player + referenceSmallBowl.getNextSmallBowlTimes(3).play(); + assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + + // opponent makes stupid move again + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + + // player makes big steal + //assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + assertEquals(19, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + + // opponent steals tiny booty + firstSmallBowlOpponent.play(); + assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + + // player is stalling until the end + referenceSmallBowl.play(); + + // opponent is heading for disaster + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + referenceSmallBowl.play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); + referenceSmallBowl.play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + // everything empty! + for (int i = 0; i < 6; i++) { + assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyStones()); + } - private void goToEndOfSillyGame() { - SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); - - // player - // Best opening - firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); - // Set up for steal move - firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); - assertEquals(2, firstSmallBowlPlayer.getKalaha().getMyStones()); - - // opponent - // ... worst opening? - firstSmallBowlOpponent.play(); - - // player - assertSame(firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite(), firstSmallBowlPlayer.getKalaha().getNextBowl().getNextBowl()); - firstSmallBowlPlayer.play(); - // Check if i did it properly on paper - assertEquals(9, firstSmallBowlPlayer.getKalaha().getMyStones()); - assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getMyStones()); - // assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite().getMyRocks()); - - // opponent - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - - //Player - firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); - assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - - // opponent makes stupid move again - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - - // player makes big steal - //assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); - assertEquals(10, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); - assertEquals(19, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - - // opponent steals tiny booty - firstSmallBowlOpponent.play(); - assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - - // player is stalling until the end - firstSmallBowlPlayer.play(); - - // opponent is heading for disaster - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - firstSmallBowlPlayer.play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); - firstSmallBowlPlayer.play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - // everything empty! - for (int i = 0; i < 6; i++) { - assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyStones()); } + private SmallBowl goToSkippableState() { + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + referenceSmallBowl.getNextSmallBowlTimes(3).play(); + + firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + + referenceSmallBowl.play(); + firstSmallBowlOpponent.play(); + + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); + + // Playing this bowl should give a skip! + assertTrue(referenceSmallBowl.getNextSmallBowlTimes(5).getMyStones() >= 8); + return referenceSmallBowl.getNextSmallBowlTimes(5); + } } - private SmallBowl goToSkippableState() { - SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); + @Nested + class GIVEN_the_play_ends{ - firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); - firstSmallBowlPlayer.getNextSmallBowlTimes(3).play(); + @Test + public void in_own_kalaha_WHEN_play_ends_THEN_turn_is_not_switched() { + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); + } - firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + @Test + public void in_own_small_bowl_WHEN_play_ends_THEN_turn_is_switched() { + referenceSmallBowl.play(); + assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); + } - firstSmallBowlPlayer.play(); - firstSmallBowlOpponent.play(); + @Test + public void in_opponents_small_bowl_WHEN_player_plays_this_bowl_THEN_turn_is_switched() { + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); + } - firstSmallBowlPlayer.getNextSmallBowlTimes(4).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); + @Test + public void in_own_empty_small_bowl_and_opposite_has_rocks_WHEN_play_ends_THEN_rocks_of_opposite_plus_last_rock_of_play_are_added_to_kalaha() { + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + assertSame(referenceSmallBowl.getNextSmallBowlTimes(1).getOpposite(), referenceSmallBowl.getKalaha().getNextSmallBowl().getNextSmallBowlTimes(4)); + // assertSame(firstSmallBowlPlayer.getOpposite(), firstSmallBowlPlayer.getKalaha().getNextSmallBowlTimes(5)); + referenceSmallBowl.play(); + assertEquals(7, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + } - // Playing this bowl should give a skip! - assertTrue(firstSmallBowlPlayer.getNextSmallBowlTimes(5).getMyStones() >= 8); - return firstSmallBowlPlayer.getNextSmallBowlTimes(5); } + + } @Nested - class GIVEN_the_play_ends{ + class given_a_kalaha { - @Test - public void in_own_kalaha_WHEN_play_ends_THEN_turn_is_not_switched() { - firstSmallBowlPlayer.getNextSmallBowlTimes(2).play(); - assertTrue(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); - } - - @Test - public void in_own_small_bowl_WHEN_play_ends_THEN_turn_is_switched() { - firstSmallBowlPlayer.play(); - assertFalse(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); - } + Kalaha playerKalaha; + Kalaha opponentKalaha; - @Test - public void in_opponents_small_bowl_WHEN_player_plays_this_bowl_THEN_turn_is_switched() { - firstSmallBowlPlayer.getNextSmallBowlTimes(5).play(); - assertFalse(firstSmallBowlPlayer.getMyOwner().hasTheTurn()); + @BeforeEach + public void makeKalahaInBoard() { + playerKalaha = referenceSmallBowl.getKalaha(); + opponentKalaha = referenceSmallBowl.getKalaha().getNextSmallBowl().getKalaha(); } @Test - public void in_own_empty_small_bowl_and_opposite_has_rocks_WHEN_play_ends_THEN_rocks_of_opposite_plus_last_rock_of_play_are_added_to_kalaha() { - firstSmallBowlPlayer.getNextSmallBowlTimes(5).play(); - SmallBowl firstSmallBowlOpponent = firstSmallBowlPlayer.getNextSmallBowlTimes(6); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - assertSame(firstSmallBowlPlayer.getNextSmallBowlTimes(1).getOpposite(), firstSmallBowlPlayer.getKalaha().getSmallBowl().getNextSmallBowlTimes(4)); - // assertSame(firstSmallBowlPlayer.getOpposite(), firstSmallBowlPlayer.getKalaha().getNextSmallBowlTimes(5)); - firstSmallBowlPlayer.play(); - assertEquals(7, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + public void when_getMyStones_is_called_after_instantiating_then_has_zero_stones() { + assertEquals(0, playerKalaha.getMyStones()); + assertEquals(0, opponentKalaha.getMyStones()); } - } - } - @Nested - @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) - class a_kalaha { - - SmallBowl smallBowl; - Kalaha kalaha; - @BeforeEach - public void makeKalahaInBoard() { - smallBowl = new SmallBowl(); - kalaha = smallBowl.getNextSmallBowlTimes(6).getKalaha(); - } + @Nested + class instantiatingGameWithStonesList { + SmallBowl referenceSmallBowl; @Test - public void exists_in_a_mancala_board() { - traverseAndCheckBoard(kalaha, 14); + void given_a_stones_list_when_instantiating_a_small_bowl_then_configure_stones_as_in_list() { + int[] stonesArray = new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14}; + List stonesList = Arrays.stream(stonesArray).boxed().collect(Collectors.toList()); + referenceSmallBowl = new SmallBowl(stonesList); + for (int i = 0; i < stonesArray.length; i++) { + if (i < 6) assertEquals(stonesArray[i], referenceSmallBowl.getNextSmallBowlTimes(i).getMyStones()); + else if (i == 6) assertEquals(stonesArray[i], referenceSmallBowl.getKalaha().getMyStones()); + else if (i == 13) assertEquals(stonesArray[i], referenceSmallBowl.getKalaha().getNextBowl().getKalaha().getMyStones()); + else { + int index = i - 1; + assertEquals(stonesArray[i], referenceSmallBowl.getNextSmallBowlTimes(index).getMyStones()); + } + } } @Test - public void has_zero_rocks_when_created() { - Bowl current = kalaha; - for (int i = 0; i < 14; i++) { - current = current.getNextBowl(); - if (current.getClass() == Kalaha.class) - assertEquals(current.getMyStones(), 0); - } + public void given_stones_can_reach_oppenent_kalaha_when_played_validly_then_opponents_kalaha_is_skipped() { + SmallBowl playWillSkipFromThisBowl = goToSkippableState(); + int opponentKalahaRocksBefore = referenceSmallBowl.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); + playWillSkipFromThisBowl.play(); + int opponentKalahaRocksAfter = referenceSmallBowl.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); + assertEquals(opponentKalahaRocksBefore, opponentKalahaRocksAfter); } } } \ No newline at end of file diff --git a/domain/src/test/java/mancala/domain/MancalaImplTest.java b/domain/src/test/java/mancala/domain/MancalaImplTest.java index b040b02..6f0ce2a 100644 --- a/domain/src/test/java/mancala/domain/MancalaImplTest.java +++ b/domain/src/test/java/mancala/domain/MancalaImplTest.java @@ -173,13 +173,36 @@ class MancalaImplTest { @Nested class isEndOfGame { + @Test void given_the_game_is_not_ended_when_isEndOfGame_is_called_then_return_false() { assertFalse(mancala.isEndOfGame()); } + + @Test + void given_the_game_is_ended_when_isEndOfGame_is_called_then_return_true() { + assertTrue(mancala.isEndOfGame()); + } + + } @Nested class getWinner { + @Test + void given_the_game_has_not_ended_when_getWinner_is_called_then_immediately_return_Mancala_NO_PLAYERS() { + assertEquals(Mancala.NO_PLAYERS, mancala.getWinner()); + } + + @Test + void given_PLAYER_ONE_has_won_in_the_domain_model_when_getWinner_is_called_then_return_Mancala_PLAYER_ONE() { + assertEquals(Mancala.PLAYER_ONE, mancala.getWinner()); + } + + @Test + void given_PLAYER_TWO_has_won_in_the_domain_model_when_getWinner_is_called_then_return_Mancala_PLAYER_TWO() { + assertEquals(Mancala.PLAYER_TWO, mancala.getWinner()); + } + } void assumeTurn(int player) { -- cgit v1.2.3 From 0f4370cee071ada7bf1974a3ae2eeb14c4d00aab Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 17:28:10 +0200 Subject: refactor(domain tests) <- reorganising --- .../mancala/domain/DomainSmallBowlException.java | 7 + .../src/main/java/mancala/domain/MancalaImpl.java | 6 +- domain/src/main/java/mancala/domain/SmallBowl.java | 11 +- domain/src/test/java/mancala/domain/BowlTest.java | 439 +++++++++++---------- 4 files changed, 245 insertions(+), 218 deletions(-) create mode 100644 domain/src/main/java/mancala/domain/DomainSmallBowlException.java diff --git a/domain/src/main/java/mancala/domain/DomainSmallBowlException.java b/domain/src/main/java/mancala/domain/DomainSmallBowlException.java new file mode 100644 index 0000000..56e5de1 --- /dev/null +++ b/domain/src/main/java/mancala/domain/DomainSmallBowlException.java @@ -0,0 +1,7 @@ +package mancala.domain; + +public class DomainSmallBowlException extends Exception { + public DomainSmallBowlException(String message) { + super(message); + } +} diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java index 29048ff..a8e6691 100644 --- a/domain/src/main/java/mancala/domain/MancalaImpl.java +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -18,7 +18,11 @@ public class MancalaImpl implements Mancala { private SmallBowl domainReference; public MancalaImpl() { - domainReference = new SmallBowl(); + try { + domainReference = new SmallBowl(); + } catch (DomainSmallBowlException e) { + e.printStackTrace(); + } domainPlayer = domainReference.getMyOwner(); domainOpponent = domainPlayer.getOpponent(); } diff --git a/domain/src/main/java/mancala/domain/SmallBowl.java b/domain/src/main/java/mancala/domain/SmallBowl.java index 07fa1e0..b9e4b45 100644 --- a/domain/src/main/java/mancala/domain/SmallBowl.java +++ b/domain/src/main/java/mancala/domain/SmallBowl.java @@ -6,13 +6,14 @@ import java.util.stream.Collectors; public class SmallBowl extends Bowl { - public SmallBowl() { - this( - Arrays.stream(new int[] {4,4,4,4,4,4,0,4,4,4,4,4,4,0}).boxed().collect(Collectors.toList()) - ); + public SmallBowl() throws DomainSmallBowlException { + this(Arrays.stream(new int[] {4,4,4,4,4,4,0,4,4,4,4,4,4,0}).boxed().collect(Collectors.toList())); } - public SmallBowl(List stonesList) { + public SmallBowl(List stonesList) throws DomainSmallBowlException { + if (stonesList.size() % 2 != 0) { + throw new DomainSmallBowlException("Stones List should contain even number of elements."); + } this.myOwner = new Player(); int boardSize = stonesList.size(); diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java index 7e1ec01..cd9f405 100644 --- a/domain/src/test/java/mancala/domain/BowlTest.java +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -49,7 +49,15 @@ class BowlTest { assertSame(initialBowl, currentBowl); } - SmallBowl referenceSmallBowl = new SmallBowl(); + SmallBowl referenceSmallBowl; + + { + try { + referenceSmallBowl = new SmallBowl(); + } catch (DomainSmallBowlException e) { + e.printStackTrace(); + } + } @Test public void default_stones_amount_check() { @@ -83,214 +91,9 @@ class BowlTest { } } - @Nested - class and_the_game_is_in_a_state_where { - - @Test - public void its_not_the_players_turn_when_play_is_called_then_nothing_happens() { - referenceSmallBowl.getMyOwner().switchTurn(); - int initialRocks = referenceSmallBowl.getMyStones(); - referenceSmallBowl.play(); - Bowl neighbour = referenceSmallBowl.getNextBowl(); - for (int i = 0; i < initialRocks; i++) { - assertEquals(4, neighbour.getMyStones()); - neighbour = neighbour.getNextBowl(); - } - assertEquals(4, neighbour.getMyStones()); - } - - @Test - public void the_bowl_is_empty_WHEN_the_player_plays_the_empty_bowl_THEN_nothing_happens() { - referenceSmallBowl.play(); - referenceSmallBowl.getMyOwner().switchTurn(); - assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); - referenceSmallBowl.play(); - assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); - assertEquals(5, referenceSmallBowl.getNextBowl().getMyStones()); - } - - @Test - public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_won() { - Player player = referenceSmallBowl.getMyOwner(); - Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); - assertFalse(player.won()); - assertFalse(opponent.won()); - goToEndOfSillyGame(); - assertTrue(player.won()); - assertFalse(opponent.won()); - - } - - @Test - public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_wonOPPONENTVARIATION() { - Player player = referenceSmallBowl.getMyOwner(); - Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); - goToEndOfGameWhereOpponentWins(); - assertFalse(player.won()); - assertTrue(opponent.won()); - } - - @Test - public void the_play_would_skip_past_opponent_kalaha_at_the_last_rock_and_steal_WHEN_played_THEN_should_skip_and_steal_correctly() { - goToSkipAndStealOnLast(); - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - assertEquals(3, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - } - - private void goToSkipAndStealOnLast() { - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - referenceSmallBowl.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - referenceSmallBowl.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.play(); - referenceSmallBowl.getNextSmallBowlTimes(3).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - referenceSmallBowl.getNextSmallBowlTimes(4).play(); - firstSmallBowlOpponent.play(); - // Cheating here, let player go again >:), i'm too dumb too make a loop/skip and steal play happen in fair game - firstSmallBowlOpponent.getMyOwner().switchTurn(); - // Should skip and steal - // this bowls rocks - assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyStones()); - // End up here by looping around the board, thus skipping - assertEquals(0, firstSmallBowlOpponent.getMyStones()); - // Thus steal from last bowl on players side - assertEquals(8, referenceSmallBowl.getNextSmallBowlTimes(5).getMyStones()); - // Result is big kalaha booty - assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - } - - private void goToEndOfGameWhereOpponentWins() { - goToSkipAndStealOnLast(); - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - referenceSmallBowl.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - referenceSmallBowl.play(); - referenceSmallBowl.getMyOwner().switchTurn(); - referenceSmallBowl.getNextSmallBowlTimes(3).play(); - referenceSmallBowl.getMyOwner().switchTurn(); - referenceSmallBowl.getNextSmallBowlTimes(4).play(); - referenceSmallBowl.getNextSmallBowlTimes(5).play(); - } - - private void goToEndOfSillyGame() { - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - - // player - // Best opening - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - // Set up for steal move - referenceSmallBowl.getNextSmallBowlTimes(4).play(); - assertEquals(2, referenceSmallBowl.getKalaha().getMyStones()); - - // opponent - // ... worst opening? - firstSmallBowlOpponent.play(); - - // player - assertSame(referenceSmallBowl.getNextSmallBowlTimes(4).getOpposite(), referenceSmallBowl.getKalaha().getNextBowl().getNextBowl()); - referenceSmallBowl.play(); - // Check if i did it properly on paper - assertEquals(9, referenceSmallBowl.getKalaha().getMyStones()); - assertEquals(0, referenceSmallBowl.getNextSmallBowlTimes(4).getMyStones()); - // assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite().getMyRocks()); - - // opponent - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - - //Player - referenceSmallBowl.getNextSmallBowlTimes(3).play(); - assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - // opponent makes stupid move again - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - // player makes big steal - //assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); - assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - assertEquals(19, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - // opponent steals tiny booty - firstSmallBowlOpponent.play(); - assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - - // player is stalling until the end - referenceSmallBowl.play(); - - // opponent is heading for disaster - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - referenceSmallBowl.play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); - referenceSmallBowl.play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - // everything empty! - for (int i = 0; i < 6; i++) { - assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyStones()); - } - - } - - private SmallBowl goToSkippableState() { - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - referenceSmallBowl.getNextSmallBowlTimes(3).play(); - - firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - - referenceSmallBowl.play(); - firstSmallBowlOpponent.play(); - - referenceSmallBowl.getNextSmallBowlTimes(4).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); - - // Playing this bowl should give a skip! - assertTrue(referenceSmallBowl.getNextSmallBowlTimes(5).getMyStones() >= 8); - return referenceSmallBowl.getNextSmallBowlTimes(5); - } - } - - @Nested - class GIVEN_the_play_ends{ - - @Test - public void in_own_kalaha_WHEN_play_ends_THEN_turn_is_not_switched() { - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); - } - - @Test - public void in_own_small_bowl_WHEN_play_ends_THEN_turn_is_switched() { - referenceSmallBowl.play(); - assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); - } - - @Test - public void in_opponents_small_bowl_WHEN_player_plays_this_bowl_THEN_turn_is_switched() { - referenceSmallBowl.getNextSmallBowlTimes(5).play(); - assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); - } - - @Test - public void in_own_empty_small_bowl_and_opposite_has_rocks_WHEN_play_ends_THEN_rocks_of_opposite_plus_last_rock_of_play_are_added_to_kalaha() { - referenceSmallBowl.getNextSmallBowlTimes(5).play(); - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - assertSame(referenceSmallBowl.getNextSmallBowlTimes(1).getOpposite(), referenceSmallBowl.getKalaha().getNextSmallBowl().getNextSmallBowlTimes(4)); - // assertSame(firstSmallBowlPlayer.getOpposite(), firstSmallBowlPlayer.getKalaha().getNextSmallBowlTimes(5)); - referenceSmallBowl.play(); - assertEquals(7, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - } - - } } @@ -325,7 +128,11 @@ class BowlTest { void given_a_stones_list_when_instantiating_a_small_bowl_then_configure_stones_as_in_list() { int[] stonesArray = new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14}; List stonesList = Arrays.stream(stonesArray).boxed().collect(Collectors.toList()); - referenceSmallBowl = new SmallBowl(stonesList); + try { + referenceSmallBowl = new SmallBowl(stonesList); + } catch (DomainSmallBowlException e) { + fail("Invalid instantiation."); + } for (int i = 0; i < stonesArray.length; i++) { if (i < 6) assertEquals(stonesArray[i], referenceSmallBowl.getNextSmallBowlTimes(i).getMyStones()); else if (i == 6) assertEquals(stonesArray[i], referenceSmallBowl.getKalaha().getMyStones()); @@ -337,13 +144,221 @@ class BowlTest { } } + @Test + void given_a_stones_list_with_odd_number_of_elements_when_instantiating_small_bowl_then_throw_DomainSmallBowlException() { + int[] stonesArray = new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13}; + List stonesList = Arrays.stream(stonesArray).boxed().collect(Collectors.toList()); + try { + referenceSmallBowl = new SmallBowl(stonesList); + fail(); + } catch (DomainSmallBowlException e) { + } + } + @Test public void given_stones_can_reach_oppenent_kalaha_when_played_validly_then_opponents_kalaha_is_skipped() { - SmallBowl playWillSkipFromThisBowl = goToSkippableState(); - int opponentKalahaRocksBefore = referenceSmallBowl.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); - playWillSkipFromThisBowl.play(); - int opponentKalahaRocksAfter = referenceSmallBowl.getNextSmallBowlTimes(11).getNextBowl().getMyStones(); - assertEquals(opponentKalahaRocksBefore, opponentKalahaRocksAfter); + try { + referenceSmallBowl = new SmallBowl( + Arrays.stream( + new int[] {0,0,0,0,0,100,0,0,0,0,0,0,0,0} + ).boxed().collect(Collectors.toList()) + ); + } catch (DomainSmallBowlException e) { + fail("Invalid instantiation."); + } + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + assertEquals(0, referenceSmallBowl.getKalaha().getNextBowl().getKalaha().getMyStones()); } + + @Nested + class and_the_game_is_in_a_state_where { + + @Test + public void its_not_the_players_turn_when_play_is_called_then_nothing_happens() { + referenceSmallBowl.getMyOwner().switchTurn(); + int initialRocks = referenceSmallBowl.getMyStones(); + referenceSmallBowl.play(); + Bowl neighbour = referenceSmallBowl.getNextBowl(); + for (int i = 0; i < initialRocks; i++) { + assertEquals(4, neighbour.getMyStones()); + neighbour = neighbour.getNextBowl(); + } + assertEquals(4, neighbour.getMyStones()); + } + + @Test + public void the_bowl_is_empty_WHEN_the_player_plays_the_empty_bowl_THEN_nothing_happens() { + referenceSmallBowl.play(); + referenceSmallBowl.getMyOwner().switchTurn(); + assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); + referenceSmallBowl.play(); + assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); + assertEquals(5, referenceSmallBowl.getNextBowl().getMyStones()); + } + + @Test + public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_won() { + Player player = referenceSmallBowl.getMyOwner(); + Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); + assertFalse(player.won()); + assertFalse(opponent.won()); + goToEndOfSillyGame(); + assertTrue(player.won()); + assertFalse(opponent.won()); + + } + + @Test + public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_wonOPPONENTVARIATION() { + Player player = referenceSmallBowl.getMyOwner(); + Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); + goToEndOfGameWhereOpponentWins(); + assertFalse(player.won()); + assertTrue(opponent.won()); + } + + @Test + public void the_play_would_skip_past_opponent_kalaha_at_the_last_rock_and_steal_WHEN_played_THEN_should_skip_and_steal_correctly() { + goToSkipAndStealOnLast(); + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + assertEquals(3, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + } + + private void goToSkipAndStealOnLast() { + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + referenceSmallBowl.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + referenceSmallBowl.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + firstSmallBowlOpponent.play(); + referenceSmallBowl.getNextSmallBowlTimes(3).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + firstSmallBowlOpponent.play(); + // Cheating here, let player go again >:), i'm too dumb too make a loop/skip and steal play happen in fair game + firstSmallBowlOpponent.getMyOwner().switchTurn(); + // Should skip and steal + // this bowls rocks + assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyStones()); + // End up here by looping around the board, thus skipping + assertEquals(0, firstSmallBowlOpponent.getMyStones()); + // Thus steal from last bowl on players side + assertEquals(8, referenceSmallBowl.getNextSmallBowlTimes(5).getMyStones()); + // Result is big kalaha booty + assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + } + + private void goToEndOfGameWhereOpponentWins() { + goToSkipAndStealOnLast(); + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + referenceSmallBowl.getNextSmallBowlTimes(1).play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + referenceSmallBowl.play(); + referenceSmallBowl.getMyOwner().switchTurn(); + referenceSmallBowl.getNextSmallBowlTimes(3).play(); + referenceSmallBowl.getMyOwner().switchTurn(); + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + } + + private void goToEndOfSillyGame() { + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + + // player + // Best opening + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + // Set up for steal move + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + assertEquals(2, referenceSmallBowl.getKalaha().getMyStones()); + + // opponent + // ... worst opening? + firstSmallBowlOpponent.play(); + + // player + assertSame(referenceSmallBowl.getNextSmallBowlTimes(4).getOpposite(), referenceSmallBowl.getKalaha().getNextBowl().getNextBowl()); + referenceSmallBowl.play(); + // Check if i did it properly on paper + assertEquals(9, referenceSmallBowl.getKalaha().getMyStones()); + assertEquals(0, referenceSmallBowl.getNextSmallBowlTimes(4).getMyStones()); + // assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite().getMyRocks()); + + // opponent + firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); + + //Player + referenceSmallBowl.getNextSmallBowlTimes(3).play(); + assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + + // opponent makes stupid move again + firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); + + // player makes big steal + //assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); + assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + assertEquals(19, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + + // opponent steals tiny booty + firstSmallBowlOpponent.play(); + assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + + // player is stalling until the end + referenceSmallBowl.play(); + + // opponent is heading for disaster + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + referenceSmallBowl.play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); + referenceSmallBowl.play(); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + // everything empty! + for (int i = 0; i < 6; i++) { + assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyStones()); + } + + } + } + + + @Nested + class GIVEN_the_play_ends{ + + @Test + public void in_own_kalaha_WHEN_play_ends_THEN_turn_is_not_switched() { + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); + } + + @Test + public void in_own_small_bowl_WHEN_play_ends_THEN_turn_is_switched() { + referenceSmallBowl.play(); + assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); + } + + @Test + public void in_opponents_small_bowl_WHEN_player_plays_this_bowl_THEN_turn_is_switched() { + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); + } + + @Test + public void in_own_empty_small_bowl_and_opposite_has_rocks_WHEN_play_ends_THEN_rocks_of_opposite_plus_last_rock_of_play_are_added_to_kalaha() { + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); + firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + assertSame(referenceSmallBowl.getNextSmallBowlTimes(1).getOpposite(), referenceSmallBowl.getKalaha().getNextSmallBowl().getNextSmallBowlTimes(4)); + // assertSame(firstSmallBowlPlayer.getOpposite(), firstSmallBowlPlayer.getKalaha().getNextSmallBowlTimes(5)); + referenceSmallBowl.play(); + assertEquals(7, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + } + + } + } } \ No newline at end of file -- cgit v1.2.3 From ba2ec6bc470399eb8ed906691626d62ef11d3244 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 17:47:56 +0200 Subject: before dinner --- domain/src/test/java/mancala/domain/BowlTest.java | 60 ++++++++++++----------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java index cd9f405..6d5a12f 100644 --- a/domain/src/test/java/mancala/domain/BowlTest.java +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -90,25 +90,13 @@ class BowlTest { assertEquals(4, neighbour.getMyStones()); } } - - - - - - } @Nested class given_a_kalaha { - Kalaha playerKalaha; - Kalaha opponentKalaha; - - @BeforeEach - public void makeKalahaInBoard() { - playerKalaha = referenceSmallBowl.getKalaha(); - opponentKalaha = referenceSmallBowl.getKalaha().getNextSmallBowl().getKalaha(); - } + Kalaha playerKalaha = referenceSmallBowl.getKalaha(); + Kalaha opponentKalaha = referenceSmallBowl.getKalaha().getNextSmallBowl().getKalaha(); @Test public void when_getMyStones_is_called_after_instantiating_then_has_zero_stones() { @@ -155,23 +143,31 @@ class BowlTest { } } - @Test - public void given_stones_can_reach_oppenent_kalaha_when_played_validly_then_opponents_kalaha_is_skipped() { - try { - referenceSmallBowl = new SmallBowl( - Arrays.stream( - new int[] {0,0,0,0,0,100,0,0,0,0,0,0,0,0} - ).boxed().collect(Collectors.toList()) - ); - } catch (DomainSmallBowlException e) { - fail("Invalid instantiation."); + @Nested + class playBehaviour { + + { + try { + referenceSmallBowl = new SmallBowl(); + } catch (DomainSmallBowlException e) { + e.printStackTrace(); + } } - referenceSmallBowl.getNextSmallBowlTimes(5).play(); - assertEquals(0, referenceSmallBowl.getKalaha().getNextBowl().getKalaha().getMyStones()); - } - @Nested - class and_the_game_is_in_a_state_where { + @Test + public void given_stones_can_reach_oppenent_kalaha_when_played_validly_then_opponents_kalaha_is_skipped() { + try { + referenceSmallBowl = new SmallBowl( + Arrays.stream( + new int[] {0,0,0,0,0,100,0,0,0,0,0,0,0,0} + ).boxed().collect(Collectors.toList()) + ); + } catch (DomainSmallBowlException e) { + fail("Invalid instantiation."); + } + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + assertEquals(0, referenceSmallBowl.getKalaha().getNextBowl().getKalaha().getMyStones()); + } @Test public void its_not_the_players_turn_when_play_is_called_then_nothing_happens() { @@ -186,6 +182,12 @@ class BowlTest { assertEquals(4, neighbour.getMyStones()); } + } + + + @Nested + class and_the_game_is_in_a_state_where { + @Test public void the_bowl_is_empty_WHEN_the_player_plays_the_empty_bowl_THEN_nothing_happens() { referenceSmallBowl.play(); -- cgit v1.2.3 From 2e1a7cbcd36296c5abb1bb6d12fc155e3e3913fb Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 20:14:22 +0200 Subject: finished making my life easier (domain tests) --- domain/src/main/java/mancala/domain/Bowl.java | 2 +- domain/src/main/java/mancala/domain/Kalaha.java | 14 ++ domain/src/main/java/mancala/domain/SmallBowl.java | 29 ++- domain/src/test/java/mancala/domain/BowlTest.java | 227 +++++++-------------- 4 files changed, 117 insertions(+), 155 deletions(-) diff --git a/domain/src/main/java/mancala/domain/Bowl.java b/domain/src/main/java/mancala/domain/Bowl.java index 8a214da..a36c621 100644 --- a/domain/src/main/java/mancala/domain/Bowl.java +++ b/domain/src/main/java/mancala/domain/Bowl.java @@ -61,5 +61,5 @@ abstract class Bowl { } - + protected abstract String makeString(String playerBowls, String opponentBowls, String kalahas); } diff --git a/domain/src/main/java/mancala/domain/Kalaha.java b/domain/src/main/java/mancala/domain/Kalaha.java index a13ebd8..c85af4a 100644 --- a/domain/src/main/java/mancala/domain/Kalaha.java +++ b/domain/src/main/java/mancala/domain/Kalaha.java @@ -55,6 +55,20 @@ class Kalaha extends Bowl { return true; } + @Override + protected String makeString(String playerBowls, String opponentBowls, String kalahas) { + if (getMyOwner().equals(SmallBowl.referencePoint.getMyOwner().getOpponent())) { + return " " + opponentBowls + "\n" + + getMyStones() + "\t\t\t\t " + kalahas + "\n" + + " " + playerBowls; + } + else { + return getNextBowl().makeString( + playerBowls, + opponentBowls, + kalahas + getMyStones()); + } + } void claimStolenBooty(int booty) { myStones = myStones + booty; diff --git a/domain/src/main/java/mancala/domain/SmallBowl.java b/domain/src/main/java/mancala/domain/SmallBowl.java index b9e4b45..216e2ad 100644 --- a/domain/src/main/java/mancala/domain/SmallBowl.java +++ b/domain/src/main/java/mancala/domain/SmallBowl.java @@ -6,8 +6,11 @@ import java.util.stream.Collectors; public class SmallBowl extends Bowl { + public static SmallBowl referencePoint; + public SmallBowl() throws DomainSmallBowlException { this(Arrays.stream(new int[] {4,4,4,4,4,4,0,4,4,4,4,4,4,0}).boxed().collect(Collectors.toList())); + referencePoint = this; } public SmallBowl(List stonesList) throws DomainSmallBowlException { @@ -22,7 +25,7 @@ public class SmallBowl extends Bowl { this.myStones = stonesList.remove(0); this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, stonesList, this, this.getMyOwner()); - + referencePoint = this; } SmallBowl(int boardSize, int bowlsToAdd, List stonesList, Bowl startBowl, Player playerOwningThisSide) { @@ -76,9 +79,10 @@ public class SmallBowl extends Bowl { // Did play end in smallbowl of my player? steal, otherwise do nothing if (getMyOwner().hasTheTurn()) stealTheBooty(false); + endTheGame(); + getMyOwner().switchTurn(); - endTheGame(); } SmallBowl getNextSmallBowl() { @@ -100,11 +104,13 @@ public class SmallBowl extends Bowl { private void stealTheBooty(boolean victim) { if (victim){ + System.out.println("help! i'm being robbed, my precious " + getMyStones() + " stones are gone :(."); getOpposite().getKalaha().claimStolenBooty(myStones); myStones = 0; } else if (getMyStones() == 1 && getOpposite().getMyStones() != 0) { + System.out.println("stealing"); getKalaha().claimStolenBooty(myStones); myStones = 0; @@ -120,4 +126,23 @@ public class SmallBowl extends Bowl { count = count + 1; return getNextBowl().getOpposite(count); } + + public String stateString() { + return SmallBowl.referencePoint.makeString("", "", ""); + } + + protected String makeString(String playerBowls, String opponentBowls, String kalahas) { + if (!this.getMyOwner().equals(SmallBowl.referencePoint.getMyOwner())) { + return getNextBowl().makeString( + playerBowls, + (getMyStones() + ", ") + opponentBowls, + kalahas); + } else { + return getNextBowl().makeString( + playerBowls + (playerBowls.equals("") ? getMyStones() : ", " + getMyStones()), + opponentBowls, + kalahas); + } + } + } diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java index 6d5a12f..0f2ea4a 100644 --- a/domain/src/test/java/mancala/domain/BowlTest.java +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -143,10 +143,24 @@ class BowlTest { } } + void setupGameSituationAndFailIfInvalid(int[] stonesArray) { + try { + referenceSmallBowl = new SmallBowl( + Arrays.stream( + stonesArray + ).boxed().collect(Collectors.toList()) + ); + } catch (DomainSmallBowlException e) { + fail("Invalid instantiation."); + } + } + @Nested class playBehaviour { { + // setup default game in this sub class + // by default try { referenceSmallBowl = new SmallBowl(); } catch (DomainSmallBowlException e) { @@ -155,22 +169,14 @@ class BowlTest { } @Test - public void given_stones_can_reach_oppenent_kalaha_when_played_validly_then_opponents_kalaha_is_skipped() { - try { - referenceSmallBowl = new SmallBowl( - Arrays.stream( - new int[] {0,0,0,0,0,100,0,0,0,0,0,0,0,0} - ).boxed().collect(Collectors.toList()) - ); - } catch (DomainSmallBowlException e) { - fail("Invalid instantiation."); - } + void given_stones_can_reach_oppenent_kalaha_when_played_validly_then_opponents_kalaha_is_skipped() { + setupGameSituationAndFailIfInvalid(new int[] {0,0,0,0,0,100,0,0,0,0,0,0,0,0}); referenceSmallBowl.getNextSmallBowlTimes(5).play(); assertEquals(0, referenceSmallBowl.getKalaha().getNextBowl().getKalaha().getMyStones()); } @Test - public void its_not_the_players_turn_when_play_is_called_then_nothing_happens() { + void given_its_not_the_players_turn_when_play_is_called_then_nothing_happens() { referenceSmallBowl.getMyOwner().switchTurn(); int initialRocks = referenceSmallBowl.getMyStones(); referenceSmallBowl.play(); @@ -182,14 +188,8 @@ class BowlTest { assertEquals(4, neighbour.getMyStones()); } - } - - - @Nested - class and_the_game_is_in_a_state_where { - @Test - public void the_bowl_is_empty_WHEN_the_player_plays_the_empty_bowl_THEN_nothing_happens() { + void given_the_bowl_is_empty_when_play_called_on_the_bowl_then_nothing_happens() { referenceSmallBowl.play(); referenceSmallBowl.getMyOwner().switchTurn(); assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); @@ -199,168 +199,91 @@ class BowlTest { } @Test - public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_won() { - Player player = referenceSmallBowl.getMyOwner(); - Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); - assertFalse(player.won()); - assertFalse(opponent.won()); - goToEndOfSillyGame(); - assertTrue(player.won()); - assertFalse(opponent.won()); + void given_stones_would_skip_opponent_kalaha_at_the_last_rock_and_steal_when_played_then_should_skip_and_steal_correctly() { + setupGameSituationAndFailIfInvalid(new int[] {13,0,0,0,0,0, + 0, + 0,0,0,0,0,8, + 0}); + System.out.println(referenceSmallBowl.getMyStones()); + System.out.println(referenceSmallBowl.stateString()); + referenceSmallBowl.play(); + System.out.println(referenceSmallBowl.stateString()); + assertEquals(11, referenceSmallBowl.getKalaha().getMyStones(), + "resulting kalaha stones after stealing should be 11."); + assertEquals(0, referenceSmallBowl.getMyStones(), + "played bowl should be zero, since steal happened"); + assertEquals(0, referenceSmallBowl.getNextSmallBowlTimes(12).getMyStones()); } @Test - public void all_small_bowls_of_the_player_are_empty_WHEN_a_play_ends_THEN_tell_players_who_wonOPPONENTVARIATION() { - Player player = referenceSmallBowl.getMyOwner(); - Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); - goToEndOfGameWhereOpponentWins(); - assertFalse(player.won()); - assertTrue(opponent.won()); + void given_that_play_ends_in_own_kalaha_when_play_is_called_validly_then_turn_is_not_switched() { + referenceSmallBowl.getNextSmallBowlTimes(2).play(); + assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); } @Test - public void the_play_would_skip_past_opponent_kalaha_at_the_last_rock_and_steal_WHEN_played_THEN_should_skip_and_steal_correctly() { - goToSkipAndStealOnLast(); - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - assertEquals(3, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - assertEquals(19, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - } - - private void goToSkipAndStealOnLast() { - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - referenceSmallBowl.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - referenceSmallBowl.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - firstSmallBowlOpponent.play(); - referenceSmallBowl.getNextSmallBowlTimes(3).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - referenceSmallBowl.getNextSmallBowlTimes(4).play(); - firstSmallBowlOpponent.play(); - // Cheating here, let player go again >:), i'm too dumb too make a loop/skip and steal play happen in fair game - firstSmallBowlOpponent.getMyOwner().switchTurn(); - // Should skip and steal - // this bowls rocks - assertEquals(10, firstSmallBowlOpponent.getNextSmallBowlTimes(3).getMyStones()); - // End up here by looping around the board, thus skipping - assertEquals(0, firstSmallBowlOpponent.getMyStones()); - // Thus steal from last bowl on players side - assertEquals(8, referenceSmallBowl.getNextSmallBowlTimes(5).getMyStones()); - // Result is big kalaha booty - assertEquals(8, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + void given_that_play_ends_in_own_small_bowl_when_play_is_called_validly_then_turn_is_switched() { + referenceSmallBowl.play(); + assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); } - private void goToEndOfGameWhereOpponentWins() { - goToSkipAndStealOnLast(); - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - referenceSmallBowl.getNextSmallBowlTimes(1).play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - referenceSmallBowl.play(); - referenceSmallBowl.getMyOwner().switchTurn(); - referenceSmallBowl.getNextSmallBowlTimes(3).play(); - referenceSmallBowl.getMyOwner().switchTurn(); - referenceSmallBowl.getNextSmallBowlTimes(4).play(); + @Test + void given_that_play_ends_in_opponents_small_bowl_when_play_is_called_validly_then_turn_is_switched() { referenceSmallBowl.getNextSmallBowlTimes(5).play(); + assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); } - private void goToEndOfSillyGame() { + @Test + void given_that_play_ends_in_own_empty_small_bowl_and_opposite_has_rocks_when_play_is_called_validly_then_rocks_of_opposite_plus_last_rock_of_play_are_added_to_next_kalaha() { + System.out.println(referenceSmallBowl.stateString()); + referenceSmallBowl.getNextSmallBowlTimes(5).play(); + System.out.println(referenceSmallBowl.stateString()); SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - - // player - // Best opening - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - // Set up for steal move - referenceSmallBowl.getNextSmallBowlTimes(4).play(); - assertEquals(2, referenceSmallBowl.getKalaha().getMyStones()); - - // opponent - // ... worst opening? - firstSmallBowlOpponent.play(); - - // player - assertSame(referenceSmallBowl.getNextSmallBowlTimes(4).getOpposite(), referenceSmallBowl.getKalaha().getNextBowl().getNextBowl()); - referenceSmallBowl.play(); - // Check if i did it properly on paper - assertEquals(9, referenceSmallBowl.getKalaha().getMyStones()); - assertEquals(0, referenceSmallBowl.getNextSmallBowlTimes(4).getMyStones()); - // assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(4).getOpposite().getMyRocks()); - - // opponent - firstSmallBowlOpponent.getNextSmallBowlTimes(3).play(); - - //Player - referenceSmallBowl.getNextSmallBowlTimes(3).play(); - assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - - // opponent makes stupid move again - firstSmallBowlOpponent.getNextSmallBowlTimes(1).play(); - - // player makes big steal - //assertEquals(0, firstSmallBowlPlayer.getNextSmallBowlTimes(5).getNextBowl().getMyRocks()); - assertEquals(10, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - assertEquals(19, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - - // opponent steals tiny booty - firstSmallBowlOpponent.play(); - assertEquals(3, firstSmallBowlOpponent.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); - - // player is stalling until the end - referenceSmallBowl.play(); - - // opponent is heading for disaster firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); + System.out.println(referenceSmallBowl.stateString()); referenceSmallBowl.play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(4).play(); - referenceSmallBowl.play(); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - // everything empty! - for (int i = 0; i < 6; i++) { - assertEquals(0, firstSmallBowlOpponent.getNextSmallBowlTimes(i).getMyStones()); - } - + System.out.println(referenceSmallBowl.stateString()); + assertEquals(7, referenceSmallBowl.getKalaha().getMyStones()); + assertEquals(0, referenceSmallBowl.getNextSmallBowlTimes(5).getMyStones()); + assertEquals(0, referenceSmallBowl.getKalaha().getNextBowl().getMyStones()); } } - @Nested - class GIVEN_the_play_ends{ - - @Test - public void in_own_kalaha_WHEN_play_ends_THEN_turn_is_not_switched() { - referenceSmallBowl.getNextSmallBowlTimes(2).play(); - assertTrue(referenceSmallBowl.getMyOwner().hasTheTurn()); - } - - @Test - public void in_own_small_bowl_WHEN_play_ends_THEN_turn_is_switched() { - referenceSmallBowl.play(); - assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); - } + class endGameBehaviour { @Test - public void in_opponents_small_bowl_WHEN_player_plays_this_bowl_THEN_turn_is_switched() { + void given_all_small_bowls_of_the_player_are_empty_when_a_play_ends_then_tell_players_who_won() { + setupGameSituationAndFailIfInvalid(new int[] {0,0,0,0,0,1,0,4,4,4,4,4,4,0}); + Player player = referenceSmallBowl.getMyOwner(); + Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); + assertFalse(player.won(), "players haven't won at start of game"); + assertFalse(opponent.won(), "players haven't won at start of game"); referenceSmallBowl.getNextSmallBowlTimes(5).play(); - assertFalse(referenceSmallBowl.getMyOwner().hasTheTurn()); + assertFalse(player.won(), "player should lose here."); + assertTrue(opponent.won(), "opponent should win here."); + + setupGameSituationAndFailIfInvalid(new int[] {4,4,4,4,4,4,0,0,0,0,0,0,1,0}); + player = referenceSmallBowl.getMyOwner(); + opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); + assertFalse(player.won(), "players haven't won at start of game"); + assertFalse(opponent.won(), "players haven't won at start of game"); + player.switchTurn(); + referenceSmallBowl.getNextSmallBowlTimes(6 + 5).play(); + assertTrue(player.won(), "player should win here."); + assertFalse(opponent.won(), "opponent should lose here."); } @Test - public void in_own_empty_small_bowl_and_opposite_has_rocks_WHEN_play_ends_THEN_rocks_of_opposite_plus_last_rock_of_play_are_added_to_kalaha() { + void given_all_small_bowls_of_the_player_are_empty_and_score_is_tied_when_a_play_ends_then_tell_both_player_they_won() { + setupGameSituationAndFailIfInvalid(new int[] {0,0,0,0,0,1,0,0,0,0,0,0,1,0}); + System.out.println(referenceSmallBowl.stateString()); referenceSmallBowl.getNextSmallBowlTimes(5).play(); - SmallBowl firstSmallBowlOpponent = referenceSmallBowl.getNextSmallBowlTimes(6); - firstSmallBowlOpponent.getNextSmallBowlTimes(5).play(); - assertSame(referenceSmallBowl.getNextSmallBowlTimes(1).getOpposite(), referenceSmallBowl.getKalaha().getNextSmallBowl().getNextSmallBowlTimes(4)); - // assertSame(firstSmallBowlPlayer.getOpposite(), firstSmallBowlPlayer.getKalaha().getNextSmallBowlTimes(5)); - referenceSmallBowl.play(); - assertEquals(7, referenceSmallBowl.getNextSmallBowlTimes(5).getNextBowl().getMyStones()); + System.out.println(referenceSmallBowl.stateString()); + assertTrue(referenceSmallBowl.getMyOwner().won() && referenceSmallBowl.getMyOwner().getOpponent().won()); } } - } } \ No newline at end of file -- cgit v1.2.3 From 3906fcf3d702f13da79c797c91a2dd32d874af49 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Tue, 22 Jun 2021 21:12:52 +0200 Subject: update(MancalaImpl) <- playPit returns stateArray --- domain/src/main/java/mancala/domain/Bowl.java | 2 + domain/src/main/java/mancala/domain/Kalaha.java | 12 ++++ domain/src/main/java/mancala/domain/Mancala.java | 2 +- .../src/main/java/mancala/domain/MancalaImpl.java | 21 ++++++- domain/src/main/java/mancala/domain/SmallBowl.java | 16 +++++- domain/src/test/java/mancala/domain/BowlTest.java | 18 ++++++ .../test/java/mancala/domain/MancalaImplTest.java | 64 ++++++++++++++++++++-- 7 files changed, 125 insertions(+), 10 deletions(-) diff --git a/domain/src/main/java/mancala/domain/Bowl.java b/domain/src/main/java/mancala/domain/Bowl.java index a36c621..07859f7 100644 --- a/domain/src/main/java/mancala/domain/Bowl.java +++ b/domain/src/main/java/mancala/domain/Bowl.java @@ -62,4 +62,6 @@ abstract class Bowl { protected abstract String makeString(String playerBowls, String opponentBowls, String kalahas); + + protected abstract int[] toStateArray(int[] stateArray, int index); } diff --git a/domain/src/main/java/mancala/domain/Kalaha.java b/domain/src/main/java/mancala/domain/Kalaha.java index c85af4a..411a86d 100644 --- a/domain/src/main/java/mancala/domain/Kalaha.java +++ b/domain/src/main/java/mancala/domain/Kalaha.java @@ -73,4 +73,16 @@ class Kalaha extends Bowl { void claimStolenBooty(int booty) { myStones = myStones + booty; } + + @Override + protected int[] toStateArray(int[] stateArray, int index) { + stateArray[index] = getMyStones(); + if (index == stateArray.length - 2) { + stateArray[stateArray.length - 1] = (getMyOwner().hasTheTurn() ? Mancala.PLAYER_TWO : Mancala.PLAYER_ONE); + return stateArray; + } else { + return getNextBowl().toStateArray(stateArray, ++index); + } + } + } diff --git a/domain/src/main/java/mancala/domain/Mancala.java b/domain/src/main/java/mancala/domain/Mancala.java index f8a2621..d80e9e5 100644 --- a/domain/src/main/java/mancala/domain/Mancala.java +++ b/domain/src/main/java/mancala/domain/Mancala.java @@ -24,7 +24,7 @@ public interface Mancala { * @param index Index of the recess to be played. * @return 15 item long Array with the current state of the game. The 15th item indicates which player has the next turn (possible values are 1 or 2). */ - void playPit(int index) throws MancalaException; + int[] playPit(int index) throws MancalaException; /** * Method for returning the amount of stones in de specified pit. Index is as specified below: diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java index a8e6691..0090f2d 100644 --- a/domain/src/main/java/mancala/domain/MancalaImpl.java +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -1,5 +1,6 @@ package mancala.domain; +import java.util.Arrays; import java.util.HashSet; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -27,6 +28,19 @@ public class MancalaImpl implements Mancala { domainOpponent = domainPlayer.getOpponent(); } + public MancalaImpl(int[] stonesArray) { + try { + domainReference = new SmallBowl( + Arrays.stream(stonesArray) + .boxed().collect(Collectors.toList()) + ); + } catch (DomainSmallBowlException e) { + e.printStackTrace(); + } + domainPlayer = domainReference.getMyOwner(); + domainOpponent = domainPlayer.getOpponent(); + } + @Override public boolean isPlayersTurn(int player) { switch (player) { @@ -40,7 +54,7 @@ public class MancalaImpl implements Mancala { } @Override - public void playPit(int index) throws MancalaException { + public int[] playPit(int index) throws MancalaException { if (isPlayersTurn(Mancala.PLAYER_ONE) && MancalaImpl.PLAYER_TWO_PITS.contains(index)) { throw new MancalaException("Player one cannot play player two's pits."); } @@ -61,6 +75,7 @@ public class MancalaImpl implements Mancala { domainReference.getNextSmallBowlTimes(skipKalahaIndex).play(); } + return domainReference.toStateArray(new int[15], 0); } @Override @@ -93,9 +108,9 @@ public class MancalaImpl implements Mancala { public int getWinner() { if (!isEndOfGame()) return Mancala.NO_PLAYERS; - if (domainPlayer.won()) return Mancala.PLAYER_ONE; + if (domainPlayer.won() && domainOpponent.won()) return Mancala.BOTH_PLAYERS; + else if (domainPlayer.won()) return Mancala.PLAYER_ONE; else if (domainOpponent.won()) return Mancala.PLAYER_TWO; - else if (domainPlayer.won() && domainOpponent.won()) return Mancala.BOTH_PLAYERS; else return Mancala.NO_PLAYERS; } diff --git a/domain/src/main/java/mancala/domain/SmallBowl.java b/domain/src/main/java/mancala/domain/SmallBowl.java index 216e2ad..b750f61 100644 --- a/domain/src/main/java/mancala/domain/SmallBowl.java +++ b/domain/src/main/java/mancala/domain/SmallBowl.java @@ -17,6 +17,9 @@ public class SmallBowl extends Bowl { if (stonesList.size() % 2 != 0) { throw new DomainSmallBowlException("Stones List should contain even number of elements."); } + if (stonesList.size() < 4) { + throw new DomainSmallBowlException("Stones list should have length greater than or equal to 4."); + } this.myOwner = new Player(); int boardSize = stonesList.size(); @@ -24,7 +27,9 @@ public class SmallBowl extends Bowl { this.myStones = stonesList.remove(0); - this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, stonesList, this, this.getMyOwner()); + if (boardSize == 4) this.nextBowl = new Kalaha(boardSize, bowlsToAdd, stonesList, this, this.getMyOwner()); + else this.nextBowl = new SmallBowl(boardSize, bowlsToAdd, stonesList, this, this.getMyOwner()); + referencePoint = this; } @@ -145,4 +150,13 @@ public class SmallBowl extends Bowl { } } + @Override + protected int[] toStateArray(int[] stateArray, int index) { + stateArray[index] = getMyStones(); + if (index == stateArray.length - 2) { + return stateArray; + } else { + return getNextBowl().toStateArray(stateArray, ++index); + } + } } diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java index 0f2ea4a..0614201 100644 --- a/domain/src/test/java/mancala/domain/BowlTest.java +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -143,6 +143,24 @@ class BowlTest { } } + @Test + void given_a_stones_list_with_less_than_four_elements_when_instantiating_small_bowl_then_throw_DomainSmallBowlException() { + int[] stonesArray = new int[] {1,2}; + List stonesList = Arrays.stream(stonesArray).boxed().collect(Collectors.toList()); + try { + referenceSmallBowl = new SmallBowl(stonesList); + fail("No exception when stones list is too small"); + } catch (DomainSmallBowlException e) { + } + stonesArray = new int[] {1,2,3,4}; + stonesList = Arrays.stream(stonesArray).boxed().collect(Collectors.toList()); + try { + referenceSmallBowl = new SmallBowl(stonesList); + } catch (DomainSmallBowlException e) { + fail("Should work fine"); + } + } + void setupGameSituationAndFailIfInvalid(int[] stonesArray) { try { referenceSmallBowl = new SmallBowl( diff --git a/domain/src/test/java/mancala/domain/MancalaImplTest.java b/domain/src/test/java/mancala/domain/MancalaImplTest.java index 6f0ce2a..61409ac 100644 --- a/domain/src/test/java/mancala/domain/MancalaImplTest.java +++ b/domain/src/test/java/mancala/domain/MancalaImplTest.java @@ -2,6 +2,7 @@ package mancala.domain; import org.junit.jupiter.api.*; +import java.util.Arrays; import java.util.HashSet; import static org.junit.jupiter.api.Assertions.*; @@ -128,8 +129,33 @@ class MancalaImplTest { playPitAndFailIfNoException(7, "Didn't throw exception when Pit was empty when played!"); } + @Test + void given_that_pit_has_stones_and_player_has_turn_when_playPit_is_called_then_return_array_representing_state() { + mancala = new MancalaImpl(new int[] {1,0,0,0,0,0,0,1,0,0,0,0,0,0}); + assumeTurn(Mancala.PLAYER_ONE); + int[] stateArray = playPitAndFailIfNotValid(0); + assertEquals(15, stateArray.length, + "length of the state array should be 15."); + assertTrue(Arrays.equals(new int[] {0,1,0,0,0,0,0,1,0,0,0,0,0,0,2}, stateArray)); + + stateArray = playPitAndFailIfNotValid(7); + assertEquals(15, stateArray.length, + "length of the state array should be 15."); + assertTrue(Arrays.equals(new int[] {0,1,0,0,0,0,0,0,1,0,0,0,0,0,1}, stateArray)); + } + } + /** + * Method for returning the amount of stones in de specified pit. Index is as specified below: + * + * 12 11 10 9 8 7 + * 13 6 + * 0 1 2 3 4 5 + * + * @param index Index of the pit. + * @return Amount of stone. + */ @Nested class getStonesForPit { @Test @@ -171,21 +197,33 @@ class MancalaImplTest { } + /** + * Method for retrieving whether the game has ended or not. + * + * @return True is the game has ended otherwise False. + */ @Nested class isEndOfGame { @Test - void given_the_game_is_not_ended_when_isEndOfGame_is_called_then_return_false() { + void given_the_game_has_not_ended_when_isEndOfGame_is_called_then_return_false() { assertFalse(mancala.isEndOfGame()); } @Test - void given_the_game_is_ended_when_isEndOfGame_is_called_then_return_true() { + void given_the_game_has_ended_when_isEndOfGame_is_called_then_return_true() { + mancala = new MancalaImpl(new int[] {0,0,0,0,0,1,0,0,0,0,0,0,0,0}); + playPitAndFailIfNotValid(5); assertTrue(mancala.isEndOfGame()); } } + /** + * Method for retrieving the player that has won the game. + * + * @return Integer value representing which player(s) (if any) won the game. + */ @Nested class getWinner { @Test @@ -195,12 +233,27 @@ class MancalaImplTest { @Test void given_PLAYER_ONE_has_won_in_the_domain_model_when_getWinner_is_called_then_return_Mancala_PLAYER_ONE() { + mancala = new MancalaImpl(new int[] {0,0,0,0,0,1,0,0,0,0,0,0,0,0}); + playPitAndFailIfNotValid(5); assertEquals(Mancala.PLAYER_ONE, mancala.getWinner()); } @Test void given_PLAYER_TWO_has_won_in_the_domain_model_when_getWinner_is_called_then_return_Mancala_PLAYER_TWO() { - assertEquals(Mancala.PLAYER_TWO, mancala.getWinner()); + mancala = new MancalaImpl(new int[] {0,1,0,0,0,0,0,0,0,0,0,2,0,0}); + playPitAndFailIfNotValid(1); + playPitAndFailIfNotValid(7 + 4); + playPitAndFailIfNotValid(7 + 5); + assertEquals(Mancala.PLAYER_TWO, mancala.getWinner(), + "PLAYER TWO should win here."); + } + + @Test + void given_BOTH_PLAYER_have_won_in_the_domain_model_when_getWinner_is_called_then_return_Mancala_BOTH_PLAYERS() { + mancala = new MancalaImpl(new int[] {0,0,0,0,0,1,0,0,0,0,0,0,1,0}); + playPitAndFailIfNotValid(5); + assertEquals(Mancala.BOTH_PLAYERS, mancala.getWinner(), + "PLAYER TWO should win here."); } } @@ -215,11 +268,12 @@ class MancalaImplTest { "It's PLAYER " + (player == 1 ? "ONE's" : "TWO's") + " turn!"); } - void playPitAndFailIfNotValid(int index) { + int[] playPitAndFailIfNotValid(int index) { try { - mancala.playPit(index); + return mancala.playPit(index); } catch (MancalaException e) { fail("Invalid play."); + return new int[0]; } } -- cgit v1.2.3 From 976ed2b105e80b257ebb724b3061400f37460041 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Thu, 24 Jun 2021 23:11:39 +0200 Subject: mega commit --- api/src/main/java/mancala/api/PlayMancala.java | 55 ++++++ api/src/main/java/mancala/api/StartMancala.java | 5 +- api/src/main/java/mancala/api/models/PlayInfo.java | 17 ++ client/src/App.css | 23 ++- client/src/Mancala/Mancala.tsx | 13 +- client/src/Mancala/Pit.css | 46 +++++ client/src/Mancala/Pit.tsx | 65 +++++++ client/src/Mancala/Play.css | 45 ++++- client/src/Mancala/Play.tsx | 207 ++++++++++++++++++++- client/src/Mancala/StartGame.tsx | 45 +++-- client/src/gameState.ts | 8 + client/src/pitState.ts | 0 client/src/pitState.tsx | 0 .../src/main/java/mancala/domain/MancalaImpl.java | 4 +- 14 files changed, 501 insertions(+), 32 deletions(-) create mode 100644 api/src/main/java/mancala/api/PlayMancala.java create mode 100644 api/src/main/java/mancala/api/models/PlayInfo.java create mode 100644 client/src/Mancala/Pit.css create mode 100644 client/src/Mancala/Pit.tsx create mode 100644 client/src/pitState.ts create mode 100644 client/src/pitState.tsx diff --git a/api/src/main/java/mancala/api/PlayMancala.java b/api/src/main/java/mancala/api/PlayMancala.java new file mode 100644 index 0000000..3b59710 --- /dev/null +++ b/api/src/main/java/mancala/api/PlayMancala.java @@ -0,0 +1,55 @@ +package mancala.api; + +import java.io.IOException; +import jakarta.servlet.http.*; +import jakarta.servlet.ServletException; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; + +import mancala.api.models.*; +import mancala.domain.MancalaException; +import mancala.domain.MancalaImpl; + +// public class StartMancala { +// @POST +// @Consumes(MediaType.APPLICATION_JSON) +// @Produces(MediaType.APPLICATION_JSON) +// public Response initialize( +// @Context HttpServletRequest request, +// PlayerInput players) { +// // var mancala = new MancalaImpl(); +// var mancala = new MancalaImpl(new int[] {0,1,2,3,4,5,6,7,8,9,10,11,12,13}); +// String namePlayer1 = players.getNameplayer1(); +// String namePlayer2 = players.getNameplayer2(); +// +// HttpSession session = request.getSession(true); +// session.setAttribute("mancala", mancala); +// session.setAttribute("player1", namePlayer1); +// session.setAttribute("player2", namePlayer2); +// +// var output = new Mancala(mancala, namePlayer1, namePlayer2); +// return Response.status(200).entity(output).build(); +// } +// } +@Path("/play") +public class PlayMancala { + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response initialize(@Context HttpServletRequest request, PlayInfo playInfo) { + + HttpSession session = request.getSession(); + + MancalaImpl mancala = (MancalaImpl) session.getAttribute("mancala"); + String namePlayer1 = (String) session.getAttribute("player1"); + String namePlayer2 = (String) session.getAttribute("player2"); + try { + mancala.playPit(playInfo.getIndex()); + } catch (MancalaException e) { + return Response.status(403).build(); + } + var output = new Mancala(mancala, namePlayer1, namePlayer2); + + return Response.status(200).entity(output).build(); + } +} diff --git a/api/src/main/java/mancala/api/StartMancala.java b/api/src/main/java/mancala/api/StartMancala.java index 7bd2330..62339d7 100644 --- a/api/src/main/java/mancala/api/StartMancala.java +++ b/api/src/main/java/mancala/api/StartMancala.java @@ -15,12 +15,13 @@ public class StartMancala { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response initialize( - @Context HttpServletRequest request, + @Context HttpServletRequest request, PlayerInput players) { var mancala = new MancalaImpl(); + // var mancala = new MancalaImpl(new int[] {40,40,40,40,40,40,40,40,40,40,40,40,40,40}); String namePlayer1 = players.getNameplayer1(); String namePlayer2 = players.getNameplayer2(); - + HttpSession session = request.getSession(true); session.setAttribute("mancala", mancala); session.setAttribute("player1", namePlayer1); diff --git a/api/src/main/java/mancala/api/models/PlayInfo.java b/api/src/main/java/mancala/api/models/PlayInfo.java new file mode 100644 index 0000000..9caca4d --- /dev/null +++ b/api/src/main/java/mancala/api/models/PlayInfo.java @@ -0,0 +1,17 @@ +package mancala.api.models; + +public class PlayInfo { + int player; + int index; + + + + public void setPlayer(int player) {this.player = player;} + public int getPlayer() {return player;} + + public void setIndex(int index) { + this.index = index; + } + public int getIndex() {return index;} + +} diff --git a/client/src/App.css b/client/src/App.css index 8eebe3a..1d59297 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -1,9 +1,28 @@ +html { + height: 100%; +} + body { margin: 0px; padding: 0px; background-color: #eeeeee; + height: 100%; +} + +#root { + height: 100%; + --board-color: rgb(222,184,135); + --recess-color: rgb(188,143,143); } .main-content { - padding: 8px; -} \ No newline at end of file + display: flex; + + justify-content: center; + align-items: center; + align-self: center; + + flex: 0 1 auto; + + height: calc(100% - (107px)) +} diff --git a/client/src/Mancala/Mancala.tsx b/client/src/Mancala/Mancala.tsx index 1c712c8..509410b 100644 --- a/client/src/Mancala/Mancala.tsx +++ b/client/src/Mancala/Mancala.tsx @@ -9,7 +9,7 @@ import "./Mancala.css"; * users to enter two player names and start a new game. * If there's an active game this component holds the game state. This game state can be passed as a prop * to child components as needed. - * + * * Child components can modify the game state by calling the setGameState (which they recieve as prop.) */ export function Mancala() { @@ -20,9 +20,16 @@ export function Mancala() { // To check if code you added also follows the rules of hooks, run "npm run lint" in the command line const [ gameState, setGameState ] = useState(undefined); + if (localStorage.getItem("state") !== null) { + var state = localStorage.getItem("state"); + const gameState = JSON.parse(state as string); + return + } + if (!gameState) { - return + return } + return -} \ No newline at end of file +} diff --git a/client/src/Mancala/Pit.css b/client/src/Mancala/Pit.css new file mode 100644 index 0000000..8cd3f63 --- /dev/null +++ b/client/src/Mancala/Pit.css @@ -0,0 +1,46 @@ +.Pit { + display: inline-flex; + + justify-content: center; + align-items: center; + + width: 16%; +} + +.Recession { + display: flex; + justify-content: center; + align-items: center; + background: var(--recess-color); +} + +.Pit .Recession { + border-radius: 50%; + height: 50%; + width: 100%; +} + +.Kalaha { + display: inline-flex; + + justify-content: center; + align-items: center; + + width: 10%; +} + +.Kalaha .Recession { + border-radius: 30%; + height: 50%; + width: 100%; +} + +.Stone { + position: absolute; + background: gray; + border-radius: 50%; + border: 2px solid black; + height: 16px; + width: 16px; + min-height: 5px; +} diff --git a/client/src/Mancala/Pit.tsx b/client/src/Mancala/Pit.tsx new file mode 100644 index 0000000..300f3f4 --- /dev/null +++ b/client/src/Mancala/Pit.tsx @@ -0,0 +1,65 @@ +import React, { useState } from "react"; +import type { GameState } from "../gameState"; +import "./Pit.css"; + +type Flatten = T extends any[] ? T[number] : T; + +type Player = Flatten; + +type Pit = Flatten; + +type PitProps = { + player: Player; + index: number; + pit: Pit; + onClick?: (index: number, player: Player, pit: Pit) => (event: React.MouseEvent) => Promise + displayStones: (pit: Pit, spread: "small" | "big") => JSX.Element[] +} + +type SmallBowl = Pit & {kind: "small"}; +type Kalaha = Pit & {kind: "big"}; + + + +export function Pit({player, index, pit, onClick, displayStones}: PitProps) { + const smallBowl = pit as SmallBowl; + smallBowl.kind = "small"; + if (onClick) { + return ( +
+ {pit.nrOfStones > 10 ? "" + pit.nrOfStones : ""} +
+
+ {displayStones(pit, smallBowl.kind)} +
+
+
+ ); + } else { + return ( +
+
+
+ {pit.nrOfStones > 10 ? pit.nrOfStones : ""} + {displayStones(pit, smallBowl.kind)} +
+
+
+ ); + } +} + +export function Kalaha({player, pit, displayStones}: PitProps) { + const kalaha = pit as Kalaha; + kalaha.kind = "big"; + return ( +
+ {pit.nrOfStones > 10 ? pit.nrOfStones : ""} +
+
+ {displayStones(pit, kalaha.kind)} +
+
+
+ ); +} diff --git a/client/src/Mancala/Play.css b/client/src/Mancala/Play.css index 8f73945..6aa0d8f 100644 --- a/client/src/Mancala/Play.css +++ b/client/src/Mancala/Play.css @@ -1 +1,44 @@ -/** Add some styles */ \ No newline at end of file +/** Add some styles */ +#board { + display: flex; + background: var(--board-color); + padding: 20px; + + border-radius: 10%; + + justify-content: center; + flex-flow: row; + flex-direction: row; + + width: 800px; + height: 400px; +} + +#pits { + position: relative; + display: inline-flex; + + + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + + width: 80%; +} + +.playArea { + position: relative; +} + +.playerStatus { + position: relative; + display: flex; + + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; + + height: 100px; + +} diff --git a/client/src/Mancala/Play.tsx b/client/src/Mancala/Play.tsx index b72bce1..d2d7f65 100644 --- a/client/src/Mancala/Play.tsx +++ b/client/src/Mancala/Play.tsx @@ -1,17 +1,216 @@ -import React from "react"; +import React, { useState } from "react"; import type { GameState } from "../gameState"; import "./Play.css"; +import { Pit, Kalaha } from "./Pit" + +type Flatten = T extends any[] ? T[number] : T; + +type Player = Flatten; + +type Pit = Flatten; type PlayProps = { gameState: GameState; setGameState(newGameState: GameState): void; } +function xyTranslate(pit: Pit, index: number) { + var angle = index/pit.nrOfStones * 2 * Math.PI; + var d = (index % 2 + 1) * 13; + var ofX = -9; + var ofY = -9; + if (pit.index === 6 || pit.index === 13) { + var xrandom = Math.random(); + var yrandom = Math.random(); + return 'translate('+ ((xrandom > 0.5 ? Math.random() : -Math.random()) * 18 - 10) + 'px, ' + (yrandom > 0.5 ? Math.random() : -Math.random()) * 60 + 'px)'; + } else { + return 'translate('+ (Math.cos(angle) * (d) + ofX) + 'px, ' + (Math.sin(angle) * (d) + ofY) + 'px)'; + } +} + export function Play({ gameState, setGameState }: PlayProps) { + + function playPit(index: number, player: Player, pit: Pit) { + const pitTotal = gameState.players[0].pits.length; + const allPits = gameState.players[0].pits.concat(gameState.players[1].pits); + const stonesToPass = pit.state.stoneElements as JSX.Element[]; + return async function event(event: React.MouseEvent) { + if (!player.hasTurn) return; + console.log("updating server"); + console.log(pit.nrOfStones); + try { + const response = await fetch('mancala/api/play', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({player: player.type === "player1" ? 0 : 1, index: index}) + }); + + if (response.ok) { + const newGameState = await response.json(); + localStorage.setItem("state", JSON.stringify(newGameState)); + setGameState({gameState, ...newGameState}); + } else { + console.error(response.statusText); + } + } catch (error) { + console.error(error.toString()); + } + console.log(pit.nrOfStones); + + } + } + + // function animate(newGameState: GameState) { + // var pits = newGameState.players[0].pits.concat(newGameState.players[1].pits); + // var type; + // for (var pit of pits) { + // type = pit.index === 6 || pit.index === 13 ? "big" : "small" as "big" | "small"; + // displayStones(pit, type); + // setTimeout(() => {}, 1000); + // } + // }; + + function stoneStyler(pit: Pit, index: number, spread: "small" | "big", ) { + const colors = ['gray', 'purple', 'blue', 'green']; + var stoneStyle; + if (spread === "small") { + stoneStyle = { + transform: xyTranslate(pit, index), + background: colors[index % colors.length], + }; + } else { + stoneStyle = { + transform: xyTranslate(pit, index), + background: colors[index % colors.length], + }; + } + return stoneStyle; + } + + function displayStones(pit: Pit, spread: "small" | "big") { + var jsx; + var kind = pit.index === 6 || pit.index === 13 ? "big" : "small" as "small"|"big"; + if (pit.state.stoneElements === undefined) { + pit.state.stoneElements = []; + for (let i = 0; i < pit.nrOfStones; i++) { + jsx = (
); + pit.state.stoneElements.reverse().push(jsx); + } + } else if (pit.nrOfStones < pit.state.stoneElements.length) { + // while (pit.nrOfStones < pit.state.stoneElements.length) { + // pit.state.stoneElements.pop(); + // } + pit.state.stoneElements = []; + for (let i = 0; i < pit.nrOfStones; i++) { + jsx = (
); + pit.state.stoneElements.reverse().push(jsx); + } + } else if (pit.nrOfStones > pit.state.stoneElements.length) { + pit.state.stoneElements = []; + for (let i = 0; i < pit.nrOfStones; i++) { + jsx = (
); + pit.state.stoneElements.reverse().push(jsx); + } + } + return pit.state.stoneElements; + } + + const playerPits = gameState.players.map( + player => { + return player.pits.slice(0, -1).map( + (pit, index) => { + const [ pitState, setPitState ] = useState({stoneElements: undefined}); + pit.state = pitState; + pit.setPitState = setPitState; + return ( + + ) + }); + }); + + const playerKalahas = gameState.players.map( + (player, index) => { + const [ kalahaState, setKalahaState ] = useState({stoneElements: undefined}); + const pit = player.pits[player.pits.length - 1]; + pit.state = kalahaState; + pit.setPitState = setKalahaState; + return ( + + ) + } + ); + + var player2Pits = []; + var player1Pits = []; + + for (var i = 0; i < playerPits[0].length; i++) { + player1Pits.push(playerPits[0][i]); + player2Pits.push(playerPits[1][i]); + } + + const revenge = ( + + ); + return ( -
-

{gameState.players[0].name} vs {gameState.players[1].name}

- To do... +
+
+ {gameState.players[1].name} has {gameState.players[1].pits.reduce((sum, current) => {return (sum + current.nrOfStones)}, 0)} stones. + {gameState.players[1].hasTurn ? " Also has the current turn" : ""} +
+
+ {playerKalahas[1]} +
+ {player2Pits.reverse()} + {player1Pits} +
+ {playerKalahas[0]} +
+
+ {gameState.players[0].name} has {gameState.players[0].pits.reduce((sum, current) => {return (sum + current.nrOfStones)}, 0)} stones. + {gameState.players[0].hasTurn ? "Also has the current turn" : ""} +
+ {gameState.gameStatus.endOfGame ? revenge : ""}
) } diff --git a/client/src/Mancala/StartGame.tsx b/client/src/Mancala/StartGame.tsx index 9d5be2c..4cf8d94 100644 --- a/client/src/Mancala/StartGame.tsx +++ b/client/src/Mancala/StartGame.tsx @@ -1,15 +1,17 @@ import React, { useState } from "react"; import type { GameState } from "../gameState"; import "./StartGame.css"; +import { Play } from "./Play" type StartGameProps = { + gameState: GameState | undefined; setGameState(newGameState: GameState): void; } /** * Allows the players to enter their name. A name is required for both players. They can't have the same names. */ -export function StartGame({ setGameState }: StartGameProps) { +export function StartGame({gameState, setGameState }: StartGameProps) { const [errorMessage, setErrorMessage] = useState(""); const [playerOne, setPlayerOne] = useState(""); @@ -52,23 +54,30 @@ export function StartGame({ setGameState }: StartGameProps) { } } - return ( -
tryStartGame(e)}> - setPlayerOne(e.target.value)} - /> + if (localStorage.getItem("state") !== null) { + var state = localStorage.getItem("state"); + const gameState = JSON.parse(state as string); + setGameState(gameState); + return + } else { + return ( + tryStartGame(e)}> + setPlayerOne(e.target.value)} + /> - setPlayerTwo(e.target.value)} - /> + setPlayerTwo(e.target.value)} + /> -

{errorMessage}

+

{errorMessage}

- - - ) -} \ No newline at end of file + + + ) + } +} diff --git a/client/src/gameState.ts b/client/src/gameState.ts index 2d5ec3e..b276136 100644 --- a/client/src/gameState.ts +++ b/client/src/gameState.ts @@ -16,4 +16,12 @@ interface Player { interface Pit { index: number; nrOfStones: number; + state: PitState; + setPitState: (newPitState: PitState) => void; } + + +interface PitState { + stoneElements: JSX.Element[] | undefined; + } + diff --git a/client/src/pitState.ts b/client/src/pitState.ts new file mode 100644 index 0000000..e69de29 diff --git a/client/src/pitState.tsx b/client/src/pitState.tsx new file mode 100644 index 0000000..e69de29 diff --git a/domain/src/main/java/mancala/domain/MancalaImpl.java b/domain/src/main/java/mancala/domain/MancalaImpl.java index 0090f2d..576eeb4 100644 --- a/domain/src/main/java/mancala/domain/MancalaImpl.java +++ b/domain/src/main/java/mancala/domain/MancalaImpl.java @@ -77,7 +77,7 @@ public class MancalaImpl implements Mancala { return domainReference.toStateArray(new int[15], 0); } - + @Override public int getStonesForPit(int index) { if (MancalaImpl.PLAYER_ONE_PITS.contains(index)) { @@ -114,4 +114,4 @@ public class MancalaImpl implements Mancala { else return Mancala.NO_PLAYERS; } -} \ No newline at end of file +} -- cgit v1.2.3 From c408d4ead869da802246c87a724e594ce639b883 Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Thu, 24 Jun 2021 23:13:11 +0200 Subject: ? --- client/src/Mancala/StartGame.tsx | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/client/src/Mancala/StartGame.tsx b/client/src/Mancala/StartGame.tsx index 4cf8d94..461148e 100644 --- a/client/src/Mancala/StartGame.tsx +++ b/client/src/Mancala/StartGame.tsx @@ -54,30 +54,23 @@ export function StartGame({gameState, setGameState }: StartGameProps) { } } - if (localStorage.getItem("state") !== null) { - var state = localStorage.getItem("state"); - const gameState = JSON.parse(state as string); - setGameState(gameState); - return - } else { - return ( -
tryStartGame(e)}> - setPlayerOne(e.target.value)} - /> + return ( + tryStartGame(e)}> + setPlayerOne(e.target.value)} + /> - setPlayerTwo(e.target.value)} - /> + setPlayerTwo(e.target.value)} + /> -

{errorMessage}

+

{errorMessage}

- -
- ) - } + + + ) } -- cgit v1.2.3 From 102b25f18d9b269c58d15677f10cd71c15003c4b Mon Sep 17 00:00:00 2001 From: Mike Vink Date: Fri, 25 Jun 2021 11:49:50 +0200 Subject: ending game from a steal should work now --- domain/src/main/java/mancala/domain/SmallBowl.java | 1 + domain/src/test/java/mancala/domain/BowlTest.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/domain/src/main/java/mancala/domain/SmallBowl.java b/domain/src/main/java/mancala/domain/SmallBowl.java index b750f61..b62ef43 100644 --- a/domain/src/main/java/mancala/domain/SmallBowl.java +++ b/domain/src/main/java/mancala/domain/SmallBowl.java @@ -88,6 +88,7 @@ public class SmallBowl extends Bowl { getMyOwner().switchTurn(); + endTheGame(); } SmallBowl getNextSmallBowl() { diff --git a/domain/src/test/java/mancala/domain/BowlTest.java b/domain/src/test/java/mancala/domain/BowlTest.java index 0614201..e2eedb9 100644 --- a/domain/src/test/java/mancala/domain/BowlTest.java +++ b/domain/src/test/java/mancala/domain/BowlTest.java @@ -272,7 +272,7 @@ class BowlTest { class endGameBehaviour { @Test - void given_all_small_bowls_of_the_player_are_empty_when_a_play_ends_then_tell_players_who_won() { + void given_all_small_bowls_of_the_player_that_did_the_turn_are_empty_when_a_play_ends_then_tell_players_who_won() { setupGameSituationAndFailIfInvalid(new int[] {0,0,0,0,0,1,0,4,4,4,4,4,4,0}); Player player = referenceSmallBowl.getMyOwner(); Player opponent = referenceSmallBowl.getNextSmallBowlTimes(6).getMyOwner(); @@ -302,6 +302,15 @@ class BowlTest { assertTrue(referenceSmallBowl.getMyOwner().won() && referenceSmallBowl.getMyOwner().getOpponent().won()); } + @Test + void given_that_the_opponents_board_is_emptied_by_stealing_when_player_made_a_play_then_tell_players_who_won() { + setupGameSituationAndFailIfInvalid(new int[] {1,0,0,0,1,0,0,1,0,0,0,0,0,0}); + System.out.println(referenceSmallBowl.stateString()); + referenceSmallBowl.getNextSmallBowlTimes(4).play(); + System.out.println(referenceSmallBowl.stateString()); + assertTrue(referenceSmallBowl.getMyOwner().won() || referenceSmallBowl.getMyOwner().getOpponent().won()); + } + } } } \ No newline at end of file -- cgit v1.2.3