I recently started getting into machine learning and I wanted to write a "beginner program" which would learn to play Tic Tac Toe. This code was inspired by a different program I saw, meaning some ideas (like saving the valid moves in a String) are not from me.
You can either play against the AI or let it play against itself. You can choose whether or not to save the data in a text file via JSON. When playing against the AI, it uses that file as a base. It would be very nice to get some kind of feedback on this.
import com.google.gson.*; import com.google.gson.reflect.*; import java.util.*; import java.lang.*; import java.io.*; import java.nio.file.*; import java.nio.charset.Charset; public class run{ private static ArrayList<String> player1States; private static ArrayList<String> player1Choices; private static ArrayList<Integer> player1AccessedChoices; private static ArrayList<Integer> player1MovesChosen; private static ArrayList<String> player2States; private static ArrayList<String> player2Choices; private static ArrayList<Integer> player2AccessedChoices; private static ArrayList<Integer> player2MovesChosen; private static ArrayList<String> savedStates; private static ArrayList<String> savedChoices; private static int maxMovesInChoiceString; public static void main(String[] args)throws Exception{ Scanner s = new Scanner(System.in); System.out.println("mode?(1 = PvAI / 2 = AItraining):"); int mode = s.nextInt(); while(mode != 1 && mode != 2){ System.out.println("Not a valid mode. Choose again(1 = PvAI / 2 = AItraining):"); mode = s.nextInt(); } if(!fileExists("./", "AI.txt")){ saveResults(0); } if(mode==2){ System.out.println("Improve file?(1 = yes/ 2 = no)"); int ov = s.nextInt(); while(ov != 1 && ov != 2){ System.out.println("Not a valid choice. Choose again(1 = yes / 2 = no:"); ov = s.nextInt(); } boolean overwrite; if(ov==1){ overwrite = true; Gson g = new Gson(); String a = readFile("AI.txt"); JsonObject jobj = g.fromJson(a, JsonObject.class); JsonArray states = jobj.getAsJsonObject("data").getAsJsonArray("state"); JsonArray choices = jobj.getAsJsonObject("data").getAsJsonArray("choice"); savedStates = g.fromJson(states, new TypeToken<ArrayList<String>>(){}.getType()); savedChoices = g.fromJson(choices, new TypeToken<ArrayList<String>>(){}.getType()); player1States = savedStates; player1Choices = savedChoices; player2States = savedStates; player2Choices = savedChoices; }else{ overwrite = false; player1States = new ArrayList<String>(); player1Choices = new ArrayList<String>(); player2States = new ArrayList<String>(); player2Choices = new ArrayList<String>(); } player1AccessedChoices = new ArrayList<Integer>(); player1MovesChosen = new ArrayList<Integer>(); player2AccessedChoices = new ArrayList<Integer>(); player2MovesChosen = new ArrayList<Integer>(); maxMovesInChoiceString = 100; String currentState; char winner = ' '; boolean player1Learns; boolean player2Learns; int player1Wins = 0; int ovPlayer1Wins = 0; int player2Wins = 0; int ovPlayer2Wins = 0; int draws = 0; int ovDraws = 0; int counter = 0; System.out.println("Do toggle? (p1first = 1 / p2first = 2 / no toggle, both learn = 0)"); int doToggle = s.nextInt(); while(doToggle != 1 && doToggle != 2 && doToggle != 0){ System.out.println("Not a valid choice. Choose again: "); doToggle = s.nextInt(); } if(doToggle == 0){ player1Learns = true; player2Learns = true; }else if(doToggle == 1){ player1Learns = true; player2Learns = false; }else{ player1Learns = false; player2Learns = true; } System.out.println("How many repetitions per set?(10 sets):"); int reps = s.nextInt(); for(int sets = 0; sets < 10; sets++){ for(int i = 0; i < reps; i++){ currentState = getBlankState(); player1AccessedChoices.clear(); player1MovesChosen.clear(); player2AccessedChoices.clear(); player2MovesChosen.clear(); if (counter % 2 == 0){ while (true){ currentState = addMark(currentState, getNextMove(currentState, 2), 'O'); winner = checkWin(currentState); if (winner != ' ') break; currentState = addMark(currentState, getNextMove(currentState, 1), 'X'); winner = checkWin(currentState); if (winner != ' ') break; } }else{ while (true){ currentState = addMark(currentState, getNextMove(currentState, 1), 'X'); winner = checkWin(currentState); if (winner != ' ') break; currentState = addMark(currentState, getNextMove(currentState, 2), 'O'); winner = checkWin(currentState); if (winner != ' ') break; } } counter++; if (winner == 'O'){ if(player1Learns) evaluate(1,2); if(player2Learns) evaluate(2,1); player2Wins++; }else if (winner == 'X'){ if(player1Learns) evaluate(1,1); if(player2Learns) evaluate(2,2); player1Wins++; }else if (winner == 'D'){ if(player1Learns) evaluate(1,0); if(player2Learns) evaluate(2,0); draws++; }else{ throw new RuntimeException("Bad response from checkWin "); } } System.out.println(""); System.out.println("Player1Wins 'X': " + player1Wins); System.out.println("Player2Wins 'O': " + player2Wins); System.out.println("Draws: " + draws); ovPlayer1Wins+=player1Wins; ovPlayer2Wins+=player2Wins; ovDraws+=draws; player1Wins = 0; player2Wins = 0; draws =0; if(doToggle != 0){ if(player1Learns){ player1Learns = !player1Learns; }if(player2Learns){ player2Learns = !player2Learns; } } } System.out.println(" "); System.out.println("Games played: "+counter); System.out.println("Overall player1 wins: "+ovPlayer1Wins); System.out.println("Overall player2 wins: "+ovPlayer2Wins); System.out.println("Overall draws: "+ovDraws); if(overwrite){ if(ovPlayer1Wins > ovPlayer2Wins){ saveResults(1); }else{ saveResults(2); } } }else{ Gson g = new Gson(); String a = readFile("AI.txt"); JsonObject jobj = g.fromJson(a, JsonObject.class); JsonArray states = jobj.getAsJsonObject("data").getAsJsonArray("state"); JsonArray choices = jobj.getAsJsonObject("data").getAsJsonArray("choice"); savedStates = g.fromJson(states, new TypeToken<ArrayList<String>>(){}.getType()); savedChoices = g.fromJson(choices, new TypeToken<ArrayList<String>>(){}.getType()); player1States = savedStates; player1Choices = savedChoices; player1AccessedChoices = new ArrayList<Integer>(); player1MovesChosen = new ArrayList<Integer>(); System.out.println("Who goes first?(you = 1 / pc = 2"); int pvcfirst = s.nextInt(); while(pvcfirst!= 1 && pvcfirst != 2){ System.out.print("Not a valid choice. Choose again(you first = 1 / pc first = 2):"); pvcfirst = s.nextInt(); } pvc(pvcfirst); } } public static void evaluate(int player, int result){ if(player == 1){ if(result == 1){ for(int x = 0; x < player1AccessedChoices.size(); x++){ addBeads(player1AccessedChoices.get(x), player1MovesChosen.get(x), 8, 1); } }else if(result == 2){ for(int x = 0; x < player1AccessedChoices.size(); x++){ removeBeads(player1AccessedChoices.get(x), player1MovesChosen.get(x), 8, 1); } }else if(result == 0){ for(int x = 0; x < player1AccessedChoices.size(); x++){ removeBeads(player1AccessedChoices.get(x), player1MovesChosen.get(x), 6, 1); } }else{ throw new RuntimeException("Result given to method \"evaluate\" could not be processed."); } }else if(player == 2){ if(result == 1){ for(int x = 0; x < player2AccessedChoices.size(); x++){ addBeads(player2AccessedChoices.get(x), player2MovesChosen.get(x), 8, 2); } }else if(result == 2){ for(int x = 0; x < player2AccessedChoices.size(); x++){ removeBeads(player2AccessedChoices.get(x), player2MovesChosen.get(x), 8, 2); } }else if(result == 0){ for(int x = 0; x < player2AccessedChoices.size(); x++){ removeBeads(player2AccessedChoices.get(x), player2MovesChosen.get(x), 6, 2); } }else{ throw new RuntimeException("Result given to method \"evaluate\" could not be processed."); } }else{ throw new RuntimeException("Player_number given to method \"evaluate\" could not be processed."); } } public static void removeBeads(int index, int move, int count, int player){ if(player ==1){ int moves = player1Choices.get(index).length() - player1Choices.get(index).replace(move + "", "").length(); if (player1Choices.get(index).length() < 2 || moves == player1Choices.get(index).length()){ player1Choices.set(index, getAvailableMoves(player1States.get(index))); return; }else if(moves>=50){ for(int tmp = 0; tmp < moves/2; tmp++){ player1Choices.set(index, player1Choices.get(index).replaceFirst(move + "", "")); } return; } for(int x = 0; x < count; x++){ if (player1Choices.get(index).lastIndexOf(move + "") != player1Choices.get(index).indexOf(move + "")) player1Choices.set(index, player1Choices.get(index).replaceFirst(move + "", "")); else break; } }else{ int moves = player2Choices.get(index).length() - player2Choices.get(index).replace(move + "", "").length(); if (player2Choices.get(index).length() < 2 || moves == player2Choices.get(index).length()){ player2Choices.set(index, getAvailableMoves(player2States.get(index))); return; }else if(moves>=50){ for(int tmp = 0; tmp < moves/2; tmp++){ player2Choices.set(index, player2Choices.get(index).replaceFirst(move + "", "")); } return; } for(int x = 0; x < count; x++){ if (player2Choices.get(index).lastIndexOf(move + "") != player2Choices.get(index).indexOf(move + "")) player2Choices.set(index, player2Choices.get(index).replaceFirst(move + "", "")); else break; } } } public static void addBeads(int index, int move, int count, int player){ if(player == 1){ if (player1Choices.get(index).length() < maxMovesInChoiceString){ for(int x = 0; x < count; x++)player1Choices.set(index, player1Choices.get(index) + move); }else{ for(int x = 0; x < player1Choices.get(index).length(); x++){ if (Integer.parseInt(player1Choices.get(index).charAt(x) + "") != Integer.toString(move).charAt(0)){ char[] charArray = player1Choices.get(index).toCharArray(); charArray[x] = Integer.toString(move).charAt(0); player1Choices.set(index, new String(charArray)); return; } } } }else{ if (player2Choices.get(index).length() < maxMovesInChoiceString){ for(int x = 0; x < count; x++)player2Choices.set(index, player2Choices.get(index) + move); }else{ for(int x = 0; x < player2Choices.get(index).length(); x++){ if (Integer.parseInt(player2Choices.get(index).charAt(x) + "") != Integer.toString(move).charAt(0)){ char[] charArray = player2Choices.get(index).toCharArray(); charArray[x] = Integer.toString(move).charAt(0); player2Choices.set(index, new String(charArray)); return; } } } } } public static char checkWin(String state){ ArrayList<String> winningPositions = new ArrayList<String>(); winningPositions.add("012"); winningPositions.add("345"); winningPositions.add("678"); winningPositions.add("036"); winningPositions.add("147"); winningPositions.add("258"); winningPositions.add("048"); winningPositions.add("246"); for(int x = 0; x < winningPositions.size(); x++){ if (state.charAt(Integer.parseInt(winningPositions.get(x).charAt(0) + "")) != ' ' && state.charAt(Integer.parseInt(winningPositions.get(x).charAt(0) + "")) == state.charAt(Integer.parseInt(winningPositions.get(x).charAt(1) + "")) && state.charAt(Integer.parseInt(winningPositions.get(x).charAt(1) + "")) == state.charAt(Integer.parseInt(winningPositions.get(x).charAt(2) + ""))) { return state.charAt(Integer.parseInt(winningPositions.get(x).charAt(0) + "")); } } if (state.contains(" ") == false)return 'D'; return ' '; } public static String addMark(String state, int position, char mark){ if (position > 8 || position < 0)throw new RuntimeException("addMark position out of range: " + position); if (mark != ' ' && mark != 'X' && mark != 'O')throw new RuntimeException("addMark mark not correct"); char[] charArray = state.toCharArray(); charArray[position] = mark; return new String(charArray); } public static void showState(String state){ System.out.println(""); System.out.println(" " + state.charAt(0) + " | " + state.charAt(1) + " | " + state.charAt(2)); System.out.println("---|---|---"); System.out.println(" " + state.charAt(3) + " | " + state.charAt(4) + " | " + state.charAt(5)); System.out.println("---|---|---"); System.out.println(" " + state.charAt(6) + " | " + state.charAt(7) + " | " + state.charAt(8)); } public static int getNextMove(String currentState, int player){ if(player == 1){ if (checkWin(currentState) != ' ')throw new RuntimeException("You're asking a player to move but someone has already won or there is a draw"); int stateIndex = getIndexOfState(currentState, 1); int randomMove = -1; if (stateIndex == -1){ player1States.add(currentState); player1Choices.add(getAvailableMoves(currentState)); stateIndex = player1Choices.size()-1; } randomMove = getMoveFromChoices(stateIndex,1); player1AccessedChoices.add(stateIndex); player1MovesChosen.add(randomMove); return randomMove; }else if(player == 2){ if (checkWin(currentState) != ' ')throw new RuntimeException("You're asking a player to move but someone has already won or there is a draw"); int stateIndex = getIndexOfState(currentState, 2); int randomMove = -1; if (stateIndex == -1){ player2States.add(currentState); player2Choices.add(getAvailableMoves(currentState)); stateIndex = player2Choices.size()-1; } randomMove = getMoveFromChoices(stateIndex, 2); player2AccessedChoices.add(stateIndex); player2MovesChosen.add(randomMove); return randomMove; }else{ throw new RuntimeException("getnextmove fucked up"); } } public static int getMoveFromChoices(int stateIndex, int player){ Random r = new Random(); if(player == 1){ int randPosition = r.nextInt(player1Choices.get(stateIndex).length()); return Integer.parseInt(player1Choices.get(stateIndex).charAt(randPosition) + ""); }else{ int randPosition = r.nextInt(player2Choices.get(stateIndex).length()); return Integer.parseInt(player2Choices.get(stateIndex).charAt(randPosition) + ""); } } public static int getIndexOfState(String state, int player){ if(player == 1){ for(int x = 0; x < player1States.size(); x++){ if (state.equals(player1States.get(x))){ return x; } } return -1; }else{ for(int x = 0; x < player2States.size(); x++){ if (state.equals(player2States.get(x))){ return x; } } return -1; } } public static String getAvailableMoves(String state){ if (checkWin(state) == 'D')throw new RuntimeException("Error in getAvailableMoves, you shouldn't be looking at a state with a full board"); String cupContents = ""; char blank = ' '; int beadStartCount = 8; for(int index = 0; index < state.length(); index++){ if (state.charAt(index) == blank){ for(int x = 0; x < beadStartCount; x++)cupContents += "" + index; } } return cupContents; } public static String getBlankState(){ return " "; } public static void saveResults(int player){ Gson g = new Gson(); String state; String choice; if(player == 0){ state = "[]"; choice = "[]"; }else if(player == 1){ state = g.toJson(player1States); choice = g.toJson(player1Choices); }else if(player == 2){ state = g.toJson(player2States); choice = g.toJson(player2Choices); }else{ throw new RuntimeException("U fucked up."); } String json = "{ data: {state: "+state+", choice: "+choice+"}}"; JsonObject jobj = g.fromJson(json, JsonObject.class); try{ File f = new File("AI.txt"); f.createNewFile(); PrintWriter writer = new PrintWriter("AI.txt", "UTF-8"); writer.println(jobj); writer.close(); }catch(Exception e){System.out.println("Filemaker fucked up.");} } static String readFile(String path) throws IOException { byte[] encoded = Files.readAllBytes(Paths.get(path)); return new String(encoded, "UTF-8"); } public static boolean validMove(String state, int move){ if (state.charAt(move) == ' '){ return true; }else{ return false; } } public static int playerMove(String state){ Scanner s = new Scanner(System.in); System.out.println("Your Move: "); int move = s.nextInt(); while(!validMove(state, move)){ System.out.println("That field is already taken. Choose a different one: "); move = s.nextInt(); } return move; } public static boolean fileExists(String directory, String fileName){ String x = ""; final File folder = new File(directory); for (final File fileEntry : folder.listFiles()) { x += fileEntry.getName(); } boolean s = x.contains(fileName); return s; } public static void pvc(int first){ Scanner s = new Scanner(System.in); char playerLetter = 'O'; char pcLetter = 'X'; char winner = ' '; String currentState = getBlankState(); System.out.println("================= NEW GAME ================="); if(first == 1){ int move = playerMove(currentState); currentState = addMark(currentState, move, playerLetter); } player1AccessedChoices.clear(); player1MovesChosen.clear(); while (true){ currentState = addMark(currentState, getNextMove(currentState, 1), pcLetter); showState(currentState); winner = checkWin(currentState); if (winner != ' ') break; currentState = addMark(currentState, playerMove(currentState), playerLetter); showState(currentState); winner = checkWin(currentState); if (winner != ' ') break; } switch(winner){ case 'X' : System.out.println("You lose!"); evaluate(1,1); break; case 'O' : System.out.println("You win!"); evaluate(1,2); break; case 'D' : System.out.println("It's a draw!"); evaluate(1,0); break; } saveResults(1); } }