Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solution to the Coding Challenge #8

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### IntelliJ IDEA ###
.idea/
*.iws
*.iml
*.ipr

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
36 changes: 16 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
# Paper Rock Scissors Exercise
# Rock Paper Scissors

The following is a small exercise to get an idea of your coding and design skills. We would like you to develop a simple interactive game of [Paper Rock Scissors](https://en.wikipedia.org/wiki/Rock_paper_scissors)
## Requirements

It's intentionally not an algorithmically complex problem, so we're looking more at modelling and structure. We are trying to get a feel for how you code in a professional setting.
- Java 11+
- Maven 3

## Functional Requirements
* The user is presented with a CLI to play the game.
* A user can enter one of 3 inputs: paper, rock or scissors.
* The computer will choose one input at random.
* The game rules will be applied and a winner is chosen:
- Paper beats Rock
- Rock beats Scissors
- Scissors beats Paper.
- The same input is a tie.
* The game will repeat until the user explictly chooses to exit.
* On exit a summary is displayed of games won, lost, and tied.
## Build

## Non-Functional Requirements
* Create a branch of this repo and submit your solution as a PR.
* You can use any language you like, but we'd like to see your object oriented design skills, so best to use a language supporting OO.
* Write your code to the same standard you would professionally (object structure, design patterns, readability, testing/testability, extensibility)
* Write some unit tests for the key pieces of logic.
* Don't go overboard, this should only take a few hours.
To build the project execute the following command:

```bash
mvn package
```

## Run

After building, you can execute the jar file that is created in the `target` directory:

```bash
java -jar "target\RockPaperScissors-1.0.jar"
```
55 changes: 55 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>nl.mplb</groupId>
<artifactId>RockPaperScissors</artifactId>
<version>1.0</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<index>true</index>
<manifest>
<mainClass>nl.mplb.Main</mainClass>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
32 changes: 32 additions & 0 deletions src/main/java/nl/mplb/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package nl.mplb;

import nl.mplb.domain.Computer;
import nl.mplb.domain.Game;
import nl.mplb.domain.GameOutcome;
import nl.mplb.domain.Human;
import nl.mplb.domain.Referee;
import nl.mplb.domain.Score;
import nl.mplb.domain.UserInterface;

/** Initialises all other classes and is responsible for running multiple games. */
public class Main {

public static void main(String[] args) {
UserInterface userInterface = new UserInterface();
Score score = new Score();
Referee referee = new Referee();

userInterface.showWelcomeMessage();
boolean userContinue = true;
while (userContinue) {
Human human = new Human(userInterface);
Computer computer = new Computer();
Game game = new Game(userInterface, human, computer, referee);

GameOutcome gameOutcome = game.play();
score.processNewOutcome(gameOutcome);
userContinue = !userInterface.promptStopPlaying();
}
userInterface.showGameEndSummary(score.getWins(), score.getLoses(), score.getTies());
}
}
21 changes: 21 additions & 0 deletions src/main/java/nl/mplb/domain/Computer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nl.mplb.domain;

import java.util.Random;

/** The Computer player. A player where the chosen move is set by the computer. */
public class Computer implements IPlayer {
private final Random random = new Random();
private Moves chosenMove;

@Override
public Moves getChosenMove() {
return this.chosenMove;
}

@Override
public void makeMove() {
int nrMoves = Moves.values().length;
int chosen = random.nextInt(nrMoves);
this.chosenMove = Moves.values()[chosen];
}
}
41 changes: 41 additions & 0 deletions src/main/java/nl/mplb/domain/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package nl.mplb.domain;

/** The Game class is responsible for running a single game */
public class Game {
UserInterface userInterface;
IPlayer human;
IPlayer computer;
Referee referee;

public Game(UserInterface userInterface, IPlayer human, IPlayer computer, Referee referee) {
this.userInterface = userInterface;
this.human = human;
this.computer = computer;
this.referee = referee;
}

/**
* Runs through the steps to perform a single Game
*
* @return The outcome of a game
*/
public GameOutcome play() {
userInterface.buildExcitementBeforeGame();
makeMoves();
GameOutcome gameOutcome = determineGameOutcome();
userInterface.showGameResults(
this.human.getChosenMove(), this.computer.getChosenMove(), gameOutcome);
return gameOutcome;
}

private void makeMoves() {
this.human.makeMove();
this.computer.makeMove();
}

private GameOutcome determineGameOutcome() {
Moves humanMove = this.human.getChosenMove();
Moves computerMove = this.computer.getChosenMove();
return this.referee.determineGameOutcome(humanMove, computerMove);
}
}
7 changes: 7 additions & 0 deletions src/main/java/nl/mplb/domain/GameOutcome.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nl.mplb.domain;

public enum GameOutcome {
WIN,
LOSE,
TIE;
}
29 changes: 29 additions & 0 deletions src/main/java/nl/mplb/domain/Human.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package nl.mplb.domain;

import nl.mplb.exceptions.InvalidMoveException;

/** The Human player. A player where the chosen move is set by the user. */
public class Human implements IPlayer {
private final UserInterface userInterface;
private Moves chosenMove;

public Human(UserInterface userInterface) {
this.userInterface = userInterface;
}

public Moves getChosenMove() {
return this.chosenMove;
}

public void makeMove() {
// While no (known) move is chosen, keep asking
while (this.chosenMove == null) {
try {
String input = this.userInterface.promptMove();
this.chosenMove = Moves.fromString(input);
} catch (InvalidMoveException e) {
this.userInterface.showAvailableMoves();
}
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/nl/mplb/domain/IPlayer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nl.mplb.domain;

/** An interface implemented by all Players */
public interface IPlayer {
public Moves getChosenMove();

public void makeMove();
}
45 changes: 45 additions & 0 deletions src/main/java/nl/mplb/domain/Moves.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package nl.mplb.domain;

import nl.mplb.exceptions.InvalidMoveException;

/**
* Enum with the possible moves. Has methods to convert to and from a String, for input and display
* purposes.
*/
public enum Moves {
ROCK("Rock"),
PAPER("Paper"),
SCISSORS("Scissors");

private final String display;

Moves(String display) {
this.display = display;
}

public static Moves fromString(String input) {
if (input == null || input.isBlank()) {
throw new InvalidMoveException("Move can't be empty");
}

String normalizedInput = input.toLowerCase().strip();
switch (normalizedInput) {
case "r":
case "rock":
return ROCK;
case "p":
case "paper":
return PAPER;
case "s":
case "scissors":
return SCISSORS;
default:
throw new InvalidMoveException("Unknown move");
}
}

@Override
public String toString() {
return this.display;
}
}
30 changes: 30 additions & 0 deletions src/main/java/nl/mplb/domain/Referee.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nl.mplb.domain;

import static nl.mplb.domain.GameOutcome.LOSE;
import static nl.mplb.domain.GameOutcome.TIE;
import static nl.mplb.domain.GameOutcome.WIN;

/** The logic to determine the outcome of two moves */
public class Referee {

// Rock Paper Scissors
// Rock T L W
// Paper W T L
// Scissors L W T
private final GameOutcome[][] outcomeMatrix = {
{TIE, LOSE, WIN},
{WIN, TIE, LOSE},
{LOSE, WIN, TIE}
};

/**
* Determine the game outcome of two moves from the perspective of the first argument.
*
* @param m1 The move that was played by 'you'
* @param m2 The move that was played by the 'other'
* @return The game outcome
*/
public GameOutcome determineGameOutcome(Moves m1, Moves m2) {
return outcomeMatrix[m1.ordinal()][m2.ordinal()];
}
}
32 changes: 32 additions & 0 deletions src/main/java/nl/mplb/domain/Score.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package nl.mplb.domain;

import java.util.ArrayList;
import java.util.List;

/**
* A class to keep track of the score New game outcomes are added to the outcomes array. To
* determine the score the number of occurrences of each outcome is counted.
*/
public class Score {
private final List<GameOutcome> outcomes = new ArrayList<>();

public void processNewOutcome(GameOutcome outcome) {
this.outcomes.add(outcome);
}

public long getWins() {
return countNumberOf(GameOutcome.WIN);
}

public long getLoses() {
return countNumberOf(GameOutcome.LOSE);
}

public long getTies() {
return countNumberOf(GameOutcome.TIE);
}

private long countNumberOf(GameOutcome outcome) {
return outcomes.stream().filter(o -> o == outcome).count();
}
}
Loading