I am trying to build a simple minesweeper game in Javascript. It works propererly apart from the function to open the entire mine-free area when clicking on a mine-free tile. It starts checking the neighbouring tiles, but stops when the first neighbouring tile has a mine.
As you can see on the screenshot below (after clicking on tile 1/5) only the tiles until the first "1" are opened. It should actually open a much larger area:
It seems I am pretty close. THis is my code:
const gridSize = 10
// generate grid
const board = document.querySelector("#minesweeper");
// loop over num for rows
let header = 0;
for(let i = 0; i < gridSize+1; i++) {
const row = document.createElement("tr");
// loop over num for cols
for (let j = 0; j < gridSize+1; j++) {
// add col to row
if ( i === 0 ) {
row.insertAdjacentHTML("beforeend", `<th>${header}</th>`);
header += 1;
} else if (j === 0) {
row.insertAdjacentHTML("beforeend", `<th>${header-10}</th>`);
header += 1;
} else {
row.insertAdjacentHTML("beforeend", `<td class='unopened' dataset-column=${j}></td>`);
};
};
// add row to board
board.append(row);
};
// functions -------------------
function getNeighbour(tile, i, j) {
const column = tile.cellIndex; // so the columns get the cellIndex
const row = tile.parentElement.rowIndex; // row gets the rowIndex(tr)
const offsetY = row + i;
const offsetX = column + j;
return document.querySelector(`[data-row="${offsetY}"][data-column="${offsetX}"]`);
}
// count mines of neighbours
function countMines(tile) {
let mines = 0;
for(i = -1; i <= 1; i++) {
for(j = -1; j <= 1; j++ ) {
// check if neighbour has mine
// get cell values from neighbour in DOM
nb = getNeighbour(tile, i, j);
if (nb && nb.classList.contains('has-mine') || (nb && nb.classList.contains('mine'))) mines += 1; // if nb exists and has a mine increase mines
}
}
// write into DOM
if (mines === 0) {
tile.classList.add(`opened`);
} else {
tile.classList.add(`neighbour-${mines}`);
}
tile.classList.remove(`unopened`);
// if mines are 0, go to neigbours and count mines there
// console.log(tile.classList);
if (mines === 0) {
// alert("mines are zero");
for (i = -1; i <= 1; i+=1) {
for (j = -1; j <= 1; j+=1) {
nb = getNeighbour(tile, i, j);
if (nb && nb.classList.contains("unopened")) {
countMines(nb);
}
}
}
}
return mines;
}
// function open tile on click
function openTile(event) {
const tile = event.currentTarget;
// if there is a mine you lose
if (tile.classList.contains("has-mine")) {
document.querySelectorAll(".has-mine").forEach((cell) => {
cell.classList.remove("has-mine", "unopened");
cell.classList.add("mine", "opened");
});
alert("booooooooom!");
} else {
countMines(tile);
}
}
const tiles = document.querySelectorAll("td");
tiles.forEach((td) => {
td.dataset.column = td.cellIndex; // so the columns get the cellIndex
td.dataset.row = td.parentElement.rowIndex; // row gets the rowIndex(tr)
// add mines randomly
const freq = 0.1;
if (Math.random() < freq) {
td.classList.add("has-mine");
}
// eventlisteners per tile
td.addEventListener("click", openTile);
});
I have been thinking hours about it but could not find a way to work on with this code. Not sure if I am close or if I would need to modify the whole approach?
Many thanks for any ideas!
the principle is simple, for each empty cell, you must add all the adjacent empty cells.
it is also necessary to collect the number of adjacent mines of each cell
a) list the 8 adjacent cells, except for the cells placed at the edge
this is the prxElm() function in my code
b) count the mines present around a cell -> prxMne()
starting from the first cell
1- we count (a) nearby mines
2- it becomes the first element of a stack of cells to be mapped
3- if its number of nearby mines is zero, repeat this operation for all adjacent cells
the particularity of this algorithm is to use only one stack to accumulate the coordinates to be mapped.
it places the elements with adjacent mines at the top of the stack, and those with none at the end of the stack.
as there can be several cells without adjacent mines, we keep an indexiExp of the last empty cell processed.
of course when you add a cell with mines nearby at the start of the stack, this index is shifted.
the algorithm also take care not to add duplicate the same cell by checking before if this cell is not in the stack.
see .filter(x=>!explor.some(e=>e.p===x.p))
this ends when the exploration index iExp reaches the end of the stack.
here is the whole code, it is not completely finalized, but the essentials are there.
const
MinesCount = 17 // adjusted values to fit this snippet display area
, gridSz = { r:7, c:20 } // grid rows x cols
, gridMx = gridSz.r * gridSz.c
, proxim = [ {v:-1,h:-1}, {v:-1,h:0}, {v:-1,h:+1}, {v:0,h:-1}, {v:0,h:+1}, {v:+1,h:-1}, {v:+1,h:0}, {v:+1,h:+1} ]
, prxElm = (r,c) => proxim.reduce((a,{v,h})=>
{
let rv = r+v, ch = c+h;
if (rv>=0 && ch>=0 && rv<gridSz.r && ch<gridSz.c) a.push({p:((rv * gridSz.c) + ch), r:rv, c:ch} )
return a
},[])
, GenNbX = (nb,vMax) => [null].reduce(arr=>
{
while (arr.length < nb)
{
let numGen = Math.floor(Math.random() * vMax)
if (!arr.includes(numGen)) arr.push(numGen);
}
return arr //.sort((a,b)=>a-b)
},[])
, minesP = GenNbX( MinesCount, gridMx )
, prxMne = (r,c) => prxElm(r,c).reduce((a,{p})=>minesP.includes(p)?++a:a,0) // count mines arroub=nd
, td2rcp = td =>
{
let r = td.closest('tr').rowIndex -1 // -1 for thead count of rows
, c = td.cellIndex
, p = (r * gridSz.c) +c
return {r,c,p}
}
, p2rc = p =>({r: Math.floor(p / gridSz.c), c: (p % gridSz.c)})
, { timE, cFlags, minesArea } = drawTable('mines-area', gridSz, MinesCount )
;
const chrono = (function( timeElm )
{
const
one_Sec = 1000
, one_Min = one_Sec * 60
, twoDgts = t => (t<10) ? `0${t}` : t.toString(10)
, chronos =
{ timZero : null
, timDisp : timeElm
, timIntv : null
, running : false
}
, obj =
{ start()
{
if (chronos.running) return
chronos.timDisp.textContent = '00:00'
chronos.running = true
chronos.timZero = new Date().getTime()
chronos.timIntv = setInterval(() =>
{
let tim = (new Date().getTime()) - chronos.timZero
chronos.timDisp.textContent = `${Math.floor(tim/one_Min)}:${twoDgts(Math.floor((tim % one_Min)/one_Sec))}`
}
, 250);
}
, stop()
{
if (!chronos.running) return
chronos.running = false
clearInterval( chronos.timIntv )
}
}
return obj
}(timE))
function drawTable(tName, gSz, mines )
{
let table = document.getElementById(tName)
// table.innerHTML = '' // eraze table
let tHead = table.createTHead()
, tBody = table.createTBody()
, xRow = tHead.insertRow()
, timE = xRow.insertCell()
, cFlags = xRow.insertCell()
;
timE.setAttribute('colspan', gSz.c -4)
timE.className = 'time'
timE.textContent = '0:00'
cFlags.setAttribute('colspan', 4)
cFlags.className = 'flag'
cFlags.textContent = ' 0/' + mines
for (let r=gSz.r;r--;)
{
xRow = tBody.insertRow()
for(let c = gSz.c;c--;) xRow.insertCell()
}
return { timE, cFlags, minesArea: tBody }
}
minesArea.onclick = ({target}) =>
{
if (!target.matches('td')) return
if (target.hasAttribute('class')) return // already done
chrono.start()
let {r,c,p} = td2rcp(target)
if (minesP.includes(p)) // you are dead!
{
chrono.stop()
minesArea.className = 'Boom'
minesP.forEach(p=> // show mines
{
let {r,c} = p2rc(p)
let td = minesArea.rows[r].cells[c]
if (!td.hasAttribute('class')) td.className = 'mineOff'
})
minesArea.rows[r].cells[c].className = 'mineBoom' // this one is for you
minesArea.querySelectorAll('td:not([class]), td.flag') // jusr disable click
.forEach(td=>td.classList.add('off')) // and cursor
}
else
{
let explor = [ {p, r, c, m:prxMne(r,c) } ]
, iExp = 0
;
while (iExp < explor.length && explor[iExp].m === 0) // Open mine-free area
{
prxElm(explor[iExp].r,explor[iExp].c) // look around
.filter(x=>!explor.some(e=>e.p===x.p)) // if not already in
.forEach(x=>
{
M = prxMne(x.r,x.c)
if (M>0 ) { explor.unshift( { p:x.p, r:x.r, c:x.c, m:M} ); iExp++ }
else explor.push( { p:x.p, r:x.r, c:x.c, m:M} ) // mine-free space
})
iExp++
}
explor.forEach(({r,c,m})=>minesArea.rows[r].cells[c].className = 'm'+m )
}
if (checkEnd()) // some kind of victory!?
{
chrono.stop()
minesArea.querySelectorAll('td.flag').forEach(td=>td.classList.add('off'))
minesArea.className = 'win'
}
}
minesArea.oncontextmenu = e => // Yes, there is a right click for flag mines
{
if (!e.target.matches('td')) return
e.preventDefault()
let {r,c,p} = td2rcp( e.target)
, cell_rc = minesArea.rows[r].cells[c]
;
if (!cell_rc.hasAttribute('class')) cell_rc.className = 'flag'
else if (cell_rc.className === 'flag') cell_rc.removeAttribute('class')
let nbFlags = minesArea.querySelectorAll('td.flag').length
cFlags.textContent = ` ${nbFlags} / ${MinesCount}`
}
function checkEnd()
{ // what about us ?
let count = 0
, reject = 0
, tdNotSeen = minesArea.querySelectorAll('td:not([class])')
, flagPos = minesArea.querySelectorAll('td.flag')
;
cFlags.textContent = ` ${flagPos.length} / ${MinesCount}`
if (tdNotSeen.length > MinesCount ) return false
flagPos.forEach(td=>
{
let {r,c,p} = td2rcp(td)
if (minesP.includes(p)) count++ // correct place
else reject++
})
tdNotSeen.forEach(td=>
{
let {r,c,p} = td2rcp(td)
if (minesP.includes(p)) count++
else reject++ // no mines there
})
if (count != MinesCount || reject != 0 ) return false
tdNotSeen.forEach(td=>
{
let {r,c,p} = td2rcp(td)
minesArea.rows[r].cells[c].className = 'mineOff'
})
cFlags.textContent = ` ${MinesCount} / ${MinesCount}`
return true
}
body { background-color: #383947; } /* dark mode ? ;-) */
table {
border-collapse : collapse;
margin : 1em auto;
--szRC : 18px;
font-family : Arial, Helvetica, sans-serif;
}
table td {
border : 1px solid #1a1a1a80;
text-align : center;
}
table thead {
font-size : .8em;
background-color : #c3c5db;
}
table tbody {
background-color : #a39999;
cursor : cell;
}
table tbody td {
width : var(--szRC);
height : var(--szRC);
overflow : hidden;
}
.m0, .m1, .m2, .m3, .m4, .m5, .m6, .m7, .m8 { background-color: whitesmoke; font-size: 12px; font-weight: bold; cursor: default; }
.m1::after { content: '1'; color: #0000ff; }
.m2::after { content: '2'; color: #008000; }
.m3::after { content: '3'; color: #ff0000; }
.m4::after { content: '4'; color: #000080; }
.m5::after { content: '5'; color: #800000; }
.m6::after { content: '6'; color: #008080; }
.m7::after { content: '7'; color: #000000; }
.m8::after { content: '8'; color: #808080; }
.off { cursor: default; }
.Boom { background-color: yellow; cursor: default; }
.mineOff { cursor: default; padding: 0; }
.flag { background-color: lightgray; padding: 0; }
.mineBoom { color: crimson; padding: 0; }
.mineOff::after,
.mineBoom::after { content: '\2738'; }
.flag::before { content: '\2691'; color: crimson; }
.time::before { content: 'Time elapsed : '; color: darkblue; }
.win td { border-color: gold;}
<table id="mines-area"></table>
I don't think a recursive method is suitable for this kind of problem.
It requires having a complex strategy for exploring empty spaces.
For example, spiraling around the starting point.
But this strategy comes up against the problem of the island hindering this progress, and which requires, once crossed, to carry out a new spiral advance to recover the points hidden during the previous spiral, but having to avoid the points already explored during the previous spiral.
You can also move forward by sectors from an initial point, but you will still encounter the same problem of the islet and its backtracking, which also multiply here with each roughness on the explored shores.
This requires complex calculations that are difficult to master and debug, not to mention the fact that recursive methods intensively use call stacks which it adds up for each new branch.
(Without neglecting the risk of going crazy, by striving to develop its recursive algorithms.)
The larger the grid and the more its recursive lists will conflict with each other, and more the computationnal time will be affected.
Of course there are already winning heuristics on this type of strategy, they are of a very high level, and we are here just on a minesweeper game where hundreds of lines of code have nothing to do.
My method only uses a single stack, its algorithm is easy to understand and requires a few lines of code.
What more ?
Related
I am currently trying to generate a View with React Typescript where I can show all Appointments of a day (similar to outlook calendar). But I am facing an issue. When Appointments are overlapping how can I determine the with and position? So I guess that I probably need an algorithm accomplish this issue.
Here an example how it could be structured:
Every box stands for an Appointment
The classes of the Object looks like this:
class AppointmentStructured {
appointment: Appointment
width?: number
left?: number
}
class Appointment {
start: Date
end: Date
subject:string
duration: number
}
I am looking for a solution where I can determine the width (max. 1 = 100%) of the specific appointment and additionally I need the position. The easiest would be to how far it is from the left (max. 1). When there are more than 2 appointments starting at the same time, the appointments are ordered by the appointment with the longest duration.
Example the third box from the image would be:
width: 0.333
left: 0.666
Out of this data I can use CSS to design them.
The generated CSS then would look more or less like this:
position: absolute;
width: calc(100% * 0.33333);
top: 20rem; //doesn't matter for the algorithm
left: calc(100% * 0.66666)
--Edit
This is how far i came. It starts with the method generateAppointmentTile([])
export interface AppointmentStructured {
appointment: Appointment
width: string | number;
left: string | number
}
export const generateAppointmentTile = (appointments: Appointment[]): AppointmentStructured[] => {
var appointmentsStructured = initAppointmentStructured(appointments);
var response: AppointmentStructured[] = [];
for (var i = 0; i < appointmentsStructured.length; ++i) {
var width = 1;
var previous = getPreviousOverlapping(appointmentsStructured, i);
var previousWidth = previous
.map((item: AppointmentStructured): number => item.width as number)
.reduce((a: number, b: number): number => a + b, 0)
var forward = getForwardOverlapping(appointmentsStructured, i);
var forwardOverlays = appointmentOverlays(forward);
var previousHasOverlayWithForward = false;
previous.forEach((structured: AppointmentStructured) => checkAppointmentOverlaySingle(structured, forward) !== 0 ? previousHasOverlayWithForward = true : null);
width = (width - previousWidth) / (!previousHasOverlayWithForward && forwardOverlays !== 0 ? forwardOverlays : 1);
appointmentsStructured[i].width = width;
response.push(appointmentsStructured[i]);
}
response.forEach((value: AppointmentStructured): void => {
value.width = `calc((100% - 8rem) * ${value.width})`;
});
return response
}
const appointmentOverlays = (reading: AppointmentStructured[]): number => {
var highestNumber = 0;
reading.forEach((structured: AppointmentStructured): void => {
var start = checkAppointmentOverlaySingle(structured, reading) + 1;
highestNumber = start > highestNumber ? start : highestNumber;
});
return highestNumber;
}
const checkAppointmentOverlaySingle = (structured: AppointmentStructured, reading: AppointmentStructured[]): number => {
var start = 0;
reading.forEach((item: AppointmentStructured): void => {
if (item.appointment.id !== structured.appointment.id) {
if ((structured.appointment.start <= item.appointment.start && structured.appointment.end >= item.appointment.start)
|| (structured.appointment.start >= item.appointment.start && structured.appointment.start <= item.appointment.end)) {
start += 1;
}
}
});
return start;
}
const getPreviousOverlapping = (appointmentsStructured: AppointmentStructured[], index: number): AppointmentStructured[] => {
var response: AppointmentStructured[] = [];
for (var i = index - 1; i >= 0; --i) {
if (appointmentsStructured[index].appointment.start >= appointmentsStructured[i].appointment.start
&& appointmentsStructured[index].appointment.start <= appointmentsStructured[i].appointment.end) {
response.push(appointmentsStructured[i]);
}
}
return response;
}
const getForwardOverlapping = (appointmentsStructured: AppointmentStructured[], index: number): AppointmentStructured[] => {
var response: AppointmentStructured[] = [];
for (var i = index; i < appointmentsStructured.length; ++i) {
if (appointmentsStructured[index].appointment.start >= appointmentsStructured[i].appointment.start
&& appointmentsStructured[index].appointment.start <= appointmentsStructured[i].appointment.end) {
response.push(appointmentsStructured[i]);
}
}
return response;
}
const initAppointmentStructured = (appointments: Appointment[]): AppointmentStructured[] => {
var appointmentsStructured: AppointmentStructured[] = appointments
.sort((a: Appointment, b: Appointment): number => a.start.getTime() - b.start.getTime())
.map((appointment: Appointment): AppointmentStructured => ({ appointment, width: 100, left: 0 }));
var response: AppointmentStructured[] = [];
// sort in a intelligent way
for (var i = 0; i < appointmentsStructured.length; ++i) {
var duration = appointmentsStructured[i].appointment.end.getTime() - appointmentsStructured[i].appointment.start.getTime();
var sameStartAppointments = findAppointmentWithSameStart(appointmentsStructured[i], appointmentsStructured);
var hasLongerAppointment: boolean = false;
sameStartAppointments.forEach((structured: AppointmentStructured) => (structured.appointment.end.getTime() - structured.appointment.start.getTime()) > duration ? hasLongerAppointment = true : null);
if (!hasLongerAppointment) {
response.push(appointmentsStructured[i]);
appointmentsStructured.splice(i, 1);
i = -1;
}
}
return response.sort((a: AppointmentStructured, b: AppointmentStructured): number => a.appointment.start.getTime() - b.appointment.start.getTime());
}
const findAppointmentWithSameStart = (structured: AppointmentStructured, all: AppointmentStructured[]): AppointmentStructured[] => {
var response: AppointmentStructured[] = [];
all.forEach((appointmentStructured: AppointmentStructured) => appointmentStructured.appointment.start === structured.appointment.start
&& appointmentStructured.appointment.id !== structured.appointment.id ? response.push(appointmentStructured) : null)
return response;
}
Even some pseudo code would help me a lot.
This solution is based on this answer: Visualization of calendar events. Algorithm to layout events with maximum width
I've just ported the script from C# to JS and extracted some steps.
Group all your intervals/appointments into distinct groups, where one group does not influence another.
For each group, fill columns from left to right (with each appointment getting the same width). Just use as many columns as you need.
Stretch each appointment maximally.
Make the actual boxes from that and render them.
This procedure is not perfect. Sometimes you will have a thin box on the left and the box to the right got stretched all the way. This happens mainly when the left box has a long "overlap chain" to the bottom, which does not really happen in practice.
/*
interface Interval {
start: number;
end: number;
}
*/
// Just for demonstration purposes.
function generateIntervals({ count, maxStart, maxEnd, minLength, maxLength, segments }) {
return Array.from(Array(count), () => randomInterval())
function randomInterval() {
const start = randomInt(0, maxStart * segments) / segments
const length = randomInt(minLength * segments, maxLength * segments) / segments
return {
start,
end: Math.min(start + length, maxEnd),
text: "Here could be your advertising :)"
}
}
function randomInt(start, end) {
return Math.floor(Math.random() * (end - start) + start)
}
}
function isOverlapping(interval1, interval2) {
const start = Math.max(interval1.start, interval2.start)
const end = Math.min(interval1.end, interval2.end)
return start < end
}
// Sorts by start ascending.
function sortIntervalsByStart(intervals) {
return intervals
.slice()
.sort(({ start: s1 }, { start: s2 }) => s1 - s2)
}
// Split intervals into groups, which are independent of another.
function groupIntervals(intervals) {
const groups = []
let latestIntervalEnd = -Infinity
for (const interval of sortIntervalsByStart(intervals)) {
const { start, end } = interval
// There is no overlap to previous intervals. Create a new group.
if (start >= latestIntervalEnd) {
groups.push([])
}
groups[groups.length - 1].push(interval)
latestIntervalEnd = Math.max(latestIntervalEnd, end)
}
return groups
}
// Fill columns with equal width from left to right.
function putIntervalsIntoColumns(intervals) {
const columns = []
for (const interval of intervals) {
let columnIndex = findFreeColumn(interval)
columns[columnIndex] = (columns[columnIndex] || []).concat([interval])
}
return columns
function findFreeColumn(interval) {
let columnIndex = 0
while (
columns?.[columnIndex]
?.some(otherInterval => isOverlapping(interval, otherInterval))
) {
columnIndex++
}
return columnIndex
}
}
// Expand columns maximally.
function makeBoxes(columns, containingInterval) {
const boxes = []
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
for (const interval of columns[columnIndex]) {
const columnSpan = findColumnSpan(columnIndex, interval)
const box = {
...interval,
top: (interval.start - containingInterval.start) / (containingInterval.end - containingInterval.start),
height: (interval.end - interval.start) / (containingInterval.end - containingInterval.start),
left: columnIndex / columns.length,
width: columnSpan / columns.length
}
boxes.push(box)
}
}
return boxes
function findColumnSpan(columnIndex, interval) {
let columnSpan = 1
while (
columns
?.[columnIndex + columnSpan]
?.every(otherInterval => !isOverlapping(interval, otherInterval))
) {
columnSpan++
}
return columnSpan
}
}
function computeBoxes(intervals, containingInterval) {
return groupIntervals(intervals)
.map(intervals => putIntervalsIntoColumns(intervals))
.flatMap(columns => makeBoxes(columns, containingInterval))
}
function renderBoxes(boxes) {
const calendar = document.querySelector("#calendar")
for (const { top, left, width, height, text = "" } of boxes) {
const el = document.createElement("div")
el.style.position = "absolute"
el.style.top = `${top * 100}%`
el.style.left = `${left * 100}%`
el.style.width = `${width * 100}%`
el.style.height = `${height * 100}%`
el.textContent = text
calendar.appendChild(el)
}
}
const hours = 24
const intervals = generateIntervals({
count: 20,
maxStart: 22,
maxEnd: hours,
maxLength: 4,
minLength: 1,
segments: 2,
})
const boxes = computeBoxes(intervals, { start: 0, end: hours })
renderBoxes(boxes)
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
#calendar {
height: 100vh;
width: 100vw;
min-height: 800px;
position: relative;
}
#calendar > * {
border: 1px solid black;
background-color: #b89d9d;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
overflow-y: auto;
}
<div id="calendar"></div>
<script src="./index.js"></script>
I have create two dices that are 1 until 11 slots.I want to display the results.If red wins (for example I want to show me a message R wins or if B ends first the B wins).Also, I want to display the results in a new table.I am trying to do that but I can't. Javascript code is this.
// basic game settings
const gameSettings = {
length: 12,
die: {
min: 1,
max: 8,
}
}
// define "actors"
let gameItems = [{
name: "R",
bgColor: "red",
color: "white",
position: 1,
},
{
name: "B",
bgColor: "black",
color: "white",
position: 1,
},
]
// the random function
// source: https://stackoverflow.com/questions/4959975/generate-random-number-between-two-numbers-in-javascript
function randomIntFromInterval(min, max) { // min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}
// -----------------
// creating a row & cells
const rowHtml = (gameItem, cols) => {
let html = `<div class="row" style="grid-template-columns: repeat(${ cols }, 1fr)">`
for (let i = 0; i < cols; i++) {
let marker = "-"
let background = "white"
let color = "black"
if (gameItem.position - 1 === i) {
marker = gameItem.name
background = gameItem.bgColor
color = gameItem.color
}
html += `<div class="cell" style="background: ${ background }; color: ${ color }">${ marker }</div>`
}
html += `</div>`
return html
}
// updating rows
const updateRows = (container, gameItems, cols) => {
let html = ''
for (let i = 0; i < gameItems.length; i++) {
html += rowHtml(gameItems[i], cols)
}
container.innerHTML = html
}
// setting container
const container = document.getElementById('container')
// set up board for first time
updateRows(container, gameItems, gameSettings.length)
// -----------------
// action buttons
const btnRoll = document.getElementById("rollTheDice")
const btnResetBoard = document.getElementById("resetBoard")
// roll action
btnRoll.addEventListener('click', function() {
const {
length,
die: {
min
},
die: {
max
}
} = gameSettings
gameItems = gameItems.map(item => ({
...item,
position: item.position + randomIntFromInterval(min, max),
}))
updateRows(container, gameItems, length)
})
// reset action
btnResetBoard.addEventListener('click', function() {
const {
length
} = gameSettings
gameItems = gameItems.map(item => ({
...item,
position: 1,
}))
updateRows(container, gameItems, length)
})
and the code of html,css is this
.row {
display: grid;
grid-template-rows: 1fr;
}
.cell {
box-sizing: border-box;
border: 1px solid black;
display: flex;
justify-content: center;
}
<div id="container"></div>
<hr />
<button id="rollTheDice">ROLL THE DICE</button><br />
<button id="resetBoard">RESET BOARD</button>
Its really simple solution but you can work on it and extend to really have what you want.
I would done it like so apply some check of each player score on each click on roll the dice button, of course after all randomizing happens to prevent need clicking another time to get results.
let winnerScore = 0;
let winner = '';
gameItems.forEach(item => {
if (item.position >= 11) {
winner = item.name;
winnerScore = item.position;
}
});
if (winnerScore >= 11) {
console.log('[winner_result]', winner, ' wins game!');
}
Of course instead of using this console.log on detect that someone won you can easily exapnd it to display scores or really what you want on screen. I think the best way to do that is to really get all properties from "actor" object in loop check.
This solution will really decreas number of loops you need to do.
To really show results you'll have to add function or outputing it into html div element like so
if (winnerScore >= 11) {
console.log('[winner_result]', winner, ' wins game!');
document.getElementById('results').innerHTML = `${winner} wins game!`;
}
to get it working really you also need to add somewhere in you html strucure div with id "results" or anything else really but you have to remeber to swap ip too on js script
Hope It answers you're needs.
Below link to codepen which I was working on this solution:
https://codepen.io/snoh666/pen/jOMamMy
I want to reporduce the placement on the board while the backtracking algorithm work. If I insert a Timeout, the ram of the tab fastly increase and crashes. How can i show the functionlity of the algorithm ?
function solveNQUtil(board, col) {
/* base case: If all queens are placed
then return true */
if (col >= N)
return true;
/* Consider this column and try placing
this queen in all rows one by one */
for (let i = 0; i < N; i++) {
/* Check if the queen can be placed on
board[i][col] */
place(i,col);
setTimeout(function (){
if (isSafe(board, i, col)) {
/* Place this queen in board[i][col] */
board[i][col] = 1;
/* recur to place rest of the queens */
if (solveNQUtil(board, col + 1) == true) {
return true;
}
/* If placing queen in board[i][col]
doesn't lead to a solution then
remove queen from board[i][col] */
remove(i,col);
board[i][col] = 0; // BACKTRACK
}
else{
remove(i,col);
}
}, 1000);
}
/* If the queen can not be placed in any row in
this colum col, then return false */
return false;
}
function place(i,col){
let id="chest"+i+""+col;
document.getElementById(id).innerHTML ="♕"
}
function remove(i,col){
let id="chest"+i+""+col;
document.getElementById(id).innerHTML=""
}
The function place and remove show the queens on the table at that position( AND they works fine)
If i remove timeOut, i see only the final solution but not the resolution;
I think the easiest is to make your function an async function, and then use await to wait for a promise to resolve (based on setTimeout). You would then also need to await your recursive calls, since the function will now return a promise:
let delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function solveNQUtil(table, board, col) {
if (col >= board.length) {
return true;
}
for (let i = 0; i < board.length; i++) {
place(table, i, col);
await delay(100);
if (isSafe(board, i, col)) {
board[i][col] = 1;
if (await solveNQUtil(table, board, col + 1)) {
return true;
}
board[i][col] = 0;
}
remove(table, i, col);
}
return false;
}
function place(table, i, col){
table.rows[i].cells[col].innerHTML = "♕"
}
function remove(table, i, col){
table.rows[i].cells[col].innerHTML = ""
}
function isSafe(board, i, col) {
return !board[i].includes(1) &&
!board.some((row, j) => row[col - Math.abs(j-i)] == 1);
}
function fillHtmlTable(table, n) {
for (let row = 0; row < n; row++) {
let tr = table.insertRow();
for (let col = 0; col < n; col++) {
tr.insertCell();
}
}
return table;
}
function createBoard(length) {
return Array.from({length}, () => Array(length).fill(0));
}
// demo
let n = 8;
let table = fillHtmlTable(document.querySelector("table"), n);
solveNQUtil(table, createBoard(n), 0).then(success => {
if (success) {
table.classList.toggle("success");
} else {
console.log("NO SOLUTION");
}
});
table { border-collapse: collapse; background-color: #eee }
tr:nth-child(even) td:nth-child(odd),
tr:nth-child(odd) td:nth-child(even) { background-color: #ccc }
td { width: 20px; height: 20px; text-align: center; font-size: 15px }
.success { color: green; font-weight: bold }
<table></table>
Sorry for the long question.
I have tried to create a meetings on a calendar for a day. I need help to take care of the overlapping intervals.
The code I have written in following :
HTML
<body>
<div id="timeline"></div>
<div id="calendar" class="calendar">
</div>
</body>
CSS
.calendar {
border: 1px solid black;
position: absolute;
width: 600px;
height: 1440px;
left: 60px;
}
.event {
position: absolute;
float: left;
width: 100%;
overflow: auto;
border: 0px solid red;
}
#timeline {
position: absolute;
float: left;
}
JS
function getRandomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function creatTimeline(tl) {
var i = 0;
while (i < tl.length) {
var divEl = document.createElement('div');
divEl.style.width = '50px';
divEl.style.height = '120px';
divEl.style.border = '0px solid yellow';
divEl.innerHTML = tl[i];
var timeLine = document.getElementById('timeline');
timeLine.appendChild(divEl);
i++;
}
}
function appendEventDivs(eventArr) {
var i = 0;
while (i < eventArr.length) {
var eventEl = document.createElement('div');
eventEl.className = 'event';
eventEl.style.height = eventArr[i].height;
eventEl.style.top = eventArr[i].top;
eventEl.style.background = eventArr[i].color;
eventEl.style.width = eventArr[i].width;
eventEl.style.left = eventArr[i].left;
eventEl.innerHTML = 'Meeting' + eventArr[i].id;
var cl = document.getElementById('calendar');
cl.appendChild(eventEl);
i++;
}
}
function collidesWith(a, b) {
return a.end > b.start && a.start < b.end;
}
function checkCollision(eventArr) {
for (var i = 0; i < eventArr.length; i++) {
eventArr[i].cols = [];
for (var j = 0; j < eventArr.length; j++) {
if (collidesWith(eventArr[i], eventArr[j])) {
eventArr[i].cols.push(i);
}
}
}
return eventArr;
}
function updateEvents(eventArr) {
eventArr = checkCollision(eventArr);
var arr = [];
arr = eventArr.map(function(el) {
//just to differentiate each event with different colours
el.color = getRandomColor();
el.height = (el.end - el.start) * 2 + 'px';
el.top = (el.start) * 2 + 'px';
el.width = (600 / el.cols.length) + 'px';
return el;
});
return arr;
}
var events = [{
id: 123,
start: 60,
end: 150
}, {
id: 124,
start: 540,
end: 570
}, {
id: 125,
start: 555,
end: 600
}, {
id: 126,
start: 585,
end: 660
}];
var timeline = ['9AM', '10AM', '11AM', '12Noon', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM', '7PM', '8PM', '9PM'];
function getEvents (eventArr) {
eventArr.sort(function(a, b) {
return a.start - b.start;
});
eventArr = updateEvents(eventArr);
appendEventDivs(eventArr);
console.log(eventArr);
//PART 1 - function returning the eventArr with all the required attributes
return eventArr;
};
creatTimeline(timeline);
getEvents(events);
Working fiddle here
Can anybody guide me how to take care of the overlapping intervals so that they appear side-by-side and not on top of each other.
Thanks in advance.
You need to figure out in which column each of the events should be before you can determine their width or left-position. To do this, you need to also store which of the colliding events came before each event:
function checkCollision(eventArr) {
for (var i = 0; i < eventArr.length; i++) {
eventArr[i].cols = [];
eventArr[i].colsBefore=[];
for (var j = 0; j < eventArr.length; j++) {
if (collidesWith(eventArr[i], eventArr[j])) {
eventArr[i].cols.push(j);
if(i>j) eventArr[i].colsBefore.push(j); //also list which of the conflicts came before
}
}
}
return eventArr;
}
Now, we can figure out the column of each event. Once we've done that, we can figure out how wide they should be, and with that, the horizontal positioning should be easy. This should be done inside your updateEvents function. I've got more detailed explanation commented in the comments of the code below.
function updateEvents(eventArr) {
eventArr = checkCollision(eventArr);
var arr=eventArr.slice(0); //clone the array
for(var i=0; i<arr.length; i++){
var el=arr[i];
el.color = getRandomColor();
el.height = (el.end - el.start) * 2 + 'px';
el.top = (el.start) * 2 + 'px';
if(i>0 && el.colsBefore.length>0){ //check column if not the first event and the event has collisions with prior events
if(arr[i-1].column>0){ //if previous event wasn't in the first column, there may be space to the left of it
for(var j=0;j<arr[i-1].column;j++){ //look through all the columns to the left of the previous event
if(el.colsBefore.indexOf(i-(j+2))===-1){ //the current event doesn't collide with the event being checked...
el.column=arr[i-(j+2)].column; //...and can be put in the same column as it
}
}
if(typeof el.column==='undefined') el.column=arr[i-1].column+1; //if there wasn't any free space, but it ito the right of the previous event
}else{
var column=0;
for(var j=0;j<el.colsBefore.length;j++){ //go through each column to see where's space...
if(arr[el.colsBefore[el.colsBefore.length-1-j]].column==column) column++;
}
el.column=column;
}
}else el.column=0;
}
//We need the column for every event before we can determine the appropriate width and left-position, so this is in a different for-loop:
for(var i=0; i<arr.length; i++){
arr[i].totalColumns=0;
if(arr[i].cols.length>1){ //if event collides
var conflictGroup=[]; //store here each column in the current event group
var conflictingColumns=[]; //and here the column of each of the events in the group
addConflictsToGroup(arr[i]);
function addConflictsToGroup(a){
for(k=0;k<a.cols.length;k++){
if(conflictGroup.indexOf(a.cols[k])===-1){ //don't add same event twice to avoid infinite loop
conflictGroup.push(a.cols[k]);
conflictingColumns.push(arr[a.cols[k]].column);
addConflictsToGroup(arr[a.cols[k]]); //check also the events this event conflicts with
}
}
}
arr[i].totalColumns=Math.max.apply(null, conflictingColumns); //set the greatest value as number of columns
}
arr[i].width=(600/(arr[i].totalColumns+1))+'px';
arr[i].left=(600/(arr[i].totalColumns+1)*arr[i].column)+'px';
}
return arr;
}
Working Fiddle: https://jsfiddle.net/ilpo/ftbjan06/5/
I added a few other events to test different scenarios.
Oh, and by the way, absolutely positioned elements can't float.
You already know the top and height of every event, so you could map the calendar and check an event already exist within the area it will occupy, then offset the left value by the number of existing events.
I'm making a small exercise for some students of mine where I am automating a kind of 10 pin bowling game I have put it into a JsBin here https://jsbin.com/qilizo/edit?html,js,output. I don't know whether I am tired, stupid or it's just because I am working on a national holiday but something has me puzzled. When i start the game I prompt the user to set up a number of desired players. This automatically produces an object array of Players like so:
[{score: Array[10], spareCount: 0, strikeCount: 0, username: "Player 1"}, ...]
Now later I allow the user to play frames where each Player in our array has two throws... I collect the score and add it to the certain player's score array. However when I try to perform this action using a .forEach method the score I generate is applied to all items in my Players array (play the game and see). I have put my code in a jsBin and the problem is on line 109 : a.score[currentFrame - 1] = playFrame();
I have tried to amend my code but I can't work out why the current (or last) frame score is being applied to all Player objects! If you can understand my syntax error and explain why I would be most appreciative. Play the game (just click the button after setting the player numbers) and you will see what I mean...
Snippet:
var players,
currentFrame = 0,
currentThrow = 0;
// helper functions
// Accurate isNumber function... Thank you Crockford (see JavaScript: The Good Parts)
function isNumber(value) {
return typeof(value === 'number') && isFinite(value);
}
function frameStyle(k) {
var returnCssClass,
k = k + 1;
if (k < currentFrame) {
returnCssClass = 'played-frame';
} else if (k === currentFrame) {
returnCssClass = 'current-frame';
} else {
returnCssClass = null;
}
return returnCssClass;
}
function setUpPlayers(num) {
var tempArray = [],
tempName = 'Player ',
emptyScores = Array(10).fill([-1, -1]); // set default to -1 as a rubbish player may hit no pins!
for (var i = 0; i < num; i++) {
tempArray.push({
username: tempName + (i + 1),
score: emptyScores,
strikeCount: 0,
spareCount: 0
}); // the way I have named the tempName is technically an antipattern!
}
return tempArray;
}
function getTotals(scores) {
var totalScore = scores.reduce(function(a, b) {
return a + b.reduce(function(c, d) {
return (c + (c + ((d > 0) ? d : 0)));
}, 0);
}, 0);
return totalScore;
}
function displayScore(score) {
// toDo reformat!
var formatScore = score.map(function(a, b) {
if (a === -1) {
a = '-';
} else if (a === 10) {
a = 'X';
}
return a;
});
return formatScore;
}
function createGrid() {
// If only I was using ES6 I could have multi line support!
var playerLen = players.length,
scoresLen = players[0].score.length;
boards = '<div class="score-board">' +
'<!-- one row for each player -->';
// need to loop this through the players...
for (var i = 0; i < playerLen; i++) {
boards += '<div class="row">' +
'<!-- first cell is the name -->' +
'<div class="name">' + players[i].username + '</div>';
// need to loop this with the users scores
for (var k = 0; k < scoresLen; k++) {
boards += '<div class="game ' + frameStyle(k) + ' ">' + displayScore(players[i].score[k]) + '</div>';
}
// don't forget the total
boards += '<div class="player-total">' + getTotals(players[i].score) + '</div>';
boards += '</div>';
}
boards += '</div>';
boards += '<div>Current Frame: ' + currentFrame + '</div>';
boards += '<button type="button" onclick="startGame()">Start Game</button>';
// fill the holder....
document.getElementById('boardHolder').innerHTML = boards;
}
function startGame() {
if (currentFrame >= 10) {
announceWinner();
} else {
currentFrame++;
// do the throws for Each Player!
players.forEach(function(a, b) {
a.score[currentFrame - 1] = playFrame();
});
// update the grid
createGrid();
// recurrrrrrsion....
//startGame();
}
}
function throwBall(pinsStanding) {
// i know it isn't a ball
return Math.floor(Math.random() * (pinsStanding + 1));
}
function playFrame() {
// here we just create the array and determine if we have a strike or a spare!
var pinsStanding = 10,
frameScore = [],
frameThrows = 2,
pinsDown;
for(var i = 0; i < frameThrows; i++) {
pinsDown = throwBall(pinsStanding);
pinsStanding = pinsStanding - pinsDown;
// if it is the pinsStanding = 0 and it is the first throw - a strike!
if(pinsStanding === 0 && i === 1) {
pinsStanding = 10;
frameThrows = 3;
}
// what if it is a spare?
frameScore.push(pinsDown);
}
return frameScore;
}
function announceWinner() {
}
// kick it all off!!!
window.onload = function() {
// lets get some users....
players = prompt('Please enter the NUMBER of players?', 2);
// check we have a number...
if (isNumber(players)) {
players = setUpPlayers(players);
createGrid();
}
};
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
/* classes */
.score-board {
border: 1px solid #000;
}
.row {
display: block;
border-bottom: 1px solid #000;
}
.row:last-child {
border-bottom: none;
}
.row > div {
display: inline-block;
padding: 5px;
}
.game {
border-right: 1px solid #000;
}
.name {
background-color: #f5f5f5;
border-right: 1px solid #000;
}
.player-total {
text-align: right;
background-color: #d5eabb;
}
.played-frame {
background-color: #aee1e8;
}
.current-frame {
background-color: #ffc0cb;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<h1>Let's go bowling!</h1>
<div id="boardHolder">
</div>
</body>
</html>
Here is the bin!
https://jsbin.com/qilizo/edit?html,js,output
You need to call Array(10).fill([-1, -1]) inside for loop, because otherwise all objects will share the same score array:
function setUpPlayers(num) {
var tempArray = [],
tempName = 'Player ';
for (var i = 0; i < num; i++) {
tempArray.push({
username: tempName + (i + 1),
score: Array(10).fill([-1, -1]),// set default to -1 as a rubbish player may hit no pins!
strikeCount: 0,
spareCount: 0
}); // the way I have named the tempName is technically an antipattern!
}
return tempArray;
}
https://jsbin.com/yeyupiteyu/1/edit?html,js,output
In JavaScript objects are passed by reference, and since array is an object, if you declare emptyScores outside the loop and then assign it to every element of the array, all elements will share the same score array.
You have make new emptyScores array for each element, so you have to declare it inside the loop:
var tempArray = [],
tempName = 'Player ';
for (var i = 0; i < num; i++) {
var emptyScores = Array(10).fill([-1, -1]);
tempArray.push({
username: tempName + (i + 1),
score: emptyScores,
strikeCount: 0,
spareCount: 0
});
}