Code viewer for World: tic tac toe 1
// Tutorial followed for Tic Tac Toe grid setup in p5.js
// https://www.youtube.com/watch?v=8x_omgn3IRg&ab_channel=PattVira

// Inspried by World "Tic Tac Toe with Cohere" by Madalina Triboi 

// AI API that is being used is Cohere and Meta Llama


// Defining board size and game setup
let cols = 3; // Number of columns
let rows = 3; // Number of rows
let size; // Size of each cell
let board = Array.from({ length: cols }, () => Array(rows).fill(0)); // Board for Cohere AI
let board2 = Array.from({ length: cols }, () => Array(rows).fill(0)); // Board for Llama AI
let players = ['X', 'O']; // Possible players
let currentPlayer; // Current player for Cohere board
let currentPlayer2; // Current player for Llama board
let gap = 50; // Gap between boards
var COHERE_API_KEY = "kWWGEmvtADgXvs7n5JBmK9m0aaK56z26h5EmClq7"; // Cohere API key
var LLAMA_API_KEY = "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"; // Llama API key
let chat_hist = []; // Chat history for Cohere
let chat_hist2 = []; // Chat history for Llama
let cohereInitialized = false; // Flag for Cohere initialization
let llamaInitialized = false; // Flag for Llama initialization
let cohereLatestResponse = ""; // Latest response from Cohere
let llamaLatestResponse = ""; // Latest response from Llama

// Setup function for initializing game and boards
function setup() {
     initializeChat();
     initializeChatLlama();
     createCanvas(900, 900);
     size = (width- gap)/cols / 2;   // Calculate size of each cell
     
     // Randomly select starting player
     currentPlayer = players[floor(random(2))];
     currentPlayer2 = players[floor(random(2))];

 }
 
 
// Draw function to render the game board and AI statuses

function draw() {

    background(220); // light gray color for background
    if (cohereInitialized) {
      textSize(24);
      fill(0, 100, 150);
      text("Cohere", size * 1.5, 100);
      drawBoard(board, 0, 0);
      textSize(16);
      text("Cohere's Move: " + cohereLatestResponse, 10, height - 50);
  }
  
  // waiting until both ais are initilised to draw the boards
  if (llamaInitialized) {
    textSize(24);
    fill(150, 50, 0);
    text("Llama", width - size * 1.5, 100);
    drawBoard(board2, (width/2) + (gap / 2), 0);
    textSize(16);
    text("Llama's Move: " + llamaLatestResponse, (width/2) + (gap / 2), height - 50);
  }  

    if (!cohereInitialized || !llamaInitialized) {
      //fill(0);
      textSize(24);
      textAlign(CENTER, CENTER);
      text("Initializing AI players...", width/2, height/2);
    }
  
}



function drawBoard(board, xOffset, yOffset){

    strokeWeight(3); // looks nicer

    for(let i=0; i<cols; i++){
        for (let j=0; j<rows; j++){
            fill(255);
            stroke(70);
            rect(i*size + xOffset, j*size + yOffset, size, size);
            if (board[i][j] !== 0){

            textAlign(CENTER, CENTER);
            textSize(size/2);
            if(board[i][j] === 'X') {
              fill(0, 100, 200);
          } else {
              fill(200, 50, 50);
          }
          
          // Add hover effect
          if (mouseX > i*size + xOffset && 
              mouseX < i*size + xOffset + size && 
              mouseY > j*size + yOffset && 
              mouseY < j*size + yOffset + size) {
              fill(100, 200, 100);
          }
          
            text(
                board[i][j], 
                xOffset + i * size +  size / 2, 
                size/2 + yOffset + j*size);
            }
        }
    }

} 
// function to handle mouse clicks to make moves on both boards
function mousePressed() {
    let boardWidth = size * 3;
    
    // For left board (Cohere)
    if (mouseX < width/2 - gap/2) {
        let x = floor(mouseX/size);
        let y = floor(mouseY/size);
        if (board[x][y] === 0) {
            placePieces(board, x, y, currentPlayer, "currentPlayer");
            let move = `${x} ${y}`;
            sendToCohere(move);
        }
    }
    // For right board (Llama)
    else if (mouseX > width/2 + gap/2) {
        let x = floor((mouseX - (width/2 + gap/2))/size);
        let y = floor(mouseY/size);
        if (x >= 0 && x < 3 && y >= 0 && y < 3 && board2[x][y] === 0) {
            placePieces(board2, x, y, currentPlayer2, "currentPlayer2");
            let move = `${x} ${y}`;
            sendToLlama(move);
        }
    }
 
}

