4
\$\begingroup\$

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);
    }   
}
\$\endgroup\$
  • 2
    \$\begingroup\$ There is a lot of code here, in order to get a better overview of your code, could you add a short description in your introductory paragraph about what each method does? That would help in understanding and reviewing your code. It will probably both give you a faster answer and a better answer. \$\endgroup\$ – Simon Forsberg Feb 8 '16 at 20:29
  • \$\begingroup\$ Do you perhaps have a github repository for this? It would be interesting to try to run your code. \$\endgroup\$ – Simon Forsberg Feb 21 '16 at 12:33
4
\$\begingroup\$
public class run{

It's more common to give classes capitalized noun names. So Runner rather run. But in this case, I'd tend to call it something like TicTacToe or TicTacToeGame. Although perhaps you are running inside some other framework that provides the name. In that case of course, you're stuck with it.

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; 

Why are these static? Half of these seem specific to a particular game. They should be fields of a game object, not class fields. Or two Player objects.

It's also customary to make the variable type the interface rather than the implementation. So List rather than ArrayList. This makes it easier to change the implementation in the future.

main

You have a lot of code in your main method--five pages worth. Try to move some of that out into other methods. Possibly into different classes.

        int ov = s.nextInt();

You use ov several times. Presumably it has some meaning to you. Will it have the same meaning to you in six months? It's not obvious to me what it represents, so it's likely not to be obvious to other readers. If you write it out rather than abbreviating, it's often easier to remember.

                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;
                    }
                }

This seems longer than necessary. Consider

                char player = (counter % 2 == 0) ? 'O' : 'X';
                while (winner != ' ') {
                    currentState = addMark(currentState, getNextMove(currentState, (player == 'X') ? 1 : 2), player);
                    winner = checkWin(currentState);
                    player = (player == 'X') ? 'O' : 'X';
                }

No more writing everything four times.

You could get it shorter by changing getNextMove to take a letter rather than a number.

What are beads?

These methods would be easier to follow if they had names that better fit what they were doing.

                    char[] charArray = player1Choices.get(index).toCharArray(); 
                    charArray[x] = Integer.toString(move).charAt(0); 
                    player1Choices.set(index, new String(charArray)); 

Why do you keep swapping to and from a String? Why not just make it a char array and only turn it into a String if you need one? Since arrays are mutable, you wouldn't even have to set the choice afterwards.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the reply. I'll try fixing stuff and making an external class for players. I don't have much time though, so maybe It'll take a while :) \$\endgroup\$ – Hans Feb 9 '16 at 13:06
1
\$\begingroup\$

Alright so it went faster than expected. I tried leaving out unnecessary stuff and ended up with 3 classes; TTT, ai, and human. The TTT class:

import com.google.gson.*;
import com.google.gson.reflect.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import java.nio.charset.Charset;

public class TTT{
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();
    if(!fileExists("./", "AI.txt")){
            deleteAIFile();
    }
    if(mode==2){
        System.out.println("Improve file?(1 = yes/ 2 = no)");
        int improve = s.nextInt();

        System.out.println("Do toggle? (p1first = 1 / p2first = 2 / no toggle, both learn = 0)");
        int doToggle = s.nextInt();

        boolean player1Learns;
        boolean player2Learns;
        if(doToggle == 2){
            player1Learns = false;
            player2Learns = true;
        }else if(doToggle == 1){
            player1Learns = true;
            player2Learns = false;
        }else{
            player1Learns = true;
            player2Learns = true;
        }

        boolean overwrite;
        if(improve==1){
            overwrite = true;
        }else{
            overwrite = false;
        }

        ai player1 = new ai(100, overwrite, player1Learns);
        ai player2 = new ai(100, overwrite, player2Learns);

        String currentState;
        char winner = ' ';
        int player1Wins = 0; int overallPlayer1Wins = 0;
        int player2Wins = 0; int overallPlayer2Wins = 0;
        int draws = 0; int overallDraws = 0;
        int counter = 0;

        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++){
                player1.clear();
                player2.clear();
                currentState = getBlankState();
                winner = checkWin(currentState);

                char player = (counter % 2 == 0) ? 'O' : 'X';
                while (winner == ' ') {
                    switch(player){
                        case 'X': currentState = addMark(currentState, player1.getNextMove(currentState), player); winner = checkWin(currentState); break;
                        case 'O': currentState = addMark(currentState, player2.getNextMove(currentState), player); winner = checkWin(currentState); break;
                    }
                    player = (player == 'X') ? 'O' : 'X';
                }
                counter++;

                if (winner == 'O'){
                    if(player1.learns) player1.evaluate(2);
                    if(player2.learns) player2.evaluate(1);
                    player2Wins++;
                }else if (winner == 'X'){
                    if(player1.learns) player1.evaluate(1);
                    if(player2.learns) player2.evaluate(2);
                    player1Wins++;
                }else if (winner == 'D'){
                    if(player1.learns) player1.evaluate(0);
                    if(player2.learns) player2.evaluate(0);
                    draws++;
                }else{
                    throw new RuntimeException("Bad response from checkWin ");
                }
            }
            System.out.println("");
            System.out.println("In this set:");
            System.out.println("Player1Wins 'X':  " + player1Wins);
            System.out.println("Player2Wins 'O':  " + player2Wins);
            System.out.println("Draws:            " + draws);

