Understanding Simulator

A simulator is developed to create an environment where intuitive decisions can be made. This section covers all the rules and basics of how the stimulator works.

The Intuitive AI stimulator is inspired by Wumpus World which was a simulated environment from the early days of Artificial Intelligence. We wanted to generate an environment where it doesn't matter who is playing (Human or AI) both are forced to make some decisions based on intuition only. So, we changed some rules in the Wumpus Wold stimulator to fulfill our requirements.

Intuitive AI stimulator is a 4x4 grid (16 blocks). Every game has 4 pits (🕳️ ), 1 gold (💰 ), and 1 monster (👹 ). Every time a new game is started the grid is randomized and one box is revealed with a hint.

  • Your goal is to find 👹 (Monster), doing so will give you 1 point and the game will end in a win.

  • If you find 💰 (Gold) before finding the monster you'll get 1 more point, hence in a game you can get a maximum of 2 points. Finding 💰 (Gold) will not end the game, think of gold as an optional encouraged behavior.

  • If you fall in 🕳️ (Pit) your score will -1 from the total score you have at the moment and the game will end in loss.

  • If a box doesn't reveal any of the three i.e. ( 👹 , 💰 , 🕳️ ) above then the box will reveal a hint that will tell you what is in the adjacent boxes.

Hints are of the following types:

  • Breeze 💨 Indicate that there is Pit(s) in the adjacent boxes.

  • Smell 👃 Indicate that there is a Monster in one of the adjacent boxes.

  • Glitter ✨ Indicate that there is Gold in one of the adjacent boxes.

  • Breeze & Smell 💨 👃 Indicate that there is Monster & Pit(s) in the adjacent boxes.

  • Breeze & Glitter 💨 ✨ Indicate that there is Gold & Pit(s) in the adjacent boxes.

  • Smell & Glitter 👃 ✨ Indicate that there is Monster & Gold in the adjacent boxes.

  • Breeze & Glitter & Smell 💨 ✨ 👃 Indicate that there is Monster, Pit(s) & Gold in the adjacent boxes.

  • Nothing ❎ This indicates that there is nothing in the adjacent boxes.

Adjacent boxes are only up, down, left, right, and not diagonal. For example, the adjacent boxes to the hint below are marked with a red dot on the grid in the picture below:

You can open any box at any given time there is no pattern to follow just some rules in place and hints to guide you through. You can follow your intuition to open any random box and ignore following the hint too, for example, the above hint which is wind 💨 is telling us that there are one or more pits in the adjacent boxes. It can be in all four or it can be in one and so one. So will you take the risk to open one of the adjacent boxes or choose to open any random box that your intuition tells you to open? All these choices are up to you!

Here are all the possible outcomes of a game:

  1. The User falls into the pit. (Game Lost)

  2. The User finds gold and falls into the pit. (Game Lost)

  3. The User finds wumpus. (Game Won)

  4. The User finds gold and wumpus. (Game Won)

Basic Logic

The IntuitiveAI simulator for humans to play is built on JavaScript and can be accessed: https://intuitive-ai.firebaseapp.com/

Every game has an array indexed from 0...15, which is randomized on a 4x4 grid that has 4 pits, 1 gold, and 1 monster.

Example:

Consider this EXAMPLE of a new game. The array is randomized and the game array now is:

strArr = ["Pit", "Glitter & Breeze", "Gold", "Glitter", "Breeze", "Pit", "Monster", "Smell Breeze", "Breeze", "Breeze", "Smell Breeze", "Pit", "Pit", "Breeze", "X", "Breeze"]

User will see the strings (symbols) as the strArr shows but behind the scenes the above array is:

indexArr = [0, 12, 7, 6, 10, 9, 11, 14, 2, 3, 1, 5, 15, 4, 13, 8]

indexArr was a sequence from 0 to 15 values and is then randomized to generate this particular game pattern and wherever in indexArr there is:

  • 0 and 9 and 5 and 15 there will always be a Pit

  • 7 there will always be Gold

  • 11 there will always be the Monster

