Single Player mode in Tic-tac-toe - javascript

I am trying to create a one-player tic-tac-toe game using JavaScript. Following is the code for JavaScript, CSS, and HTML respectively:
const grid = [];
const GRID_LENGTH = 3;
let turn = 'X';
function initializeGrid() {
for (let colIdx = 0; colIdx < GRID_LENGTH; colIdx++) {
const tempArray = [];
for (let rowidx = 0; rowidx < GRID_LENGTH; rowidx++) {
tempArray.push(0);
}
grid.push(tempArray);
}
}
function getRowBoxes(colIdx) {
let rowDivs = '';
for (let rowIdx = 0; rowIdx < GRID_LENGTH; rowIdx++) {
let additionalClass = 'darkBackground';
let content = '';
const sum = colIdx + rowIdx;
if (sum % 2 === 0) {
additionalClass = 'lightBackground'
}
const gridValue = grid[colIdx][rowIdx];
if (gridValue === 1) {
content = '<span class="cross">X</span>';
} else if (gridValue === 2) {
content = '<span class="cross">O</span>';
}
rowDivs = rowDivs + '<div colIdx="' + colIdx + '" rowIdx="' + rowIdx + '" class="box ' +
additionalClass + '">' + content + '</div>';
}
return rowDivs;
}
function getColumns() {
let columnDivs = '';
for (let colIdx = 0; colIdx < GRID_LENGTH; colIdx++) {
let coldiv = getRowBoxes(colIdx);
coldiv = '<div class="rowStyle">' + coldiv + '</div>';
columnDivs = columnDivs + coldiv;
}
return columnDivs;
}
function renderMainGrid() {
const parent = document.getElementById("grid");
const columnDivs = getColumns();
parent.innerHTML = '<div class="columnsStyle">' + columnDivs + '</div>';
}
function onBoxClick() {
var rowIdx = this.getAttribute("rowIdx");
var colIdx = this.getAttribute("colIdx");
let newValue = 1;
grid[colIdx][rowIdx] = newValue;
renderMainGrid();
addClickHandlers();
}
function addClickHandlers() {
var boxes = document.getElementsByClassName("box");
for (var idx = 0; idx < boxes.length; idx++) {
boxes[idx].addEventListener('click', onBoxClick, false);
}
}
initializeGrid();
renderMainGrid();
addClickHandlers();
.parentTop {
display: flex;
align-items: center;
justify-content: center;
}
.gridTop {
border-color: "#f44336";
border: '1px solid red';
display: flex;
flex-direction: column;
}
.lightBackground {
background-color: 00FFFF
}
.columnsStyle {
display: flex;
flex-direction: column;
}
.rowStyle {
display: flex;
}
.darkBackground {
background-color: F0FFFF
}
.box {
width: 100;
height: 100;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 100px;
}
.cross {
color: #f90;
font-size: 2.5em;
border-radius: 5px;
line-height: 100px;
}
<div class="header">
<h1> Tic Tac Toe</h1>
</div>
<div class="parentTop">
<div class="gridTop">
<div id="grid">
</div>
</div>
</div>
<script type="text/javascript" src="./index.js"></script>
<link rel="stylesheet" href="./index.css">
Here box represents one placeholder for either X or a 0.
We have a 2D array to represent the arrangement of X or O is a grid
0 -> empty box
1 -> box with X
2 -> box with O
User is playing with Computer so every alternate move should be by Computer
X -> player
O -> Computer
I am not able wrap my head around the implementation of the algorithm used for computer (Min-max algorithm is one) in code.

traverse it in a loop as column first, and row and then diagonal these are three cases where you have to check consecutive user inputs if first two consecutive user inputs found in any of above case then your program have put their value as third one as apponent

Related

Multiplication Table in Javascript space between numbers?

