I have created a Master Mind BoardSolver
. To apply the BoardSolver
I have created an app (MasterMind
) in which I create a Board
with ColorPin
s and CheckPin
s. ColorPin
s and CheckPin
s are associated within a PinRow
. A Board
represents a given PinRow
(code) and a List of PinRow
s as attempts.
Please can you review this code?
Structure
Code
Board
package com.github.martinfrank.mastermind;
import java.util.ArrayList;
import java.util.List;
public class Board {
private final int maxAttempts;
private final PinRow code;
private final List<PinRow> attempts = new ArrayList<>();
public Board(PinRow code, int maxAmountTrials) {
this.code = code;
this.maxAttempts = maxAmountTrials;
}
public List<PinRow> getAttempts() {
return attempts;
}
public List<CheckPin> addAttempt(PinRow trial) {
attempts.add(trial);
trial.check(code);
return trial.getCheckResult();
}
private List<CheckPin> getLatestResult() {
PinRow pinRow = attempts.get(attempts.size() - 1);
pinRow.check(code);
return pinRow.getCheckResult();
}
public boolean isSolved() {
return code.getWidth() == getLatestResult().stream().filter(CheckPin.BLACK::equals).count();
}
public int getCodeWidth() {
return code.getWidth();
}
public PinRow getCode() {
return code;
}
public int getMaxAttempts() {
return maxAttempts;
}
public boolean isAttemptLimitReached() {
return attempts.size() < maxAttempts;
}
}
ColorPin
package com.github.martinfrank.mastermind;
import java.util.*;
public enum ColorPin {
RED, GREEN, BLUE, YELLOW, PURPLE, WHITE, ORANGE, GRAY, BLACK, CYAN;
public static Set<ColorPin> randomSet(int amountColors) {
List<ColorPin> all = Arrays.asList(ColorPin.values());
Collections.shuffle(all);
return new HashSet<>(all.subList(0, amountColors));
}
public static int maxWordLength(){
return Arrays.stream(values()).mapToInt(c -> c.toString().length()).max().orElse(0);
}
}
CheckPin
package com.github.martinfrank.mastermind;
public enum CheckPin {
WHITE, //proper color, wrong location
BLACK; //proper color, proper location
}
PinRow
package com.github.martinfrank.mastermind;
import java.util.*;
import java.util.stream.IntStream;
public class PinRow {
private final int width;
private final ColorPin[] colors;
private List<CheckPin> checkResult = new ArrayList<>();
public PinRow(int[] indices, Set<ColorPin> givenColors) {
List<ColorPin> sampleColors = new ArrayList<>(givenColors);
width = indices.length;
colors = new ColorPin[width];
IntStream.range(0, width).forEach(c -> colors[c] = sampleColors.get(indices[c]));
}
public static PinRow randomPinRow(int codeWidth, Set<ColorPin> colors) {
int[] indices = new int[codeWidth];
int amountColors = colors.size();
IntStream.range(0, codeWidth).forEach(c -> indices[c] = new Random().nextInt(amountColors));
return new PinRow(indices, colors);
}
public List<CheckPin> check(PinRow code) {
checkResult.clear();
List<ColorPin> myRemaining = new ArrayList<>();
List<ColorPin> codeRemaining = new ArrayList<>();
for (int i = 0; i < width; i++) {
ColorPin current = colors[i];
ColorPin expected = code.colors[i];
if (current == expected) {
checkResult.add(CheckPin.BLACK);
} else {
myRemaining.add(current);
codeRemaining.add(expected);
}
}
for (ColorPin mine : myRemaining) {
for (ColorPin candidate : codeRemaining) {
if (mine.equals(candidate)) {
codeRemaining.remove(candidate);
checkResult.add(CheckPin.WHITE);
break;
}
}
}
return checkResult;
}
@Override
public String toString(){
return Arrays.toString(colors);
}
public List<CheckPin> getCheckResult() {
return checkResult;
}
public List<ColorPin> getColorPins() {
return Arrays.asList(colors);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PinRow pinRow = (PinRow) o;
return Arrays.equals(colors, pinRow.colors);
}
@Override
public int hashCode() {
return Arrays.hashCode(colors);
}
public int getWidth() {
return width;
}
}
PinChecker
package com.github.martinfrank.mastermind;
import java.util.List;
public class PinChecker {
private final List<CheckPin> reference;
public PinChecker(List<CheckPin> reference) {
this.reference = reference;
}
public boolean isSuperior(List<CheckPin> candidate){
if( candidate.size() < reference.size()){
return false;
}
if( candidate.size() == reference.size()){
long baseBlack = reference.stream().filter(cp -> cp==CheckPin.BLACK).count();
long candidateBlack = candidate.stream().filter(cp -> cp==CheckPin.BLACK).count();
return candidateBlack <= baseBlack;
}
return false;
}
}
BoardSolver
package com.github.martinfrank.mastermind;
import java.util.*;
import java.util.stream.Collectors;
public class BoardSolver {
private final Board board;
private final Set<ColorPin> colors;
private final Random random;
public BoardSolver(Board board, Set<ColorPin> colors) {
this.board = board;
this.colors = colors;
random = new Random();
}
public void solve() {
int codeWidth = board.getCodeWidth();
List<PinRow> combinations = PinRowCombinations.getAll(colors, codeWidth);
do {
PinRow attempt = removeRandom(combinations);
PinChecker pinChecker = new PinChecker(board.addAttempt(attempt));
combinations = combinations.stream().filter(pr -> pinChecker.isSuperior(pr.check(attempt))).collect(Collectors.toList());
} while (!board.isSolved() && board.isAttemptLimitReached());
}
private PinRow removeRandom(List<PinRow> combinations) {
return combinations.remove(random.nextInt(combinations.size()));
}
}
PinRowCombinations
package com.github.martinfrank.mastermind;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
public class PinRowCombinations {
public static List<PinRow> getAll(Set<ColorPin> colors, int width) {
int amountColors = colors.size();
int size = (int)Math.pow( amountColors, width);
List<PinRow> result = new ArrayList<>();
for (int i = 0; i < size; i ++){
BigInteger bi = BigInteger.valueOf(i);
int[] indices = createIndice(leadingZeros(bi.toString(amountColors), width) );
result.add(new PinRow(indices, colors));
}
return result;
}
private static String leadingZeros(String numberString, int size) {
StringBuilder sb = new StringBuilder();
IntStream.range(0,size-numberString.length()).forEach(i -> sb.append("0"));
sb.append(numberString);
return sb.toString();
}
private static int[] createIndice(String numberString) {
int length = numberString.length();
int[] indice = new int[length];
IntStream.range(0,length).forEach(i -> indice[i] = createIndex(numberString.charAt(i)));
return indice;
}
private static int createIndex(char charAt) {
if (charAt >= '0' && charAt <= '9'){
return charAt-'0';
}else{
return charAt-'a'+10;
}
}
}
BoardPrinter
package com.github.martinfrank.mastermind;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class BoardPrinter {
public static void print(Board board){
final int columnSize = ColorPin.maxWordLength();
final int codeWidth = board.getCodeWidth();
final PinRow code = board.getCode();
String separator = createSeparator(columnSize+2, codeWidth);
String bigSeparator = createSeparator(columnSize+2, codeWidth, '=');
System.out.println((board.isSolved()?"":"not ")+"solved in "+board.getAttempts().size()+" turns...");
System.out.println(separator);
System.out.println(printRow(code, columnSize)+emptyPins(codeWidth, columnSize));
System.out.println(bigSeparator);
for(int i = board.getMaxAttempts()-1; i >= 0; i --){
if(i < board.getAttempts().size() ){
PinRow trial = board.getAttempts().get(i);
System.out.println(printRow(trial, columnSize)+printCheck(trial.getCheckResult(), codeWidth, columnSize));
}else{
System.out.println(emptyPins(codeWidth, columnSize)+emptyPins(codeWidth, columnSize));
}
System.out.println(separator);
}
}
private static String emptyPins(int codeWidth, int columnSize) {
StringBuilder sb = new StringBuilder("|");
IntStream.range(0,codeWidth).forEach(i -> append(sb, " ", centerString("", columnSize), " |"));
return sb.toString();
}
private static String createSeparator(int columsSize, int codeWidth) {
return createSeparator(columsSize, codeWidth, '-');
}
private static String createSeparator(int columsSize, int codeWidth, char c) {
StringBuilder sb = new StringBuilder("+");
String stroke = createStroke(columsSize, c);
IntStream.range(0,codeWidth).forEach(i -> append(sb,stroke,"+"));
sb.append("+");
IntStream.range(0,codeWidth).forEach(i -> append(sb,stroke,"+"));
return sb.toString();
}
private static String createStroke(int strokeLength, char c) {
StringBuilder sb = new StringBuilder();
IntStream.range(0,strokeLength).forEach(i -> sb.append(c));
return sb.toString();
}
private static void append(StringBuilder sb, String... values){
Arrays.stream(values).forEach(sb::append);
}
private static String printCheck(List<CheckPin> checkers, int codeWidth, int columnLength) {
StringBuilder sb = new StringBuilder("|");
for(int i = 0; i < codeWidth; i++){
String value = i < checkers.size()?checkers.get(i).toString():" ";
append(sb, " ",centerString(value, columnLength)," |");
}
return sb.toString();
}
private static String printRow(PinRow row, int columnLength) {
StringBuilder sb = new StringBuilder("|");
row.getColorPins().forEach(cp -> append(sb, " ",centerString(cp.toString(), columnLength)," |"));
return sb.toString();
}
public static String centerString (String string, int width) {
return String.format("%-" + width + "s", String.format("%" + (string.length() + (width - string.length()) / 2) + "s", string));
}
}
MasterMind
package com.github.martinfrank.mastermind;
import java.util.Set;
public class MasterMind {
public static void main(String[] args){
final int codeWidth = 4;
final Set<ColorPin> colors = ColorPin.randomSet(6);
final int maxAmountTrials = 20;
final PinRow code = PinRow.randomPinRow(codeWidth, colors);
Board board = new Board(code, maxAmountTrials);
BoardSolver solver = new BoardSolver(board, colors);
solver.solve();
BoardPrinter.print(board);
}
}
Accepted Answer
I've been sick for a few days so i was not able to react any sooner. It's hard for me to pick the right answer out of these here, they are all really helpful and every answer discovers more improvements on my code. So I can't pick one and accept it without disrespecting the answers of the others. That's why i don't accept one but all answer here!!!