The adjacent values are then calculated for each of the above elements and hence the hints are generated and finally a random index other than the one assigned to the elements is picked and is revealed.

Source Code

The source code is available in JavaScript and Python. Here we'll discuss the logic behind using the JavaScript version. A link to python will be available at the end.

We start by writing a new game function:

function newGame() {
//Code comig soon
}

Inside the newGame function we start by initializing some variables:

var numbers = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var numberStrs = ['❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎', '❎'];
var tempArray = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var didFMonster = false;
var didFGold = false;
var didFInPit = false;
var userGamePlayPattern = [];
var userGamePlayPatternIndex = [];
finalScore = 0;

Our main array numbers is a size 16 array which has values from 0 to 15 in sequence. Then we have numberStrs and tempArray which will be used to work side by side one will provide a GUI based approach the other will hold numeric data against each GUI element (i.e. emoji).

You'll understand more about what data we're storing for analysis purposes in the sub-page Understanding DataSet.

The next three i.e. didFMonster, didFGold, and didFInPit are all checking the outcome of the game that either user has won or lost the game and if the user has found gold in a single game.

Finally, userGamePlayPattern stores the GUI values of how use has played the game and userGamePlayPatternIndex stores from which index user moved to which index basically maps out how user played the game. In the end, we have finalScore which will store the score of a game.

Next, we need to write a method that'll shuffle arrays.

function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}

The above function is written outside the newGame function and it is plain and simple shuffling each element to a random position within the grid.

Now we go back to our newGame function and we overwrite numbers array

numbers = shuffle(numbers);

Next, Using the updated grid we assign fixed indexes to their correspondent values:

var indexOfMonster = numbers.indexOf(11);
var indexOfGold = numbers.indexOf(7);
var indexOfPit1 = numbers.indexOf(0);
var indexOfPit2 = numbers.indexOf(5);
var indexOfPit3 = numbers.indexOf(9);
var indexOfPit4 = numbers.indexOf(15);

The above code will assign the index of wherever the value 11 is shuffled to indexOfMonster and just like that, we have reserved numbers that will always be the same, meaning doesn't matter where 11 is shuffled to in the list, where ever it'll be the game will place monster there and the same goes for gold and our four pits.

Now we need to find adjacent of each of these values so that we can assign hints accordingly. This is an important step as we need to calculate these values carefully so that we can place the right hint at the right place. Here is the code:

//Findinng Adjacents
var adjToMonster = []
if (indexOfMonster+4 < 16) { //number below
adjToMonster.push(numbers[indexOfMonster+4]);
}
if (indexOfMonster+1 < 16 && indexOfMonster%4 != 3) { //number right
adjToMonster.push(numbers[indexOfMonster+1]);
}
if (indexOfMonster-4 >= 0) { //number Above
adjToMonster.push(numbers[indexOfMonster-4]);
}
if (indexOfMonster-1 >= 0 && indexOfMonster%4 != 0) { //number left
adjToMonster.push(numbers[indexOfMonster-1]);
}
var adjToGold = []
if (indexOfGold+4 < 16) { //number below
adjToGold.push(numbers[indexOfGold+4]);
}
if (indexOfGold+1 < 16 && indexOfGold%4 != 3) { //number right
adjToGold.push(numbers[indexOfGold+1]);
}
if (indexOfGold-4 >= 0) { //number Above
adjToGold.push(numbers[indexOfGold-4]);
}
if (indexOfGold-1 >= 0 && indexOfGold%4 != 0) { //number left
adjToGold.push(numbers[indexOfGold-1]);
}
var adjToPit1 = []
if (indexOfPit1+4 < 16) { //number below
adjToPit1.push(numbers[indexOfPit1+4]);
}
if (indexOfPit1+1 < 16 && indexOfPit1%4 != 3) { //number right
adjToPit1.push(numbers[indexOfPit1+1]);
}
if (indexOfPit1-4 >= 0) { //number Above
adjToPit1.push(numbers[indexOfPit1-4]);
}
if (indexOfPit1-1 >= 0 && indexOfPit1%4 != 0) { //number left
adjToPit1.push(numbers[indexOfPit1-1]);
}
var adjToPit2 = []
if (indexOfPit2+4 < 16) { //number below
adjToPit2.push(numbers[indexOfPit2+4]);
}
if (indexOfPit2+1 < 16 && indexOfPit2%4 != 3) { //number right
adjToPit2.push(numbers[indexOfPit2+1]);
}
if (indexOfPit2-4 >= 0) { //number Above
adjToPit2.push(numbers[indexOfPit2-4]);
}
if (indexOfPit2-1 >= 0 && indexOfPit2%4 != 0) { //number left
adjToPit2.push(numbers[indexOfPit2-1]);
}
var adjToPit3 = []
if (indexOfPit3+4 < 16) { //number below
adjToPit3.push(numbers[indexOfPit3+4]);
}
if (indexOfPit3+1 < 16 && indexOfPit3%4 != 3) { //number right
adjToPit3.push(numbers[indexOfPit3+1]);
}
if (indexOfPit3-4 >= 0) { //number Above
adjToPit3.push(numbers[indexOfPit3-4]);
}
if (indexOfPit3-1 >= 0 && indexOfPit3%4 != 0) { //number left
adjToPit3.push(numbers[indexOfPit3-1]);
}
var adjToPit4 = []
if (indexOfPit4+4 < 16) { //number below
adjToPit4.push(numbers[indexOfPit4+4]);
}
if (indexOfPit4+1 < 16 && indexOfPit4%4 != 3) { //number right
adjToPit4.push(numbers[indexOfPit4+1]);
}
if (indexOfPit4-4 >= 0) { //number Above
adjToPit4.push(numbers[indexOfPit4-4]);
}
if (indexOfPit4-1 >= 0 && indexOfPit4%4 != 0) { //number left
adjToPit4.push(numbers[indexOfPit4-1]);
}

The above code is calculating adjacents for the monster, gold, and our 4 pits.

Let's say that pit 4 is at the index 12 in the grid and the grid starts from 0 and Left-Top this makes the 12th index first block of the 4th row as shown in the image below.