            overallPlayer1Wins+=player1Wins;
            overallPlayer2Wins+=player2Wins;
            overallDraws+=draws;
            player1Wins = 0; player2Wins = 0; draws =0;

            if(doToggle != 0){
                player1.toggle();
                player2.toggle();
            }
        }
        System.out.println(" ");
        System.out.println("Games played: "+counter);
        System.out.println("Overall player1 wins: "+overallPlayer1Wins);
        System.out.println("Overall player2 wins: "+overallPlayer2Wins);
        System.out.println("Overall draws:        "+overallDraws);

        if(overwrite){
            if(overallPlayer1Wins > overallPlayer2Wins){
                player1.saveResults();
            }else{
                player2.saveResults();
            }
        }

    }else{
        System.out.println("Who goes first?(you = 1 / pc = 2");
        int pvcfirst = s.nextInt();
        pvc(pvcfirst);
    }   
}

public static void pvc(int first){
    ai ai = new ai(100, true, true);
    human human = new human();
    Scanner s = new Scanner(System.in);

    char humanLetter = 'X';
    char aiLetter = 'O';
    char winner = ' ';
    String currentState = getBlankState();
    System.out.println("================= NEW GAME =================");

    if(first == 1){
        int move = human.getNextMove(currentState);
        currentState = addMark(currentState, move, humanLetter);
    }
    ai.clear();
    while (true){
        currentState = addMark(currentState, ai.getNextMove(currentState), aiLetter);
        showState(currentState);
        winner = checkWin(currentState);
        if (winner != ' ') break;
        currentState = addMark(currentState, human.getNextMove(currentState), humanLetter);
        showState(currentState);
        winner = checkWin(currentState);
        if (winner != ' ') break;
    }
    switch(winner){
        case 'O' : System.out.println("You lose!"); ai.evaluate(1); break;
        case 'X' : System.out.println("You win!"); ai.evaluate(2); break;
        case 'D' : System.out.println("It's a draw!"); ai.evaluate(0); break;
    }
ai.saveResults();
}

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 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 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 getBlankState(){
    return "         ";
}

public static void deleteAIFile(){
    Gson g = new Gson();
    String json = "{ data: {state: [], 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 could not write the file.");}
}

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;
}
}

The ai class :

import com.google.gson.*;
import com.google.gson.reflect.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import java.nio.charset.Charset;