var numbers = "";
for (var i = 1; i <= 13; i++){
for (var j = 1; j<= 13; j++){
numbers += (i*j) + '';
}
numbers += '<br>';
}
element.innerHTML = numbers;
how can i make a space between every number?
example:
You can use <table> for that to structure your text:
let numbers = "<tbody>";
for (let i = 1; i <= 13; i++){
numbers += '</tr>';
for (let j = 1; j<= 13; j++){
numbers += `<td>${i*j}</td>`;
}
numbers += '</tr>';
}
numbers += "</tbody>"
const element = document.querySelector("#element");
element.innerHTML = numbers;
td {
text-align: right;
width: 2em;
}
<table id="element">
You can use grid approach with some CSS variables to make cell elements, for more details see comments in Code snippet:
Also you could render headings of the cells.
const gridSize = 13;
// get grid element
const grid = document.querySelector('.grid');
// create temporary wrapper
const fragmentWrapper = document.createDocumentFragment();
const renderTop = () => {
for (var i = 0; i <= gridSize; i++) {
if (i === 0) {
// render mult char
renderCell('x');
continue;
}
renderCell(i, true);
}
}
const renderGrid = () => {
// append cells to wrapper
for (var i = 1; i <= gridSize; i++) {
for (var j = 0; j <= gridSize; j++) {
if (j === 0) {
// render heading cell
const num = i;
renderCell(num, true);
continue;
}
// render default cell
const num = i * j;
renderCell(num);
}
}
}
const renderCell = (num, select = false) => {
// create temporary cell
const cell = document.createElement('div');
cell.className = 'cell';
// if requested to highligh cell
select && cell.classList.add('select');
cell.innerText = `${ num }`;
// add cell to grid
fragmentWrapper.append(cell);
}
renderTop();
renderGrid();
// set grid size
grid.style.setProperty('--grid-size', gridSize);
// render grid
grid.append(fragmentWrapper);
.grid {
display: grid;
grid-template-columns: repeat(calc(var(--grid-size) + 1), 1fr);
/* using CSS grid size (set in JS) */
gap: .25rem;
}
.cell {
display: flex;
align-items: center;
/* align content */
justify-content: center;
/* align content */
aspect-ratio: 1/1;
/* make cell square */
border: 1px solid #d4d4d4;
/* show cell borders */
}
.cell.select {
background-color: tomato;
color: white;
font-weight: bold;
}
<div class="grid"></div>
You could be ambitious and, like David said, use CSS to take the weight of how the data is meant to be displayed. I've used CSS grid in this example which means you only need to have one loop, and the CSS takes care of the rest.
const arr = [];
const size = 13;
const limit = size * size;
// Push an HTML template string into
// the array
for (var i = 1; i <= limit; i++) {
arr.push(`<div>${i}</div>`);
}
// Add the joined array (it makes an HTML string
// to the element with the `grid` class
const grid = document.querySelector('.grid');
grid.innerHTML = arr.join('');
.grid {
display: grid;
grid-template-columns: repeat(13, 30px);
grid-gap: 0.2em;
}
.grid div {
border: 1px solid #cdcdcd;
font-size: 0.8em;
background-color: #efefef;
text-align: center;
padding: 0.5em 0;
}
<div class="grid"></div>
Additional documentation
Template/string literals
Edit: you can even create a function that will accept a number, and, using CSS variables, the function will work out the size of the grid required and return the HTML.
function createGrid(columns) {
const arr = [];
const grid = columns * columns;
document.documentElement.style.setProperty('--columns', columns)
// Push an HTML template string into
// the array
for (var i = 1; i <= grid; i++) {
arr.push(`<div>${i}</div>`);
}
return arr.join('');
}
const grid = document.querySelector('.grid');
grid.innerHTML = createGrid(5);
:root {
--columns: 13;
}
.grid {
display: grid;
grid-template-columns: repeat(var(--columns), 40px);
grid-gap: 0.2em;
}
.grid div {
border: 1px solid #cdcdcd;
font-size: 0.8em;
background-color: #efefef;
text-align: center;
padding: 0.5em 0;
}
<div class="grid"></div>
Please use like this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
table {
border-collapse: collapse;
width: 100%;
}
th, td {
text-align: left;
padding: 8px;
}
</style>
</head>
<body>
<div id="test" style="width:800px;height: 350px; border: 1px solid sandybrown;">
</div>
<script>
var element = document.getElementById("test");
var html = "<table>";
for (var i = 1; i <= 10; i++){
html += "<tr>";
for (var j = 1; j<= 10; j++){
html += "<td>"+ (i*j) +"</td>";
}
html += '</tr> ';
}
html += "</table>";
element.innerHTML = html;
</script>
</body>
</html>

How to build a table on HTML page which is pushed as list?