The adjacent boxes to 12 are 8 (Above) and 13 (Right). So in the code, I've added the comment for each if-condition that explains the code so when the code will reach if (indexOfPit4+4 < 16) { //number below it'll add 12+4 which is equal to 16 and the condition will only run if 16 is less than 16 which is false so the number below will have nothing which is fine as with this example this is the expected outcome. The next if-condition is if (indexOfPit4+1 < 16 && indexOfPit4%4 != 3) { //number right will do 12+1 less than 16 AND 12%4 is not equal to 3. Both these conditions are true as 13 is less than 16 and 0 is not equal to 3 so index 13 will be added to the adjToPit4. Similarly, we check for left and above.

Now that we know the adjacents we add some weight to each adjacent cell. Let's look at the code first:

for(var i = 0; i < adjToMonster.length; i++) {
let temp = numbers.indexOf(adjToMonster[i])
addValueToArray(temp, 10);
}
for(var i = 0; i < adjToGold.length; i++) {
let temp = numbers.indexOf(adjToGold[i])
addValueToArray(temp, 5);
}
for(var i = 0; i < adjToPit1.length; i++) {
let temp = numbers.indexOf(adjToPit1[i])
addValueToArray(temp, 2);
}
for(var i = 0; i < adjToPit2.length; i++) {
let temp = numbers.indexOf(adjToPit2[i])
addValueToArray(temp, 2);
}
for(var i = 0; i < adjToPit3.length; i++) {
let temp = numbers.indexOf(adjToPit3[i])
addValueToArray(temp, 2);
}
for(var i = 0; i < adjToPit4.length; i++) {
let temp = numbers.indexOf(adjToPit4[i])
addValueToArray(temp, 2);
}

Let's again take pit 4 to understand the code. We call a function that we haven't created yet, named addValueToArray let's add that first:

function addValueToArray(index, value) {
tempArray[index] = tempArray[index] + value;
}

So now in the adjToPit4 array we have index 8 and 13. So the for-loop will run two times. As at the start, we randomized the numbers array so we find at which index of the numbers array 8 and 13 have shuffled to. We store it in a temp variable and then addValueToArray adds it at the tempArray exactly at the same index as numbers array. As tempArray was initialized with 0 so we add 2 in the case of pits and 5 in the case of gold and 10 in the case of monster. These values will help us determine which hint will show at which index in the grid. Next, we'll do that.

for(var i = 0; i<tempArray.length; i++) {
switch(tempArray[i]) {
case 10:
numberStrs[i] = "👃";
break;
case 5:
numberStrs[i] = "✨";
break;
case 2:
case 4:
case 6:
case 8:
numberStrs[i] = "💨";
break;
case 23:
case 21:
case 19:
case 17:
numberStrs[i] = "💨✨👃";
break;
case 15:
numberStrs[i] = "👃✨";
break;
case 7:
case 9:
case 11:
case 13:
numberStrs[i] = "✨💨";
break;
case 18:
case 16:
case 14:
case 12:
numberStrs[i] = "👃💨";
break;
case 0:
numberStrs[i] = "❎";
break;
default:
console.log("Error# 874B3: Temp Value array value is not found. Value = " + tempArray[i]);
} //EO Switch
}//EO For-loop

Once the tempArray is loaded with the values we made we run a for-loop on it. The case numbers are very easy to determine, as we know the weight for each pit is 2, gold is 5 and monster carries a weight of 10. So let's say a box has all three a pit, gold ad monster its value will be 2+5+10 = 17 and in the code above you can see case 17 is adding 💨✨👃 to the array. Let's say there were two pits, gold and monster around a box its value will be 2+2+5+10 = 19, and as you can see how the case is handled.

As we're replacing numbers with emojis so we still need to replace values for our monster, gold, and pits. So after the for-loop ends we add:

numberStrs[indexOfMonster] = "👹";
numberStrs[indexOfGold] = "💰";
numberStrs[indexOfPit1] = "🕳️";
numberStrs[indexOfPit2] = "🕳️";
numberStrs[indexOfPit3] = "🕳️";
numberStrs[indexOfPit4] = "🕳️";

Until this point, the code will create the grid exactly required by the simulator. Now to create a game play we need to open one box randomly, but it has to be a hint box and not the gold, monster, or a pit. So, we add this code:

//Radom opening of one box when game starts
var tempRandNum;
do {
tempRandNum = Math.floor(Math.random() * 16);
} while (tempRandNum == indexOfMonster || tempRandNum == indexOfGold || tempRandNum == indexOfPit1 || tempRandNum == indexOfPit2 || tempRandNum == indexOfPit3 || tempRandNum == indexOfPit4);
console.log(numberStrs[tempRandNum]);
userGamePlayPattern.push(numberStrs[tempRandNum]);
userGamePlayPatternIndex.push(tempRandNum);

We generate a random number from 0-15 and keep doing that until we find a value that is not equal to the index of our monster, gold, and, pits. In this code, I've just printed that value to the console, but ow we have the index that'll be opened when the game start.

Using this exact logic, with HTML and buttons press code we've developed a simulator running here: https://intuitive-ai.firebaseapp.com/.

Expanding The Grid

In this section, we'll set some ground rules of if this 4x4 is to be expanded then how do we go about it.

A 4x4 grid can make up to 16! = 20,922,789,888,000 (twenty trillion, nine hundred twenty-two billion, seven hundred eighty-nine million, eight hundred eighty-eight thousand) unique games.

When expanding the 4x4 grid it always has to be both the row and column. For example, expanding 4x4 gid to 5x5 is valid but 4x5 or 5x4 is invalid.

If you have added one row and one column then there should be one additional pit. For example, if the grid is now 5x5 there must be 5 pits in it whereas the gold ad monster remains the same.

The index you'll give to the new pit can be an odd number from any of the 5th-row or 5th column.

The above three rules are all there is if you want to expand the grid.