public class ai{
private List<String> states;
private List<String> choices;
private List<Integer> accessedChoices;
private List<Integer> movesChosen;
private int maxMovesInChoiceString;
public boolean learns;

public ai(int maxMoves, boolean getDataFromFile, boolean learning){
    if(getDataFromFile){
        Gson g = new Gson();
        String a = readFile("AI.txt");
        JsonObject jobj = g.fromJson(a, JsonObject.class);
        JsonArray savedstates = jobj.getAsJsonObject("data").getAsJsonArray("state");
        JsonArray savedchoices = jobj.getAsJsonObject("data").getAsJsonArray("choice");
        states = g.fromJson(savedstates, new TypeToken<ArrayList<String>>(){}.getType());
        choices = g.fromJson(savedchoices, new TypeToken<ArrayList<String>>(){}.getType());
    }else{
        states = new ArrayList<String>();
        choices = new ArrayList<String>();
    }
    accessedChoices = new ArrayList<Integer>();
    movesChosen = new ArrayList<Integer>();
    maxMovesInChoiceString = maxMoves;
    learns = learning;
}

public int getNextMove(String currentState){
    int stateIndex = getIndexOfState(currentState);
    int randomMove = -1;
    if (stateIndex == -1){
        states.add(currentState);
        choices.add(getAvailableMoves(currentState));
        stateIndex = choices.size()-1;
    }
    randomMove = getMoveFromChoices(stateIndex);    
    accessedChoices.add(stateIndex);
    movesChosen.add(randomMove);
    return randomMove;
}

public String getAvailableMoves(String state){
    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 int getMoveFromChoices(int stateIndex){
    Random r = new Random();
    int randPosition = r.nextInt(choices.get(stateIndex).length());     
    return Integer.parseInt(choices.get(stateIndex).charAt(randPosition) + "");
}

public int getIndexOfState(String state){
    for(int x = 0; x < states.size(); x++){ 
        if (state.equals(states.get(x))){
            return x;
        }   
    }
    return -1;
}

public void evaluate(int result){
    if(result == 1){
        for(int x = 0; x < accessedChoices.size(); x++){
            addMoves(accessedChoices.get(x), movesChosen.get(x), 8);
        }   
    }else if(result == 2){
        for(int x = 0; x < accessedChoices.size(); x++){
            removeMoves(accessedChoices.get(x), movesChosen.get(x), 8);
        }
    }else if(result == 0){
        for(int x = 0; x < accessedChoices.size(); x++){
            removeMoves(accessedChoices.get(x), movesChosen.get(x), 6);
        }
    }else{
        throw new RuntimeException("Result given to method \"evaluate\" could not be processed.");
    }
}

public void removeMoves(int index, int move, int count){
    int moves = choices.get(index).length() - choices.get(index).replace(move + "", "").length();
    if (choices.get(index).length() < 2 || moves == choices.get(index).length()){
        choices.set(index, getAvailableMoves(states.get(index)));
        return;
    }else if(moves>=50){
        for(int tmp = 0; tmp < moves/2; tmp++){
            choices.set(index, choices.get(index).replaceFirst(move + "", ""));
        }
        return;
    }
    for(int x = 0; x < count; x++){
        if (choices.get(index).lastIndexOf(move + "") != choices.get(index).indexOf(move + ""))
            choices.set(index, choices.get(index).replaceFirst(move + "", ""));
        else break;
    }
}

public void addMoves(int index, int move, int count){
    if (choices.get(index).length() < maxMovesInChoiceString){ 
        for(int x = 0; x < count; x++)choices.set(index, choices.get(index) + move);
    }else{
        for(int x = 0; x < choices.get(index).length(); x++){ 
            if (Integer.parseInt(choices.get(index).charAt(x) + "") != Integer.toString(move).charAt(0)){ 
                char[] charArray = choices.get(index).toCharArray(); 
                charArray[x] = Integer.toString(move).charAt(0); 
                choices.set(index, new String(charArray)); 
                return;
            }
        }
    }
}

public void saveResults(){
    Gson g = new Gson();
    String state;
    String choice;
    state = g.toJson(states);
    choice = g.toJson(choices);
    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 could not write the file.");}
}

static String readFile(String path){
    try{
        byte[] encoded = Files.readAllBytes(Paths.get(path));
        return new String(encoded, "UTF-8");
    }catch(Exception e){System.out.println("File could not be read.");}
    return null;
}

public void clear(){
    accessedChoices.clear();
    movesChosen.clear();
}

public void toggle(){
    learns = !learns;
}
}

The human class:

import java.util.Scanner;

public class human{
public int getNextMove(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 validMove(String state, int move){
    if (state.charAt(move) == ' '){
        return true;
    }else{
        return false;
    }
}
}

Alright, the method names should be a bit clearer now(btw ov was short for overwrite). I'm also not really sure wether the human class was necessary or not, since it's quite small with only those 2 methods. But for the sake of clarity I did it anyways :)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.