why does the following implementation of my qlearning agent not always at least win or draw every single game: package ticTacToe;

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

/**
 * A Q-Learning agent with a Q-Table, i.e. a table of Q-Values. This table is implemented in the {@link QTable} class.
 * 
 *  The methods to implement are: 
 * (1) {@link QLearningAgent#train}
 * (2) {@link QLearningAgent#extractPolicy}
 * 
 * Your agent acts in a {@link TTTEnvironment} which provides the method {@link TTTEnvironment#executeMove} which returns an {@link Outcome} object, in other words
 * an [s,a,r,s']: source state, action taken, reward received, and the target state after the opponent has played their move. You may want/need to edit
 * {@link TTTEnvironment} - but you probably won't need to. 
 * @author ae187
 */

public class QLearningAgent extends Agent {
	
	/**
	 * The learning rate, between 0 and 1.
	 */
	double alpha=0.1;
	
	/**
	 * The number of episodes to train for
	 */
	int numEpisodes=10000;
	
	/**
	 * The discount factor (gamma)
	 */
	double discount=0.9;
	
	
	/**
	 * The epsilon in the epsilon greedy policy used during training.
	 */
	double epsilon=0.1;
	
	/**
	 * This is the Q-Table. To get an value for an (s,a) pair, i.e. a (game, move) pair.
	 * 
	 */
	
	QTable qTable=new QTable();
	
	
	/**
	 * This is the Reinforcement Learning environment that this agent will interact with when it is training.
	 * By default, the opponent is the random agent which should make your q learning agent learn the same policy 
	 * as your value iteration and policy iteration agents.
	 */
	TTTEnvironment env=new TTTEnvironment();
	
	
	/**
	 * Construct a Q-Learning agent that learns from interactions with {@code opponent}.
	 * @param opponent the opponent agent that this Q-Learning agent will interact with to learn.
	 * @param learningRate This is the rate at which the agent learns. Alpha from your lectures.
	 * @param numEpisodes The number of episodes (games) to train for
	 */
	public QLearningAgent(Agent opponent, double learningRate, int numEpisodes, double discount)
	{
		env=new TTTEnvironment(opponent);
		this.alpha=learningRate;
		this.numEpisodes=numEpisodes;
		this.discount=discount;
		initQTable();
		train();
	}
	
	/**
	 * Initialises all valid q-values -- Q(g,m) -- to 0.
	 *  
	 */
	
	protected void initQTable()
	{
		List<Game> allGames=Game.generateAllValidGames('X');//all valid games where it is X's turn, or it's terminal.
		for(Game g: allGames)
		{
			List<Move> moves=g.getPossibleMoves();
			for(Move m: moves)
			{
				this.qTable.addQValue(g, m, 0.0);
				//System.out.println("initing q value. Game:"+g);
				//System.out.println("Move:"+m);
			}
			
		}
		
	}
	
	/**
	 * Uses default parameters for the opponent (a RandomAgent) and the learning rate (0.2). Use other constructor to set these manually.
	 */
	public QLearningAgent()
	{
		this(new RandomAgent(), 0.1, 100, 0.9);
		
	}
	
	
	/**
	 *  Implement this method. It should play {@code this.numEpisodes} episodes of Tic-Tac-Toe with the TTTEnvironment, updating q-values according 
	 *  to the Q-Learning algorithm as required. The agent should play according to an epsilon-greedy policy where with the probability {@code epsilon} the
	 *  agent explores, and with probability {@code 1-epsilon}, it exploits. 
	 *  
	 *  At the end of this method you should always call the {@code extractPolicy()} method to extract the policy from the learned q-values. This is currently
	 *  done for you on the last line of the method.
	 */
	