function placePieces(b, x, y,player, playerName){
    if(x >= 0 && x < cols && y >= 0 && y < rows){
        if (b[x][y] == 0){
            b[x][y] = player;
            if (playerName === "currentPlayer"){
                currentPlayer = (currentPlayer === 'X') ? 'O' : 'X';
            }else{
                currentPlayer2 = (currentPlayer2 === 'X') ? 'O' : 'X';
            }
        }
         else{
            print("Spot not available");
        }
        }
    
}

// Function to initialize the chat with the initial prompt orignal code from Madalina Triboi 
function initializeChat() {
  // Initial prompt message explaining game rules
  const initialPrompt =
    "RESET. Welcome to Tic Tac Toe! You are 'O', and I am 'X'. The game unfolds on a 3x3 grid. Your goal is to align three 'O's either horizontally, vertically, or diagonally. If the grid fills up without a winner, it's a Tie." +
    "Respond with your move using coordinates like 'x y', where 'x' and 'y' are row and column numbers (starting from 0 in the top left). No extra explanations, please." +
    "Don't stop sending coordinates until I stop sending you them." +
    "Valid coordinates range 0 0 to 2 2. Middle of the grid is 1 1; last cell in the grid is 2 2. Let's kick off! I'll make the first move. Do you understand ?";

  // Add the initial prompt to the chat history
  chat_hist.push({ role: "USER", message: initialPrompt, user_name: "Player" });
  chat_hist2.push({ role: "USER", message: initialPrompt, user_name: "assistant" });

    // MH edit
    console.log ( "MH prompt: " + initialPrompt );
    console.log ( "MH chat hist: "  );
    console.log ( chat_hist );



  // Send the initial prompt to the Cohere API
  // API DOCS: https://docs.cohere.com/reference/chat
  const cohereSettings = {
    async: true,
    crossDomain: true,
    url: "https://api.cohere.ai/v1/chat",
    method: "POST",
    headers: {
      accept: "application/json",
      "content-type": "application/json",
      Authorization: "BEARER " + COHERE_API_KEY,
    },
    processData: false,
    data: JSON.stringify({
      message: initialPrompt,
      model: "command",
      temperature: 0.3,
      chat_history: chat_hist,
    }),
  };

  // Handle the API response
  $.ajax(cohereSettings)
    .done(function (response) {
        
        
      cohereInitialized = true;
        // MH edit 
      console.log( "MH response: " );
      console.log ( response);
      console.log( "MH response.text: " );
      console.log ( response.text );
      
      
      // Check if the message is a string before pushing it into the chat history
      // MH edit: change response.message to response.text
      if (typeof response.text === "string") {
        chat_hist.push({
          role: "CHATBOT",
          message: response.text,
          user_name: "CohereBot",
        });
        
      } else {
        console.error("Invalid message:", response.text);
      }
    })
    .fail(function (error) {
      console.error("Error:", error);
      $("#cohere").text(
        "An error occurred while fetching the response. Check your API key.",
      );
    });
    
}


// Function to send user's input to Cohere API original code from Madalina Triboi 
function sendToCohere(input) {
  // Put input into the chat history so the API has somewhat a clue what the next coordinates should be
  chat_hist.push({ role: "USER", message: input, user_name: "Player" });


  // API DOCS: https://docs.cohere.com/reference/chat
  const cohereSettings = {
    async: true,
    crossDomain: true,
    url: "https://api.cohere.ai/v1/chat",
    method: "POST",
    headers: {
      accept: "application/json",
      "content-type": "application/json",
      Authorization: "BEARER " + COHERE_API_KEY,
    },
    processData: false,
    data: JSON.stringify({
      message: input,
      model: "command",
      temperature: 0.0,
      chat_history: chat_hist,
    }),
  };

// Handle the API response
$.ajax(cohereSettings)
  .done(function (response) {
      cohereLatestResponse = response.text;
        // MH edit 
      console.log( "MH response.text: " );
      console.log ( response.text );
        

    // Check if the message is a string before pushing it into the chat history
    if (typeof response.text === "string") {
      // Display API response for the user to see if the API is playing right and not confused
      $("#text").text(`${response.text}`);
      
      // Use a regex to find the coordinates in the response text
      // Delimited by boundaries, 1 digit number separated by whitespace
      let regex = /\b\d{1} \d{1}\b/;
      let match = response.text.match(regex);

      // If a match was found, split the match into x and y coordinates
      if (match) {
        let [x, y] = match[0].split(" ");
        chat_hist.push({
          role: "CHATBOT",
          message: response.text,
          user_name: "CohereBot",
        });

        // Draw the move on the canvas
        placePieces(board, x, y, currentPlayer, "currentPlayer");


        console.log(`Move coordinates: ${x} ${y}`);
      } else {
        console.error("No coordinates found in response:", response.text);
        // Try to get an answer again by calling sendToCohere() again
        sendToCohere(input);
      }
    } else {
      console.error("Invalid message:", response.text);
    }
  })
  .fail(function (error) {
    console.error("Error:", error);
    $("#text").text("An error occurred while fetching the response.");
  });
}



