Code viewer for World: Tic Tac Toe with Cohere
// Cloned by Madalina Triboi on 1 Dec 2023 from World "cohere chatbot api test" by Madalina Triboi
// Please leave this clone trail here.

// Code guidance from Chat with GPT Model by Dr. Mark Humphrys
// It follows the same kind of structure as to understand how to call the API.

// AI API that is being used is Cohere.
// Similar to OpenAI's APIs but it offers free API keys; thus why I picked it.

// Tutorial followed for Tic Tac Toe grid setup in p5.js
// https://www.youtube.com/watch?v=8x_omgn3IRg&ab_channel=PattVira



// Cohere API key for authentication
var COHERE_API_KEY = "E6vTBQ2D4AWDQHrv4kA0sIXekaq3BX731TMSjila";

// Variable to track whether the game has ended
let gameEnded = false;

// Array to store chat history
var chat_hist = [];

// Variable to keep track of the current player ("X" or "O")
let currentPlayer = "X";

// Set body style using jQuery
$("body").css({
  margin: "25px",
  padding: "12px",
  "font-family": "'Poppins', sans-serif",
  "font-family": "'Roboto', sans-serif",
  "font-size": "125%",
  color: "#4A4B44",
});

// Call the initialization function when the page is first loaded
$(document).ready(function () {
    // MH edit
    AB.removeRunHeader(); 

  initializeChat();
  // Initially hide the board and other elements
  // Did this in order to send the initialChat to the API to explain game rules;
  // And to avoid the user clicking on the grid, confusing the API.
  $("#p5-container").hide();
  $("#end-game").hide();
  $("#cohere").hide();
  
});

// Function to initialize the chat with the initial prompt
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" });


    // 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 settings = {
    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(settings)
    .done(function (response) {
        
        // MH edit 
      console.log( "MH response: " );
      console.log ( response);
       console.log( "MH response.text: " );
      console.log ( response.text );
      
      // Once we have a response, show the board. The API now knows what game we are playing.
      showBoard();
      $("#start").hide();
      
      // 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 show the Tic Tac Toe board and other elements once we received an answer from the API
function showBoard() {
  $("#p5-container").show();
  $("#end-game").show();
  $("#cohere").show();
  
}

// Dynamically create HTML elements for the Tic Tac Toe game
document.write(`
<!DOCTYPE HTML>
<style>
   @import url('https://fonts.googleapis.com/css2?family=Poppins&family=Roboto&display=swap');
</style>

<div style='position:absolute; left: 25%; right:25%'>
  <br></br>

  <h2> Tic Tac Toe with Cohere AI API </h2>
  <hr style="border-top: 1px dotted #76877D">
  </hr>
  <br></br>
  <h3 id="start">Fetching API... Loading Game... </h3>
   <div id="p5-container"></div>
   <div id="end-game"></div>
   <div id=cohere> 
   <h3> Cohere Says: </h3>
    <p id="text"> </p>
    </div>
     
</div>
`);

// Function to send user's input to Cohere API
function sendToCohere(input) {
  // If the game has ended, do not make an API call
  if (gameEnded) {
    console.log("Game Ended.");
    return;
  }

  // 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" });

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

  // API DOCS: https://docs.cohere.com/reference/chat
  const settings = {
    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(settings)
  .done(function (response) {
      
        // 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
    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
        drawMove(parseInt(y), parseInt(x), "O");

        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.");
  });
}

// Set up the canvas for drawing the Tic Tac Toe grid
function setup() {
  let canvas = createCanvas(500, 500);
  canvas.parent("p5-container"); // Set parent to the div with id "p5-container"

// MH edit 
ABWorld.canvas = canvas;        // so AB screenshot can find this non-standard P5 canvas later 

  let cellSize = width / 3;

  // Draw the grid
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      stroke(0);
      noFill();
      rect(i * cellSize, j * cellSize, cellSize, cellSize);
    }
  }
  // Stop redrawing after setup
  noLoop();
  
}


// Initialize the game board with empty cells
let board = [
  ["", "", ""],
  ["", "", ""],
  ["", "", ""],
];

// Function to draw a move on the canvas
function drawMove(x, y, player) {
  // Check if the cell is empty before making a move
  if (board[x][y] === "") {
    // Update the board with the move
    board[x][y] = player;

    // Draw the move on the canvas
    let cellSize = width / 3;
    let moveX = x * cellSize + cellSize / 2;
    let moveY = y * cellSize + cellSize / 2;

    textSize(32);
    textAlign(CENTER, CENTER);
    if (player === "X") {
      text("X", moveX, moveY);
      winner(board);
    } else if (player === "O") {
      text("O", moveX, moveY);
      winner(board);
    }
  } else {
    console.error("Cell is already occupied!");
    console.log(JSON.stringify(board));
    // Provide a message indicating that the selected cell is already occupied
    const message = `Cell at ${x} ${y} is already occupied. Analyze this grid to see where you may be able to find an empty cell ${JSON.stringify(
      board,)}. Please choose a different placement.`;
    sendToCohere(message);
  }
}

// Function to handle mouse click events (user's move)
function mousePressed() {
  // Make a move when the mouse is pressed
  let cellSize = width / 3;
  let i = floor(mouseX / cellSize);
  let j = floor(mouseY / cellSize);

  // Check if the cell is empty before making a move
  if (board[i][j] === "") {
    drawMove(i, j, "X");
    // Send the user's move to the Cohere API
    console.log(`Mouse pressed at ${j} ${i}`);
    let move = `${j} ${i}`;
    sendToCohere(move);
  }
}

// Evaluate the winner of the Tic Tac Toe game
// Reference: https://stackoverflow.com/questions/69505095/how-to-detect-tic-tac-toe-winner-in-javascript-with-3-child-array
function winner(board) {
  winners = new Set();

  // Columns check
  for (let i = 0; i < 3; i++) {
    if (
      board[0][i] !== "" &&
      new Set([board[0][i], board[1][i], board[2][i]]).size === 1
    ) {
      winners.add(board[0][i]);
    }
  }

  // Rows check
  for (let i = 0; i < 3; i++) {
    if (board[i][0] !== "" && new Set(board[i]).size === 1) {
      winners.add(board[i][0]);
    }
  }

  // Diagonals check
  if (
    board[1][1] !== "" &&
    (new Set([board[0][0], board[1][1], board[2][2]]).size === 1 ||
      new Set([board[0][2], board[1][1], board[2][0]]).size === 1)
  ) {
    winners.add(board[1][1]);
  }

  if (winners.size === 2) {
    return "error";
  }

  if (winners.size === 0) {
    // TIE
    if (board.every((y) => y.every((z) => z))) {
      gameEnded = true;
      $("#end-game").html(`<h2>TIE <h2>
                            <p> Reload Page if you want to play again </p>`);
    }
    return "incomplete";
  }

  if (
    winners.values().next().value === "X" ||
    winners.values().next().value === "O"
  ) {
    // WIN
    gameEnded = true;
    $("#end-game").html(`<h2>WINNER: ${winners.values().next().value} <h2>
                           <p> Reload Page if you want to play again </p>`);
  }
}