	public void train()
	{
		// Loop over each episode
		for (int i = 0; i < this.numEpisodes; i++) {
			// Reset the current environment
			env.reset();
			
			while (!env.isTerminal()) {
	            // Get the current state from the environment
	            Game currentState = env.getCurrentGameState();
	            Outcome outcome = null;

	            // Choose an action using epsilon-greedy policy
	            List<Move> possibleMoves = env.getPossibleMoves();

	            // Choose the move with the highest Q-value during exploitation
	            Move chosenMove = null;

	            if (Math.random() > epsilon) {
	                // Exploit: Choose the move with the highest Q-value
	                chosenMove = possibleMoves.get((int) (Math.random() * possibleMoves.size()));
	                // Initialize current best Q-value to be the current move's Q-value
	                Double bestQValue = qTable.getQValue(currentState, chosenMove);

	                // For every possible move
	                for (Move m : possibleMoves) {
	                    // Set the current Q-value to be the current move's Q-value
	                    Double qValue = qTable.getQValue(currentState, m);
	                    // If the current Q-value is greater than the best Q-value
	                    if (qValue > bestQValue) {
	                        // Set the best Q-value to be the current Q-value
	                        bestQValue = qValue;
	                        // Set the chosen move to be the current move
	                        chosenMove = m;
	                    }
	                }
	            } else {
	                // Explore: Choose a random valid move
	                chosenMove = possibleMoves.get((int) (Math.random() * possibleMoves.size()));
	            }
                
                // Get the current Q-value for the chosen move
                Double currentQValue = qTable.getQValue(currentState, chosenMove);

                // Execute the chosen move and get the outcome
				try {
					outcome = env.executeMove(chosenMove);
				} catch (IllegalMoveException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
                
				double maxNextQValue = 0.0;
                List<Move> nextPossibleMoves = outcome.sPrime.getPossibleMoves();
                for (Move nextMove : nextPossibleMoves) {
                    double nextQValue = qTable.getQValue(outcome.sPrime, nextMove);
                    if (nextQValue > maxNextQValue) {
                        maxNextQValue = nextQValue;
                    }
                }
                
                double newQValue = currentQValue + alpha * (outcome.localReward + discount * maxNextQValue - currentQValue);
                qTable.addQValue(currentState, chosenMove, newQValue);
                currentQValue = newQValue;
			}
			
		}
		//--------------------------------------------------------
		//you shouldn't need to delete the following lines of code.
        this.policy=extractPolicy();
		
		if (this.policy==null)
		{
			System.out.println("Unimplemented methods! First implement the train() & extractPolicy methods");
			//System.exit(1);
		}
	}
	
	/** Implement this method. It should use the q-values in the {@code qTable} to extract a policy and return it.
	 *
	 * @return the policy currently inherent in the QTable
	 */
	public Policy extractPolicy()
	{
		Policy extractedPolicy = new Policy();
		  for (Game s : qTable.keySet()) {
		    List<Move> possibleMoves = s.getPossibleMoves();

		    if (!possibleMoves.isEmpty()) {
		      List<Move> bestMoves = new ArrayList<>();
		      double bestQValue = Double.NEGATIVE_INFINITY;

		      for (Move m : possibleMoves) {
		        double qValue = qTable.getQValue(s, m);
		        if (qValue > bestQValue) {
		          bestMoves.clear();
		          bestMoves.add(m);
		          bestQValue = qValue;
		        } else if (qValue == bestQValue) {
		          bestMoves.add(m);
		        }
		      }

		      // Choose a random move from the set of best moves
		      int randomIndex = (int) (Math.random() * bestMoves.size());
		      extractedPolicy.policy.put(s, bestMoves.get(randomIndex));
		    }
		  }
        return extractedPolicy;
	}
	
	public static void main(String a[]) throws IllegalMoveException
	{
		//Test method to play your agent against a human agent (yourself).
		QLearningAgent agent=new QLearningAgent();
		
		HumanAgent d=new HumanAgent();
		
		Game g=new Game(agent, d, d);
		g.playOut();
		
		
		

		
		
	}
	
	
	


	
}