async function initializeChatLlama() {
    // Define the system prompt
    const prompt =
        "RESET. Welcome to Tic Tac Toe! You are 'O', and I am 'X'. The game unfolds on a 3x3 grid. Your goal is to align three 'O's either horizontally, vertically, or diagonally. If the grid fills up without a winner, it's a Tie. " +
        "Respond with your move using coordinates like 'x y', where 'x' and 'y' are row and column numbers (starting from 0 in the top left). No extra explanations, please. " +
        "Don't stop sending coordinates until I stop sending you them. " +
        "Valid coordinates range 0 0 to 2 2. Middle of the grid is 1 1; last cell in the grid is 2 2. Let's kick off! I'll make the first move. Do you understand?";

    // Set up headers for the API request
    const headers = {
        "Authorization": `Bearer ${LLAMA_API_KEY}`,
        "Content-Type": "application/json"
    };

    // Define the API URL
    const apiUrl = "https://api-inference.huggingface.co/models/meta-llama/Llama-3.2-1B";

    // Prepare the payload for the API request
    const data = {
        inputs: prompt,
        parameters: {
            max_new_tokens: 30,
            temperature: 0.1
        }
    };

    try {
        const response = await fetch(apiUrl, {
            method: "POST",
            headers: headers,
            body: JSON.stringify(data)
        });
        
        const result = await response.json();
        
        llamaInitialized = true;

        
    } catch (error) {
        console.log("Error:", error);
    }
}
function sendToLlama(message) {
    $.ajax({
        url: "https://api-inference.huggingface.co/models/meta-llama/Llama-3.2-1B",
        type: "POST",
        headers: {
            "Authorization": `Bearer ${LLAMA_API_KEY}`,
            "Content-Type": "application/json"
        },
        
        data: JSON.stringify({
            inputs: message,
            parameters: {
                max_new_tokens: 30,
                temperature: 0.2,
                return_full_text: false
            }
        }),
        success: function(response) {
            console.log(response[0].generated_text)
            const numbers = response[0].generated_text.match(/[0-2]\s*[0-2]/);
            if (numbers) {
                const [x, y] = numbers[0].split(" ").map(Number);
                console.log(x, y)
                board2[x][y] = currentPlayer2;
                llamaLatestResponse = `${x} ${y}`;
                currentPlayer2 = currentPlayer2 === 'X' ? 'O' : 'X';
            }
        },
        error: function(error) {
            console.log("Error:", error);
        }
    });
}

// Add this function to check for a winner
function checkWinner(board) {
  // Check rows, columns and diagonals
  for (let i = 0; i < 3; i++) {
      if (board[i][0] && board[i][0] === board[i][1] && board[i][0] === board[i][2]) {
          console.log(`Winner: ${board[i][0]}`);
          return board[i][0];
      }
      if (board[0][i] && board[0][i] === board[1][i] && board[0][i] === board[2][i]) {
          console.log(`Winner: ${board[0][i]}`);
          return board[0][i];
      }
  }
  if (board[0][0] && board[0][0] === board[1][1] && board[0][0] === board[2][2]) {
      console.log(`Winner: ${board[0][0]}`);
      return board[0][0];
  }
  if (board[0][2] && board[0][2] === board[1][1] && board[0][2] === board[2][0]) {
      console.log(`Winner: ${board[0][2]}`);
      return board[0][2];
  }
  return null;
}