-
Notifications
You must be signed in to change notification settings - Fork 1
/
MastermindGame.java
283 lines (248 loc) · 8.99 KB
/
MastermindGame.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import java.util.*;
public class MastermindGame {
private static final String[] validColors = {
"w", //White
"b", //Blue
"r", //Red
"y", //Yellow
"g", //Green
"o", //Orange
};
private final int CODE_LENGTH = 4;
private String[] hiddenCode = new String[CODE_LENGTH];
private final int NUMBER_OF_TURNS = 12;
private String[][] guesses = new String[NUMBER_OF_TURNS][];
private int correctLocationAndColorCount = 0;
private int correctColorWrongPlaceCount = 0;
private boolean won = false;
private int gameCount = 0;
private double guessesTotal = 0;
private boolean providedHint = false; //whether the user asked for a hint
private int currentHintIndex = 0;
private boolean quit = false;
private ArrayList<String[]> availableComputerGuesses = new ArrayList<String[]>(); //6! factorial combinations
private String[] lastComputerGuess;
public MastermindGame(){
System.out.println("Mastermind");
start();
}
private ArrayList<String[]> generateAllPossibleGuesses(){
ArrayList<String[]> allPossibleGuesses = new ArrayList<String[]>();
for (int digit1 = 0; digit1 < validColors.length; digit1++){
for (int digit2 = 0; digit2 < validColors.length; digit2++){
for (int digit3 = 0; digit3 < validColors.length; digit3++){
for (int digit4 = 0; digit4 < validColors.length; digit4++) {
if (digit1 != digit2 && digit1 != digit3 && digit1 != digit4
&& digit2 != digit3 && digit2 != digit4
&& digit3 != digit4)
{
allPossibleGuesses.add(new String[]{validColors[digit1], validColors[digit2], validColors[digit3], validColors[digit4]});
}
}
}
}
}
return allPossibleGuesses;
}
//Random number to create code, allows repeated colors
private void generateHiddenCode(){
ArrayList<String> usedColors = new ArrayList<String>();
for(int i=0; i<hiddenCode.length; ){
int randomIndexForColor = (int)(Math.random() * validColors.length);
String randomColor = validColors[randomIndexForColor];
if(!usedColors.contains(randomColor)){
hiddenCode[i] = randomColor;
usedColors.add(randomColor);
i++;
}
}
}
private void start(){
Scanner keyboard = new Scanner(System.in);
String gameMode = "";
guesses = new String[NUMBER_OF_TURNS][];
boolean playAgain = true;
while(playAgain){
System.out.print("Choose game mode (player/computer): ");
String previousGameMode = gameMode; //Save old mode to see if score statistics be reset
gameMode = sanitize(keyboard.next());
keyboard.nextLine();
while(!gameMode.equals("player") && !gameMode.equals("computer")){
System.out.print("Error. Please only enter \"player\" or \"computer\": ");
gameMode = sanitize(keyboard.next());
keyboard.nextLine();
}
if(!previousGameMode.equals(gameMode)){ //Reset statistics if changing between computer/player game modes
guessesTotal=0;
gameCount=0;
}
if(gameMode.equals("player")){
generateHiddenCode();
System.out.print("Enter a 4-character guess for the code's colors");
}
else{ //Computer guessing mode
availableComputerGuesses = generateAllPossibleGuesses();
System.out.print("Enter a 4-digit code using distinct colors for the computer to guess: ");
String code = sanitize(keyboard.nextLine());
while(!isValueComputerCode(code)){
System.out.print("Invalid! Must be 4 characters, distinct & using valid colors: ");
code = sanitize(keyboard.nextLine());
}
hiddenCode = code.split("");
}
won = false;
for(int turn=0; turn<NUMBER_OF_TURNS && !won; ){
if(gameMode.equals("player")){
executeUserGuess(turn, keyboard);
if(quit){
break;
}
}
else{
executeComputerGuess(turn, keyboard);
}
if(!providedHint){
turn++; //only increment turn if they DIDN't ask for a hint
System.out.println("\tCorrect location & Color = "+correctLocationAndColorCount + "\tCorrect Color, wrong location = "+correctColorWrongPlaceCount);
}
}
if(won){
System.out.println("Correct Code!");
}
else if(!quit){
System.out.println("\nRan out of turns\n" + "The correct code was " + Arrays.toString(hiddenCode));
}
gameCount++;
if(quit){
System.out.println("Game quit");
}
else{
System.out.println("Average guesses per game = "+getAverage());
}
System.out.print("\nPlay again? (y/n) ");
playAgain=false; //assume game will not repeat & ask for user input
if(sanitize(keyboard.next()).charAt(0) == 'y'){
playAgain=true;
}
}
System.out.println("Goodbye");
keyboard.close();
}
private void executeUserGuess(int turn, Scanner keyboard){
System.out.println("\nPress:");
System.out.println("w for white, r for red, y for yellow");
System.out.println("b for blue, g for green, o for orange");
System.out.print("Guess #"+(turn+1)+": ");
String guess = sanitize(keyboard.nextLine());
providedHint = false;
quit=false;
if(guess.equals("h")){
providedHint=true;
provideHint();
}
else if(guess.equals("q")){
quit=true;
}
else{
while(guess.length() != CODE_LENGTH){
System.out.print("Invalid! Guess must be 4 characters: ");
guess = sanitize(keyboard.nextLine());
}
guessesTotal++;
guesses[turn] = guess.split("");
if(isCorrectCode(guesses[turn])){
won = true;
}
}
}
private void provideHint(){
System.out.println("Position "+(currentHintIndex+1)+" is: "+hiddenCode[currentHintIndex]);
if(currentHintIndex<CODE_LENGTH-1){
currentHintIndex++;
}
else{
System.out.println("(You've used up all the hints)");
}
}
private void executeComputerGuess(int turn, Scanner keyboard){
guessesTotal++;
lastComputerGuess = availableComputerGuesses.get((int) (Math.random()*availableComputerGuesses.size()) ); //pick random from remaining possible guesses
guesses[turn] = lastComputerGuess;
System.out.print("Computer guess #"+(turn+1)+": "+Arrays.toString(lastComputerGuess));
if(isCorrectCode(lastComputerGuess)){
won = true;
return;
}
else{ //Remove current guess from pool if it's not correct
availableComputerGuesses.remove(lastComputerGuess);
}
//Save result from current guess to compare with all other remaining guesses
int previousCorrectCount=correctLocationAndColorCount;
int previousCloseCount=correctColorWrongPlaceCount;
for (int i = 0; i < availableComputerGuesses.size(); i++) {
previousCorrectCount=correctLocationAndColorCount;
previousCloseCount=correctColorWrongPlaceCount;
String[] possibleGuess = availableComputerGuesses.get(i);
//Remove all combinations that give a different response than the current guess (also skip the correct code & DO NOT remove it from remaining guesses)
if(!isCorrectCode(possibleGuess) && (correctLocationAndColorCount!=previousCorrectCount || correctColorWrongPlaceCount!=previousCloseCount) ){
availableComputerGuesses.remove(i--); //remove guess & decrement I since removing from list shifts all items 1 index back
}
}
isCorrectCode(lastComputerGuess); //check original code to update global fields used in other functions (we evaluated all remaining guesses so if there is only 1 possiblity left, checking it will accidentally tell the method that called executeComputerGuess() to see that the code is correct)
}
private boolean isCorrectCode(String[] guess){
correctLocationAndColorCount = 0;
correctColorWrongPlaceCount = 0;
ArrayList<Integer> finalizedIndices = new ArrayList<Integer>();
ArrayList<String> colorsAlreadySearched = new ArrayList<String>();
//Search for correct location & correct color
for(int i=0; i<guess.length; i++){
if(guess[i].equals(hiddenCode[i])){
correctLocationAndColorCount++;
finalizedIndices.add(i);
}
}
//Search remaining spaces for correct colors in incorrect locations
for(int i=0; i<guess.length; i++){
for(int j=0; j<hiddenCode.length; j++){
if(!finalizedIndices.contains(j) && !colorsAlreadySearched.contains(guess[i]) && guess[i].equals(hiddenCode[j])){ //only check indices that aren't already correct & colors that haven't been searched
correctColorWrongPlaceCount++;
finalizedIndices.add(j);
colorsAlreadySearched.add(guess[i]);
}
}
}
return correctLocationAndColorCount == hiddenCode.length;
}
private double getAverage(){
return Math.round(guessesTotal/gameCount * 100.0) / 100.0;
}
private boolean isValueComputerCode(String code){
return code.length()==CODE_LENGTH && areColorsDistinct(code) && areValidColors(code);
}
private boolean areColorsDistinct(String guess){
Set<Character> seen = new HashSet<Character>();
for(char color : guess.toCharArray()) {
if (seen.contains(color)){
return false;
}
seen.add(color);
}
return true;
}
private boolean areValidColors(String code){
for(char color : code.toCharArray()) {
if(!Arrays.asList(validColors).contains(color+"")){
return false;
}
}
return true;
}
//Remove spaces, trim & convert to lowercase
private String sanitize(String input){
return input.trim().replaceAll(" ", "").toLowerCase();
}
public static void main(String[] args) {
MastermindGame game = new MastermindGame();
}
}