Hi Everyone!
In this post, I will show you how to make an unbeatable emoji tic-tac-toe that makes fun of you if you make a move or lose. It is made with HTML, CSS, and Javascript. You cannot win this game. The most you can get is a draw. Follow me if you want more projects.
Let's start with the HTML Design:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css>
<title>Unbeatable Tic Tac Toe | Abhinav Gupta</title>
</head>
<body>
<section id="start-select">
<div>
<div>
<h3>Go First or Second?</h3>
<button id="1st" class="active" onclick="pickTurn(true);" style='font-size: 14px;'>1st</button><button id="2nd" onclick="pickTurn(false);" style='font-size: 14px;'>2nd</button>
<h3>Choose Your Character</h3>
<span id="charSymbols"></span>
<button id ="start-btn" onclick="startGame();" style='font-size: 18px;'>Start Game</button>
</div>
</div>
</section>
<header id="header" style="opacity:0.6;">
<div id="emoji-outer-div">
<div id="emoji">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/win02.png" id="emoji-img">
</div>
<div id="emoji-text">
<span id="aiTalk">"Hi, I'm Unbeatable-AI<br>Wanna Play Against Me?"</span>
</div>
</div>
</header>
<nav id="menu-nav">
<div>
<div>
<h3>Next Round Go First or Second?</h3>
<button id="1st-next" class="active" onclick="pickTurn(true);">1st</button><button id="2nd-next" onclick="pickTurn(false);">2nd</button>
<h3>Change Your Character</h3>
<span id="menu-chars"></span>
<button id ="menu-close" onclick="openMenu(false);" style="">Close</button>
</div>
</div>
</nav>
<section id='main-section' style="opacity:0.6;">
<section id="side-section">
<a id="menu-open" href="javascript:void(0);" onclick="openMenu(true);"><div>☰</div></a>
<div id="score">
<div><span>Score</span></div>
<div><table>
<tr>
<th>You</th>
<th style="width: 12px;"></th>
<th>Ai</th>
</tr>
<tr>
<td>0</td>
<td> </td>
<td id="score-ai">0</td>
</tr>
</table></div>
<div style="height: 10px; width:0;"></div>
<div><table>
<tr>
<th>Tie</th>
</tr>
<tr>
<td id="score-tie">0</td>
</tr>
</table></div>
</div>
</section>
<div id="outer-grid">
<section id="grid">
<div id="pos0"><a href="javascript:void(0);" onclick='playerMove(0);' class="pos"></a></div>
<div id="pos1"><a href="javascript:void(0);" onclick='playerMove(1);' class="pos"></a></div>
<div id="pos2"><a href="javascript:void(0);" onclick='playerMove(2);' class="pos"></a></div>
<div id="pos3"><a href="javascript:void(0);" onclick='playerMove(3);' class="pos"></a></div>
<div id="pos4"><a href="javascript:void(0);" onclick='playerMove(4);' class="pos"></a></div>
<div id="pos5"><a href="javascript:void(0);" onclick='playerMove(5);' class="pos"></a></div>
<div id="pos6"><a href="javascript:void(0);" onclick='playerMove(6);' class="pos"></a></div>
<div id="pos7"><a href="javascript:void(0);" onclick='playerMove(7);' class="pos"></a></div>
<div id="pos8"><a href="javascript:void(0);" onclick='playerMove(8);' class="pos"></a></div>
</section>
</div>
</section>
<script href="script.js"></script>
</body>
</html>
Next, let's make the CSS for the nice neat look:
* {
box-sizing: border-box;
font-family: 'Cabin', sans-serif;
}
a {
text-decoration: none;
color: inherit;
}
button:focus {outline:0;}
body {
margin: 20px auto;
padding: 5px;
max-width: 510px;
background-color: #4ECCB0;
}
#start-select {
position: absolute;
width: 100vw;
height: 100vh;
z-index: 100;
left: 50%;
top: 40%;
transform: translate(-50%,-50%);
}
#start-select div {
position: fixed;
display: flex;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
width: 350px;
height: 300px;
border-radius: 35px;
background-color: #5A8BCE;
box-shadow: 3px 3px 6px #0B3A97;
}
#start-select div div {
display: flex;
justify-content: center;
flex-wrap: wrap;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
#start-select div div h3 {
margin: auto;
text-align: center;
width: 100%;
}
#start-select div div span {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
#start-select div div button {
background-color: #4ECCB0;
border-radius: 8px;
margin: 3px;
padding: 15px;
height: 50px;
min-width: 55px;
border: none;
-webkit-transition: background-color 500ms; /* Safari */
transition: background-color 500ms;
}
#start-select div div button:hover {
background-color: #77E0C9;
box-shadow: 0.5px 0.5px 5px black;
cursor: pointer; cursor: hand;
-webkit-transition: background-color 300ms; /* Safari */
transition: background-color 300ms;
}
#start-select div div button.active {
border: 2px solid;
background-color: #2EB798;
padding: 13px;
-webkit-transition: background-color 500ms; /* Safari */
transition: background-color 500ms;
}
#start-select div div button.charBtn {
min-width: 40px;
height: 40px;
width: 40px;
text-align: center;
padding: 6px;
}
header {
display: flex;
height: 110px;
margin: 0 15px;
opacity: 1;
-webkit-transition: opacity 750ms; /* Safari */
transition: opacity 750ms;
}
#emoji-outer-div {
flex: 1;
display: flex;
align-items: center;
}
#emoji {
width: 100px;
height: 100px;
}
#emoji img {
height: 100%;
}
#emoji-text {
flex: 1;
text-align: center;
margin: 0 30px;
font-size: 20px;
}
#aiTalk {
font-family: 'Poiret One', Segoe, sans-serif;
font-weight: bold;
}
nav {
display: none;
position: absolute;
width: 100vw;
height: 100vh;
z-index: 99;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
nav div {
position: fixed;
display: flex;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
width: 310px;
height: 350px;
border-radius: 35px;
background-color: #5A8BCE;
box-shadow: 3px 3px 6px #0B3A97;
}
nav div div {
display: flex;
justify-content: center;
flex-wrap: wrap;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
nav div div h3 {
margin: auto;
text-align: center;
width: 100%;
}
nav div div span {
width: 270px;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
nav div div button {
background-color: #4ECCB0;
border-radius: 8px;
margin: 3px;
padding: 15px;
height: 50px;
min-width: 55px;
border: none;
-webkit-transition: background-color 750ms; /* Safari */
transition: background-color 750ms;
}
nav div div button:hover {
background-color: #77E0C9;
box-shadow: 0.5px 0.5px 5px black;
cursor: pointer; cursor: hand;
-webkit-transition: background-color 300ms; /* Safari */
transition: background-color 300ms;
}
nav div div button.active {
border: 2px solid;
background-color: #2EB798;
padding: 13px;
-webkit-transition: background-color 500ms; /* Safari */
transition: background-color 500ms;
}
nav div div button.charBtn {
min-width: 40px;
height: 40px;
width: 40px;
text-align: center;
padding: 6px;
}
#main-section {
display: flex;
flex-wrap: wrap;
margin: auto;
opacity: 1;
-webkit-transition: opacity 500ms; /* Safari */
transition: opacity 500ms;
}
#side-section {
margin: 15px;
}
#menu-open div {
margin: auto;
text-align: center;
width: 60px;
border-radius: 10px;
font-size: 35px;
border: solid #009271;
margin: 25px;
padding: 2.5px;
background-color: #1BA485;
}
#menu-open div:hover {
background-color: #28C09E;
border: solid #009271;
}
#score {
margin: auto;
}
#score table, #score div {
font-size: 20px;
text-align: center;
margin: auto;
display: flex;
}
tr, th, td {
padding: 1px;
}
#score div span {
font-size: 25px;
margin: 10px auto;
}
@media screen and (max-width: 530px) {
body {
margin: 10px auto;
padding: 5px;
}
header {
margin: auto;
max-width: 400px;
}
#emoji-text {
margin: 0 10px;
font-size: 20px;
}
#emoji {
height: 65px;
width: 65px;
}
#start-select div {
width: 300px;
height: 380px;
padding: 13px;
}
#score-2 {
position: relative;
right: 15px;
}
#menu-open div {
margin: 0 5px;
}
#score {
margin: auto;
display: flex;
flex: 1;
}
#side-section {
margin: auto;
width: 360px;
display: flex;
}
}
#outer-grid {
height: 300px;
width: 300px;
margin: 8px auto;
}
@media screen and (min-width: 380px) {
#outer-grid {
width: 360px;
height: 360px;
margin: 15px auto;
}
}
#grid {
height: 100%;
width: 100%;
display: flex;
flex-wrap: wrap;
}
#grid div {
width: 33.3%;
height: 33.3%;
padding: 2.5px;
display: flex;
}
#grid div div, #grid div a {
background-color: #134DBF;
text-shadow: 1px 1px grey;
font-size: 45px;
width: 100%;
height: 100%;
text-align: center;
border-radius: 15px;
display: flex;
box-shadow: 2px 2px 4px #008B81;
}
#grid div div.taken {
background-color: #0B3A96;
box-shadow: 4px 4px 5px #008B81;
}
#grid div div.win {
background-color: #E26200;
box-shadow: 4px 4px 5px #008B81;
}
#grid div div span {
margin: auto;
}
.pos-span {
opacity: 0;
display: flex;
width: 100%;
height: 100%;
-webkit-transition: opacity 250ms; /* Safari */
transition: opacity 250ms;
}
.pos-span span {
margin: auto;
}
.pos-span:hover {
opacity: 0.4;
-webkit-transition: opacity 550ms; /* Safari */
transition: opacity 550ms;
}
#grid div div:hover {
background-color: #0B3A96;
box-shadow: 4px 4px 5px #008B81;
-webkit-transition: background-color 300ms; /* Safari */
transition: background-color 300ms;
}
Lastly, let's write the Javascript to actually make it "Unbeatable":
var aiTalksWin = [[["win4"],"Sorry, humans made me to powerful!"], [["win01"],"Honestly, I thought you could win, but I guess I was wrong."], [["win02"],"<del>Win Tic-Tac-Toe?</del> <br>2. First, win against ME!"], [["win3"],"What Did You Expect? You Are Only a Human..."], [["win01"],"Unbeatable Is In My Name, I can't do it is in yours."], [["win3"],"The score counter is pointless."], [["win4"],"Let You Win? I'm Afraid I Can't Do That."], [["win02"],""]];
var aiTalksMove = [[["move00"],"..."], [["move00"],"Hmmm..."], [["move05"],"Go AI !!"], [["move08"],"Sadness is Victory"],[["move08"], "Your Defeat Is a WIN for me..."], [["move03"],"Nice Try (not)"], [["move03"],"Knock Knock. Who's there? R O B O T"], [["move4"],"There are 255,168 Possible Board Combinations, Yet You Picked That One?"], [["win4"],"Infinity x Infinity Wins for me, not you!"], [["draw02"],"When Was The Last Time You Rebooted Your Device?"], [["draw04"],"I feel strange..."], [["move01"],"A Wise Computer Once Told Me That The Meaning Of Life Is 42"], [["draw01"],"Whoops, wrong move."], [["win02"],"The end is upon! "], [["move06"], "Can't Touch This!"], [["move07"], "Your Last Move Goes In The loosing Category"]];
var aiTalksTie = [[["draw01"],"..."], [["draw02"],"..."], [["draw03"],"..."], [["draw04"],"..."]];
// </> Ai Talking
function randomEmoji(chance, arr) {
var randTest = Math.random() < chance;
if (randTest) {
var rand = Math.floor(Math.random()*arr.length);
console.log(rand);
document.getElementById("emoji-img")
.src = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/"+arr[rand][0][0]+".png";
document.getElementById("aiTalk")
.innerHTML = '"'+arr[rand][1]+'"';
}
}
var winCond = [[0,1,2],[3,4,5],[6,7,8],
[0,3,6],[1,4,7],[2,5,8],
[0,4,8],[2,4,6]];
var gameMain = ["0", "0", "0",
"0", "0", "0",
"0", "0", "0"];
var chars = ["01","02","03","04","05","06","07","08","09","10","11","12","13"];
function charsBtnGen() {
for (var i = 0; i < chars.length; i++) {
document.getElementById("charSymbols").innerHTML += '<button id="char'+i+'" class="charBtn" onclick="chrChoose('+i+');"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon'+chars[i]+'.png" style="width: 25px"></button>';
document.getElementById("menu-chars").innerHTML += '<button id="char-chng'+i+'" class="charBtn" onclick="chrChange('+i+');"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon'+chars[i]+'.png" style="width: 25px"></button>';
}
}
function openMenu(open) {
if (open) {
document.getElementById('menu-nav').style.display = 'flex';
document.getElementById('header').style.opacity = '0.6';
document.getElementById('main-section').style.opacity = '0.6';
} else {
document.getElementById('menu-nav').style.display = 'none';
document.getElementById('header').style.opacity = '';
document.getElementById('main-section').style.opacity = '';
}
}
var aiChar = 'O';
var plChar = 'X';
var aiScore = 0;
var tieScore = 0;
var gameStarted = false;
// --- \/ \/ \/ Before Game Start \/ \/ \/ ---
// </> Player 1st or 2nd
plFirst = true;
function pickTurn(first) {
if (first) {
document.getElementById("1st").className = "active";
document.getElementById("2nd").className = "";
document.getElementById("1st-next").className = "active";
document.getElementById("2nd-next").className = "";
}
if (!first) {
document.getElementById("2nd").className = "active";
document.getElementById("1st").className = "";
document.getElementById("2nd-next").className = "active";
document.getElementById("1st-next").className = "";
}
plFirst = first;
}
// </> Character Chooser
function chrChoose(x) {
for (var i = 0; i < chars.length; i++) {
document.getElementById("char"+i).className = "charBtn";
}
document.getElementById("char"+x).className += " active";
plChar = chars[x];
}
// </> Character Change
function chrChange(x) {
for (var i = 0; i < chars.length; i++) {
document.getElementById("char-chng"+i).className = "charBtn";
}
document.getElementById("char-chng"+x).className += " active";
if (aiChar === chars[x]) {
var y = -1;
while (y === x || y === -1) {y = Math.floor(Math.random()*chars.length);}
for (var j = 0; j < 9; j++) {
if (gameMain[j] === aiChar) {
gameMain[j] = chars[y];
document.getElementById("div"+j)
.innerHTML = "<span style='display: flex;'><img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon"+chars[y]+".png' style='width: 50px; margin: auto;'></span>";
}
}
aiChar = chars[y];
}
// "<span style='display: flex;'><img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon"+chars[x]+".png' style='width: 50px; margin: auto;'></span>"
for (var i = 0; i < 9; i++) {
if (gameMain[i] === plChar) {
gameMain[i] = chars[x];
document.getElementById("div"+i)
.innerHTML = "<span style='display: flex;'><img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon"+chars[x]+".png' style='width: 50px; margin: auto;'></span>";
} else if (gameMain[i] === "0") {
document.getElementById("transpChars"+i)
.innerHTML = "<span style='display: flex;'><img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon"+chars[x]+".png' style='width: 50px; margin: auto;'></span>";
}
}
plChar = chars[x];
}
// </> Random Ai Char
function randChar() {
var rand = Math.floor(Math.random()*chars.length);
aiChar = chars[rand];
if (aiChar === plChar) {return randChar();}
return;
}
// </> Start Game
var round = 0;
function startGame() {
gameStarted = true;
plMoveDisable = false;
document.getElementById('start-select').style.display = 'none';
document.getElementById('header').style.opacity = '';
document.getElementById('main-section').style.opacity = '';
if (round === 0) {
document.getElementById("aiTalk").innerHTML = '"Have Fun"';
document.getElementById("emoji-img").src = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/win3.png";
}
round++;
!function () {
var randPl = Math.floor(Math.random()*chars.length);
if (plChar === "X") {plChar = chars[randPl];}
}();
randChar();
var pos = document.getElementsByClassName("pos");
for (var i = 0; i < 9; i++) {
pos[i].innerHTML = '<div><span class="pos-span"><span id="transpChars'+i+'"><span style="display: flex;"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon'+plChar+'.png" style="width: 50px; margin: auto;"></span></span></span></div>';
}
if (!plFirst) {
aiTurn();
}
}
// --- /\ /\ /\ Before Game Start /\ /\ /\ ---
// --- \/ \/ \/ After Game Start \/ \/ \/ ---
// </> Checks for Victory
function checkVictory(who) {
var inx = [], i;
for (i = 0; i < 9; i++) {
if (gameMain[i] === who) {
inx.push(i); }
}
for (var j = 0; j < 8; j++) {
var win = winCond[j];
if (inx.indexOf(win[0]) !== -1 &&
inx.indexOf(win[1]) !== -1 &&
inx.indexOf(win[2]) !== -1) {
randomEmoji(1, aiTalksWin);
for (let k = 0; k < 3; k++) {
setTimeout(function() {
document.getElementById("div"+win[k]).className = "win";
},350*(k+1));
}
gameStarted = false;
aiScore++;
document.getElementById("score-ai").innerHTML = aiScore;
setTimeout(function() {restart("tie");},2000);
return true;
}
}
if (gameMain.indexOf("0") === -1) {
gameStarted = false;
randomEmoji(1, aiTalksTie);
setTimeout(function() {
for (let k = 0; k < 9; k++) {
setTimeout(function() {
document.getElementById("div"+[k]).innerHTML = "";
},125*(k+1));
}
},500);
setTimeout(function() {restart("tie");},2100);
tieScore++;
document.getElementById("score-tie").innerHTML = tieScore;
return true;
} else if (who === aiChar && gameMain.indexOf(plChar) !== -1) {randomEmoji(0.3, aiTalksMove);}
return false;
}
// </> Restart Game
function restart(x) {
for (var i = 0; i < 9; i++) {
document.getElementById("pos"+i).innerHTML = '<a href="javascript:void('+i+');" onclick="playerMove('+i+');" class="pos"></a>';
}
gameMain = ["0", "0", "0",
"0", "0", "0",
"0", "0", "0"];
startGame();
disableRestart = false;
}
// </> Write a Move
function writeOnGame(pos, char) {
gameMain[pos] = char;
document.getElementById("pos"+pos)
.innerHTML = "<div class='taken' id='div"+pos+"'><span style='display: flex;'><img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/1134440/icon"+char+".png' style='width: 50px; margin: auto;'></span></div>";
}
// </> Ai Triger and Equal Value Ai Move Randomizer
function aiTurn() {
var posArr = ai();
var ran = Math.floor(Math.random() * posArr.length);
writeOnGame(posArr[ran], aiChar);
checkVictory(aiChar);
}
// </> Player Click
var plMoveDisable = false
function playerMove(pos) {
if (gameStarted && !plMoveDisable) {
plMoveDisable = true;
writeOnGame(pos, plChar);
var win = checkVictory(plChar);
if (win) {return;}
setTimeout(function() {
aiTurn();
plMoveDisable = false;
},450);
}
}
// --- /\ /\ /\ After Game Start /\ /\ /\ ---
// --- \/ \/ \/ AI \/ \/ \/ ---
// </> MinMax algo
function ai() {
if (gameStarted) {
function isOpen(gameState,pos) {
return gameState[pos] === "0";
}
function didWin(gameState, val) {
var inx = [], i;
for (i = 0; i < 9; i++) {
if (gameState[i] === val) {
inx.push(i); } }
for (var i = 0; i < 8; i++) {
if (inx.indexOf(winCond[i][0]) !== -1 &&
inx.indexOf(winCond[i][1]) !== -1 &&
inx.indexOf(winCond[i][2]) !== -1) {
return true; } } return false; }
function findScore(scores, x) {
if (scores.indexOf(x) !== -1) {return x;}
else if (scores.indexOf(0) !== -1) {return 0;}
else if (scores.indexOf(x * -1) !== -1) {return x * -1;}
else {return 0;}
}
var scoresMain = ['0','0','0','0','0','0','0','0','0'];
function findBestMove() { // MAIN FUNCTION
for (var i = 0; i < 9; i++) {
if (isOpen(gameMain, i)) {
var simGame = gameMain.slice();
simGame[i] = aiChar;
if (didWin(simGame, aiChar)) {
scoresMain[i] = 1;
} else {
scoresMain[i] = plSim(simGame);
}
}
}
var bigest = -99;
for (var j = 0; j < 9; j++) {
if (scoresMain[j] !== '0' && scoresMain[j] > bigest) {
bigest = scoresMain[j];
}
}
var inx = [], i;
for (i = 0; i < 9; i++) {
if (scoresMain[i] === bigest) {
inx.push(i); } }
console.log(gameMain.slice(0,3), scoresMain.slice(0,3));
console.log(gameMain.slice(3,6), scoresMain.slice(3,6));
console.log(gameMain.slice(6,9), scoresMain.slice(6,9));
return inx;
}
function plSim(simGame) { // PL SIM
var simGameTest = simGame.slice();
for (var i = 0; i < 9; i++) {
if (isOpen(simGame, i)) {
simGameTest = simGame.slice();
simGameTest[i] = plChar;
if (didWin(simGameTest, plChar)) {
return -1;
}
}
}
var plScores = ['0','0','0','0','0','0','0','0','0'];
for (var j = 0; j < 9; j++) {
if (isOpen(simGame, j)) {
simGameTest = simGame.slice();
simGameTest[j] = plChar;
plScores[j] = aiSim(simGameTest);
}
}
return findScore(plScores, -1);
}
function aiSim(simGame) { // AI SIM
var simGameTest = simGame.slice();
for (var i = 0; i < 9; i++) {
if (isOpen(simGame, i)) {
simGameTest = simGame.slice();
simGameTest[i] = aiChar;
if (didWin(simGameTest, aiChar)) {
return 1;
}
}
}
var aiScores = ['0','0','0','0','0','0','0','0','0'];
for (var j = 0; j < 9; j++) {
if (isOpen(simGame, j)) {
simGameTest = simGame.slice();
simGameTest[j] = aiChar;
aiScores[j] = plSim(simGameTest);
}
}
return findScore(aiScores, 1);
} // aiSim()
return findBestMove();
}
} // ai() end
charsBtnGen();
That's It! You have now successfully created an Unbeatable Emoji Tic-Tac-Toe.
Top comments (1)
Sorry, I must have hit "Hide".