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