I am trying to show .csv content on a page as a table. The list includes unknown rows and columns that depends on the user. It is not a fixed type like 2x2 or 3x4.
But I got something like the following;
[
[
&
#
x
2
7
;
x
x
.......
I am redirecting a list and also tried json. The content of the list is not fixed. The length and column side are dependable. I am trying to pass data properly and show as table
return list;
return render(request, 'yuklenen.html', {'veriler': yuklenen, 'file': listx })
I want to show it as
<div id="contain"></div>
Here's the code:
<script>
var str = '<ul>';
var data1 = "{{file}}" ;
for(var x in at){
str+='<li>' + at[x] + '<li>';
}
str += '</ul>';
document.getElementById("contain").innerHTML = str;
</script>
Hope this will get you started:
function rand(min, max)
{
return Math.round(Math.random() * (max - min) + min);
}
function update()
{
const at = [];
for(let x = 0; x < rand(3, 10); x++)
{
const c = [];
for(let y = 0; y < rand(1, 10); y++)
{
c[y] = rand(0, 100);
}
at[x] = c;
}
var str = '<ul>';
for(var x in at){
str+='<li>';
for(var y in at[x])
str += "<span>" + at[x][y] + "</span>";
str+='</li>';
}
str += '</ul>';
document.getElementById("contain").innerHTML = str;
}
update();
ul
{
display: table;
border-collapse: collapse;
}
li
{
display: table-row;
}
li > span
{
border: 1px solid black;
vertical-align: middle;
text-align: center;
display: table-cell;
width: 2em;
height: 2em;
}
<button onclick="update()">update</button>
<div id="contain"></div>

How to save active class to localstorage and keep when reloading in javascript

Is there any way to store toggled/activated item using localstorage and reload it?
I'm building a habit tracker and want the selected stars stay active when reloading the page.
So I tried localstorage.setItem with queryselectorAll, but it's not working.
Also tried stringify but only empty array was saved to localstorage.
let today = new Date();
let currentMonth = today.getMonth();
let currentYear = today.getFullYear();
const trackerBody = document.querySelector(".js-tracker-body");
function showTracker(month, year) {
const selectedDate = new Date(year, month);
const lastDate = new Date(
selectedDate.getFullYear(),
selectedDate.getMonth() + 1,
0
);
let date = 1;
for (let i = 0; i < 6; i++) {
const row = document.createElement("tr");
for (let j = 0; j < 6; j++) {
if (date <= lastDate.getDate()) {
const cell = document.createElement("td");
const cellPtag = document.createElement("p");
const cellText = document.createTextNode(date);
const cellIcon = document.createElement("i");
cell.setAttribute("class", "habit-count");
cellIcon.setAttribute("class", "fas fa-star star-icon");
cellPtag.appendChild(cellText);
cell.appendChild(cellPtag);
cell.appendChild(cellIcon);
row.appendChild(cell);
date++;
} else {
break;
}
}
trackerBody.appendChild(row);
}
}
showTracker(currentMonth, currentYear);
document.body.addEventListener("click", e => {
if (e.target.closest(".fa-star")) {
e.target.classList.toggle('selected');
}
saveSelectedTracker();
});
function saveSelectedTracker() {
const selectedTracker = document.querySelectorAll('.selected');
localStorage.setItem('selected', selectedTracker);
}
.tracker {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
background-color: #413f63;
}
.tracker-head {
margin-bottom: 30px;
text-align: center;
}
.date-picker {
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.previous,
.next {
padding: 0 20px;
font-size: 17px;
}
.monthAndYear {
font-size: 36px;
font-weight: 300;
}
.habit-content {
font-size: 16px;
font-weight: 300;
}
.tracker-items {
font-size: 20px;
text-align: center;
}
tr {
display: flex;
}
.habit-count {
padding: 0 10px 15px 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.habit-count:last-child {
padding-right: 0;
}
.habit-count p {
margin-bottom: 5px;
font-size: 14px;
}
.star-icon {
font-size: 30px;
color: #c2b7b0;
}
.selected {
color: #f4df21;
}
<div class="wrapper">
<div class="tracker">
<div class="tracker-head">
<div class="date-picker">
<a class="previous" onclick="previous()">
<i class="fas fa-chevron-left"></i>
</a>
<strong class="monthAndYear"></strong>
<a class="next" onclick="next()">
<i class="fas fa-chevron-right"></i>
</a>
</div>
<h1 class="js-habit-content habit-content"></h1>
</div>
<div class="tracker-main">
<table class="traker-items">
<thead></thead>
<tbody class="js-tracker-body"></tbody>
</table>
</div>
</div>
</div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/js/all.min.js"
integrity="sha256-HkXXtFRaflZ7gjmpjGQBENGnq8NIno4SDNq/3DbkMgo="
crossorigin="anonymous"
></script>
function showTracker(month, year) {
const selectedDate = new Date(year, month);
const lastDate = new Date(
selectedDate.getFullYear(),
selectedDate.getMonth() + 1,
0
);
monthAndYear.innerHTML = `${months[month]} ${year}`;
trackerBody.innerHTML = "";
let date = 1;
for (let i = 0; i < 6; i++) {
const row = document.createElement("tr");
for (let j = 0; j < 6; j++) {
if (date <= lastDate.getDate()) {
const cell = document.createElement("td");
const cellPtag = document.createElement("p");
const cellText = document.createTextNode(date);
const cellIcon = document.createElement("i");
cell.setAttribute("class", "habit-count");
cellIcon.setAttribute("class", "fas fa-star star-icon");
cellPtag.appendChild(cellText);
cell.appendChild(cellPtag);
cell.appendChild(cellIcon);
row.appendChild(cell);
date++;
} else {
break;
}
}
trackerBody.appendChild(row);
}
}
showTracker(currentMonth, currentYear);
document.body.addEventListener("click", e => {
if (e.target.closest(".fa-star")) {
e.target.classList.toggle('selected');
}
saveSelectedTracker();
});
function saveSelectedTracker() {
const selectedTracker = document.querySelectorAll('.selected');
localStorage.setItem('selected', selectedTracker);
}
I think the issue is that you're saving references to the actual elements on the page, vs data about which items are selected. Try changing it so that you save an array of identifiers for which items are selected, and then on page load looked for that list and reapplied the selected class to those.
You can do a quick check that your .setItem is working by hardcoding a simple string vs the variable you're currently using.
Perhaps try setting the index (value of j) into the cellIcon, like this:
cellIcon.setAttribute("data-index", j);
then in saveSelectedTracker get all the selected cells as you are doing, but save the index of j instead.
const selectedTracker = document.querySelectorAll('.selected');
const starredIndexes = [];
selectedTracker.forEach(function(item) {
starredIndexes.push(item.getAttribute('data-index'));
});
localStorage.setItem('selected', JSON.stringify(starredIndexes));
then in your showTracker method, pull out the array from local storage, deserialize and use the array to check if the star should be marked with the css class selected.
const starredIndexes = JSON.parse(localStorage.getItem('selected'))
//in your forloop
if(starredIndexes && Array.isArray(starredIndexes) && starredIndexes.indexOf(j) !== -1){
cellIcon.setAttribute("class", "fas fa-star star-icon selected"); //mark ones saved in local storage
}else{
cellIcon.setAttribute("class", "fas fa-star star-icon");
}
I think this would accomplish the gist of what you are trying to do. Let me know if you have any questions.
You should not try to store the elements themselves in the localStorage; instead, store their indexes.
const stars = document.querySelectorAll('.fa-star > path');
let stored = localStorage.getItem("selected") || '[]'
let indexes = JSON.parse(stored)
for(const index of indexes){
stars[index].classList.add('selected');
}
document.body.addEventListener("click", e => {
if (e.target.closest(".fa-star")) {
e.target.classList.toggle('selected');
}
saveSelectedTracker();
});
function saveSelectedTracker() {
const indexes = [];
for(let i = 0; i < stars.length; i++){
if(stars[i].classList.contains("selected")){
indexes.push(i);
}
}
console.log(indexes);
localStorage.setItem('selected', JSON.stringify(indexes));
}
Working Demo

Grid if box has specific class stop JavaScript

I have a grid with a player, yellow box, and obstacles (.ob) and black boxes. I don't want the player to go in the obstacle squares when I click the 'UP' button.
I was thinking to check if the next class has .ob do not go there. Any suggestions?
let moveCounter = 0;
var grid = document.getElementById("grid-box");
for (var i = 1; i <= 50; i++) {
var square = document.createElement("div");
square.className = 'square';
square.id = 'square' + i;
grid.appendChild(square);
}
var obstacles = [];
while (obstacles.length < 20) {
var randomIndex = parseInt(49 * Math.random());
if (obstacles.indexOf(randomIndex) === -1) {
obstacles.push(randomIndex);
var drawObstacle = document.getElementById('square' + randomIndex);
$(drawObstacle).addClass("ob")
}
}
var playerTwo = [];
while (playerTwo.length < 1) {
var randomIndex = parseInt(49 * Math.random());
if (playerTwo.indexOf(randomIndex) === -1) {
playerTwo.push(randomIndex);
var drawPtwo = document.getElementById('square' + randomIndex);
$(drawPtwo).addClass("p-1")
}
};
$('#button_up').on('click', function() {
moveCounter += 1;
$pOne = $('.p-1')
var id = $pOne.attr('id')
var idNumber = +id.slice(6);
var idMove = idNumber - 10
var idUpMove = 'square' + idMove;
$pOne.removeClass('p-1');
$('#' + idUpMove).addClass('p-1');
});
#grid-box {
width: 400px;
height: 400px;
margin: 0 auto;
font-size: 0;
position: relative;
}
#grid-box > div.square {
font-size: 1rem;
vertical-align: top;
display: inline-block;
width: 10%;
height: 10%;
box-sizing: border-box;
border: 1px solid #000;
}
.p-1 {
background-color: yellow;
}
.ob {
background-color: black;
}
<div id="grid-box"></div>
<div class="move">
<button id="button_up">UP</button><br>
</div>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
jsFifddle
Use the following code
$('#button_up').on('click', function() {
moveCounter += 1;
$pOne = $('.p-1')
var id = $pOne.attr('id')
var idNumber = +id.slice(6);
var idMove = idNumber - 10
var idUpMove = 'square' + idMove;
if($('#' + idUpMove).hasClass('ob')){
return false;
}
$pOne.removeClass('p-1');
$('#' + idUpMove).addClass('p-1');
});
Here we check the next selected class having ".ob" class if its return true then we stop the process if it returns false then process continues
if($('#' + idUpMove).hasClass('ob')){
return false;
}
Fiddle

drawing a line between two table cells

My homework was to create a scipt that connects two marked cells of a table. My solution works fine, but the result line is a bit disproportionate.
As you can see, the last row is 4-cells long while all other rows are 3-cells long. But I'd like the middle part of a line to be the longest. Like this:
How to get the desired result and not make the code too complicated. The code itself:
function connectThem() {
if (markedCells.length == 2) {
y_distance = markedCells[0][0] - markedCells[1][0];
y_distance = Math.abs(y_distance) + 1; // vertial distance
x_distance = markedCells[0][1] - markedCells[1][1];
x_distance = Math.abs(x_distance) + 1; // horizontal distance
if (x_distance > y_distance) { // if horizontal distance greater than vertical distance
x_distance -= 0;
alert("horizontal connection");
totalRows = y_distance;
for (var row = 0; row < y_distance; row++) {
thisRowLength = Math.floor(x_distance / totalRows);
for (var c = 0; c < thisRowLength; c++) {
document.getElementById('cell-' + markedCells[0][0] + '-' + markedCells[0][1]).style.backgroundColor = "red";
markedCells[0][1] = parseInt(markedCells[0][1]) + 1;
}
markedCells[0][0] = parseInt(markedCells[0][0]) + 1;
totalRows -= 1; // vertical remaining
x_distance -= thisRowLength; // horizontal remaining
}
} else {
y_distance -= 0;
alert("vertical or horizontal connection");
totalCols = x_distance;
for (var col = 0; col < x_distance; col++) {
thisColLength = Math.floor(y_distance / totalCols);
for (var r = 0; r < thisColLength; r++) {
document.getElementById('cell-' + markedCells[0][0] + '-' + markedCells[0][1]).style.backgroundColor = "red";
markedCells[0][0] = parseInt(markedCells[0][0]) + 1;
}
markedCells[0][1] = parseInt(markedCells[0][1]) + 1;
totalCols -= 1;
y_distance -= thisColLength;
}
}
alert("x distance: " + x_distance + " y distance: " + y_distance);
} else {
alert("Can't connect " + markedCells.length + " cells together :-(")
}
}
var htmlElements = ""; // storing the whole table here
for (var r = 1; r < 41; r++) { // creating the table row by row
htmlElements += '<tr>';
for (var c = 1; c < 61; c++) { // and column by column
htmlElements += '<td class="tCell white" id="cell-' + r.toString() + '-' + c.toString() + '"></td>';
}
htmlElements += '</tr>'
}
var theTable = document.getElementById("tab");
theTable.innerHTML = htmlElements;
var allTableCells = document.querySelectorAll("td"); // adding all cells to an array
var markedCells = [] // storing marked cells here
for (var i = 0; i < allTableCells.length; i++) {
allTableCells[i].addEventListener("click", function() { // when click any cell
let stringID = this.id.split('-');
if (this.className == "tCell white") {
this.className = "tCell red";
markedCells.push([stringID[1], stringID[2]]);
} else {
this.className = "tCell white";
let index = markedCells.indexOf([stringID[1], stringID[2]]);
markedCells.splice(index, 1);
}
console.log(markedCells);
});
}
body {
background-color: #333;
}
#workspace {
position: absolute;
width: 900px;
height: 600px;
background-color: whitesmoke;
top: 50%;
left: 50%;
margin-left: -450px;
margin-top: -300px;
}
table,
tr,
td {
border: 1px solid black;
border-collapse: collapse;
padding: 0;
}
table {
width: 900px;
height: 600px;
}
.red {
background: red;
color: white;
}
.white {
background: white;
color: black;
}
#btn {
position: absolute;
width: 100px;
top: 50px;
left: 50%;
margin-left: -50px;
}
<div id="workspace">
<table id="tab">
</table>
</div>
<button id="btn" onclick="connectThem()">Connect!</button>

Categories