JavaScript Minesweeper - Opening whole mine-free area at once not working properly (flood-fill) - javascript

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

A customized swap fails in bubbleSort

I wrote such a swap function for bubbleSort:
function swap(a, b) {
tmp = a;
a = b;
b = tmp;
}
then encapsulate it in the bubbleSort:
function bubbleSort(arr) {
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - 1; j++) {
// swap the elements
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]); // use swap here.
}
}
}
}
And test it with:
arr = [34, 87, 21, 0, -11];
bubbleSort(arr)
but it stay unchanged, what's the problem?
The swap function takes a copy of the number instead of the pointer.. welcome to javascript?
Either way, you would need the array and the indexes to do a swap because objects are pointers, numbers are not
Here's an example
//working swap function
function swap(arr, a, b){
var tmp=arr[a]
arr[a]=b
arr[b]=tmp
}
function bubbleSort(arr) {
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - 1; j++) {
// swap the elements
if (arr[j] > arr[j + 1]) {
swap(arr, j, j+1); // use swap here.
}
}
}
return arr //because why not
}
arr = [34, 87, 21, 0, -11];
console.log(bubbleSort(arr))
my 2 cents...
function bubbleSort(arr) // original algorythm
{
for ( let i=arr.length ; --i > 0 ; )
for ( let j=0, k=1 ; j < i ; j++, k++ )
if (arr[j] > arr[k])
[arr[j],arr[k]] = [arr[k],arr[j]];
}
const myArr = [33, 103, 3, 726, 200, 984, 198, 764, 9];
bubbleSort(myArr)
document.write( JSON.stringify(myArr) )
function bubbleSort(arr) // optimised algorythm
{
for (let i=arr.length, noSwap; --i>0;) // go reverse, important!
{
noSwap = true
for (let j=0, k=1; j<i; j++,k++)
if (arr[j] > arr[k])
{
[arr[j],arr[k]] = [arr[k],arr[j]];
noSwap = false
}
if (noSwap) break
} }
For those who wonder why in the BubbleSort algorithm the first index must necessarily start from the right to go up to the left
while the following clues go in the other direction, I made this animation to visualize its logic.
the idea of Bubble Sort is to push the highest value to the right, much like air bubbles in a liquid.
this code also makes it possible to realize why the performance of this type of sorting is poor, unless, by chance, the elements are close to the correct order (which is still hazardous).
const
btSort = document.querySelector('#bt-sort')
, btGener = document.querySelector('#bt-gen')
, movinBars = document.querySelector('#moving-bars')
, arrLen = document.querySelector('#arr-len')
, delay = ms => new Promise( r => setTimeout(r, ms))
;
// input range HTML system to get future length of the arr to sort
arrLen.value = arrLen.closest('label').dataset.val;
arrLen.oninput =_=>{ arrLen.closest('label').dataset.val = arrLen.value }
const
arrBars = (()=> // => { set size, setBarLeft(), setBarClass() }
{
let
len = 0
, Keys = [] // array keys on Bars
, Bars = [] // Bars elements
;
const
shuffleKeys =_=>
{
let X = len, Y;
while (X)
{
Y = (Math.random() * X--) | 0;
[Keys[X], Keys[Y]] = [ Keys[Y], Keys[X]];
}
}
, genBars =_=>
{
let xHeight= 0, szMin = (240/len)|0, szMax = (53.7143 -(len*(4/7)))|0;
let hg = [];
Bars = new Array(len);
for (let i=0;i<len;i++)
{
xHeight += szMin + ((Math.random() *szMax)|0);
hg.push( xHeight )
}
for (let i=0;i<len;i++)
{
Bars[i] = document.createElement('div')
Bars[i].style.height = `${hg[Keys[i]]}px`
Bars[i].style.left = `${i *17}px`
movinBars.appendChild(Bars[i])
}
}
, Obj =
{ set size( length )
{
if ( len !== length ) { Keys = [...Array(length).keys()] };
for( let i=0;i<len;i++) { delete Bars[i]; };
while (movinBars.firstChild) { movinBars.removeChild(movinBars.lastChild) };
len = length;
shuffleKeys();
genBars();
}
, bubbleSort: function* () // optimuzed version
{
let noSwap;
for (let i=len; --i>0 ;)
{
noSwap = true;
for (let j=0, k=1, j$=Keys[j], k$=Keys[k]; j<i; j$=Keys[++j], k$=Keys[++k] )
{
if ( j$>k$ )
{
Keys[j] = k$;
Keys[k] = j$;
noSwap = false;
yield ([ j, k, true ]);
}
else
yield ([ j, k, false ]);
}
if (noSwap) break;
}
}
, setBarLeft: (indx, vLeft ) => new Promise( r =>
{
let bar = Bars[indx];
bar.ontransitionend =_=>
{
bar.ontransitionend = null
r()
}
bar.style.left = `${vLeft *17}px`
})
, setBarsClass( x, y, onOff, doSwapp )
{
Bars[x].classList.toggle('Mov1',onOff)
Bars[y].classList.toggle('Mov2',onOff)
if (!onOff && doSwapp)
{
Bars[x].classList.remove('Mov1')
Bars[y].classList.remove('Mov2')
let hg_x = Bars[x].style.height
, lf_x = Bars[x].style.left
;
Bars[x].style.height = Bars[y].style.height
Bars[x].style.left = Bars[y].style.left
Bars[y].style.height = hg_x
Bars[y].style.left = lf_x
}
}
}
;
return Obj;
})();
/***************************** main part ***********/
btSort.disabled = true;
btGener.onclick =_=>
{
btGener.disabled = true;
arrLen.disabled = true;
arrBars.size = arrLen.valueAsNumber;
btSort.disabled = false;
}
btSort.onclick = async () =>
{
btSort.disabled = true;
for await (let [ x,y, doSwapp ] of arrBars.bubbleSort())
{
arrBars.setBarsClass( x, y, true, doSwapp )
if (doSwapp)
{
await arrBars.setBarLeft( x, y )
await arrBars.setBarLeft( y, x )
}
else
await delay(80);
arrBars.setBarsClass( x, y, false, doSwapp )
await delay(90)
}
btGener.disabled = false;
arrLen.disabled = false;
}
body {
background : #14141f;
margin : 0;
font-family : Arial, Helvetica, sans-serif;
font-size : 18px;
}
header {
background : #2d2d47;
padding : 1.3rem 0;
}
button {
border : 3px solid #29293a;
border-radius : .8rem;
background-color : #5b5b8d;
color : black;
font-size : 1.2rem;
width : 7.6rem;
vertical-align : middle;
margin : 0 0 0 1.5rem;
}
button:not(:disabled):hover {
background-color : #67678a;
color : whitesmoke;
cursor : pointer;
}
button:disabled {
background-color : #5b5b8d;
border-color : darkslategrey;
color : #29293a;
}
#moving-bars {
position : relative;
margin : 20px 0 0 20px;
}
#moving-bars > div {
position : absolute;
background : #3e3e50;
width : 10px;
top : 0;
}
#moving-bars > div.Mov1 {
transition : left .3s ease-in-out;
background : crimson;
}
#moving-bars > div.Mov2 {
transition : left .3s ease-in-out;
background : #ffff0088;
}
/******************************/
label.rg {
display : inline-block;
height : 1.8rem;
padding : .3rem .5rem 0 1rem;
color : black;
}
label.rg input {
vertical-align : middle;
width : 18rem;
}
label.rg::before {
display : inline-block;
content : attr(data-lib);
font-weight : bold;
color : #5f5fc2;
}
label.rg::after {
content : attr(data-val);
display : inline-block;
font-size : 1.2rem;
vertical-align : middle;
border : 1px solid #5b5b8d;
width : 2.8rem;
text-align : center;
border-radius : .6rem;
padding : .2rem 0;
margin-left : .2rem;
color : #5f5fc2;
}
<header>
<button id="bt-sort" > Sort </button>
<button id="bt-gen" > Generate </button>
<label class="rg" data-val="12" data-lib="Len: "> <input type="range" id="arr-len" min="9" max="40" step="1" ></label>
</header>
<div id="moving-bars"></div>

How to display XML content in HTML file with Javascript

This is my xml code . I want to display the contents in a html page with Javascript.
<businesses>
<business bfsId="" id="41481">
<advertHeader>Welding Supplies, Equipment and Service Business</advertHeader>
<Price>265000</Price>
<catalogueDescription>Extremely profitable (Sales £500k, GP £182k) business</catalogueDescription>
<keyfeature1>
Well established 25 year business with excellent trading record
</keyfeature1>
<keyfeature2>
Consistently high levels of turnover and profitability over last 5 years
</keyfeature2>
</business>
<business bfsId="" id="42701">
<broker bfsRef="1771" ref="003">Birmingham South, Wolverhampton & West Midlands</broker>
<tenure>freehold</tenure>
<advertHeader>Prestigious Serviced Office Business</advertHeader>
<Price>1200000</Price>
<reasonForSale>This is a genuine retirement sale.</reasonForSale>
<turnoverperiod>Annual</turnoverperiod>
<established>28</established>
<catalogueDescription>This well-located and long-established serviced office</catalogueDescription>
<underOffer>No</underOffer>
<image1>https://www.business-partnership.com/uploads/business/businessimg15977.jpg</image1>
<keyfeature1>other connections</keyfeature1>
<keyfeature2> Investment Opportunity</keyfeature2>
<keyfeature3>Over 6,000 sq.ft.</keyfeature3>
<keyfeature4>Well-maintained </keyfeature4>
<keyfeature5>In-house services & IT provided</keyfeature5>
</business>
</businesses>
This is the original xml file https://alpha.business-sale.com/bfs.xml I have just took a short portion to describe the situation.
Requirements
Print a row for every <business> element
For every <business> pick some specific child element and print column only for those element.( Not all ). For an example in this case I only want to print the value for <advertHeader> ; <Price> and <description> and want to ignore other elements.
Only print the row those <business> where value of <Price> is > 10000 . if it is then less than 10000 do not print that row
pagination after every 10 row
This is the html table
<table id="MainTable"><tbody id="BodyRows"></tbody></table>
And this is the javascript code that i have tried .
window.addEventListener("load", function() {
getRows();
});
function getRows() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("get", "2l.xml", true);
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
showResult(this);
}
};
xmlhttp.send(null);
}
function showResult(xmlhttp) {
var xmlDoc = xmlhttp.responseXML.documentElement;
removeWhitespace(xmlDoc);
var outputResult = document.getElementById("BodyRows");
var rowData = xmlDoc.getElementsByTagName("business");
addTableRowsFromXmlDoc(rowData,outputResult);
}
function addTableRowsFromXmlDoc(xmlNodes,tableNode) {
var theTable = tableNode.parentNode;
var newRow, newCell, i;
console.log ("Number of nodes: " + xmlNodes.length);
for (i=0; i<xmlNodes.length; i++) {
newRow = tableNode.insertRow(i);
newRow.className = (i%2) ? "OddRow" : "EvenRow";
for (j=0; j<xmlNodes[i].childNodes.length; j++) {
newCell = newRow.insertCell(newRow.cells.length);
if (xmlNodes[i].childNodes[j].firstChild) {
newCell.innerHTML = xmlNodes[i].childNodes[j].firstChild.nodeValue;
} else {
newCell.innerHTML = "-";
}
console.log("cell: " + newCell);
}
}
theTable.appendChild(tableNode);
}
function removeWhitespace(xml) {
var loopIndex;
for (loopIndex = 0; loopIndex < xml.childNodes.length; loopIndex++)
{
var currentNode = xml.childNodes[loopIndex];
if (currentNode.nodeType == 1)
{
removeWhitespace(currentNode);
}
if (!(/\S/.test(currentNode.nodeValue)) && (currentNode.nodeType == 3))
{
xml.removeChild(xml.childNodes[loopIndex--]);
}
}
}
But this code prints columns for all the nodes under <business> element. And the number of child elements under <business> are different . So the result comes like this
I dont want that. I want to only display the value of specific nodes under <business> element (in this case only include <advertHeader> ; <Price> and <description> ) so that the number of columns are equal in every row. How to do that?
Try finding the <business>-element with the most values and build your table around that. Here's an example snippet that does that for the data you presented.
{
const xml = new DOMParser()
.parseFromString(getData(), `text/xml`);
// the <business>-elements from xml
const businessElems = [...xml.querySelectorAll(`business`)];
// the nodeNames will be the header. While we're at it,
// we can count the number of headers (len) for later use
const headersFromXml = businessElems.map( v =>
[...v.querySelectorAll(`*`)]
.map( v => v.nodeName) )
.map( v => ( {len: v.length, headers: v} )
);
// now determine the longest header using a reducer
const businessElemWithMostNodes = headersFromXml
.reduce( (acc, v) => v.len > acc.len ? v : acc, {len: 0});
// utility to create a tablecell/header and append it to a row
const createCell = (rowToAppendTo, cellType, value) => {
const cell = document.createElement(cellType);
cell.innerHTML = value;
rowToAppendTo.appendChild(cell);
}
// utility to create a datarow and append it to a table
const createDataRow = (tableToAppendTo, businessElem) => {
const row = document.createElement(`tr`);
const rowValues = [];
// create values using the businessElemWithMostNodes order
businessElemWithMostNodes.headers
.forEach( head => {
const valueElem = businessElem.querySelector(`${head}`);
rowValues.push(valueElem ? valueElem.innerHTML : `-`);
});
rowValues.forEach( v => createCell(row, `td`, v) );
tableToAppendTo.appendChild(row);
};
// now we know enough to create the table
const table = document.createElement(`table`);
// the headerRow first
const headRow = document.createElement(`tr`);
businessElemWithMostNodes.headers.forEach( hv => createCell(headRow, `th`, hv) );
table.appendChild(headRow);
// next create and append the rows
businessElems.forEach(be => createDataRow(table, be));
// finally, append the table to document.body
document.body.appendChild(table);
// your xml string
function getData() {
return `
<businesses>
<business bfsId="" id="41481">
<advertHeader>Welding Supplies, Equipment and Service Business</advertHeader>
<Price>265000</Price>
<catalogueDescription>Extremely profitable (Sales £500k, GP £182k) business</catalogueDescription>
<keyfeature1>
Well established 25 year business with excellent trading record
</keyfeature1>
<keyfeature2>
Consistently high levels of turnover and profitability over last 5 years
</keyfeature2>
</business>
<business bfsId="" id="42701">
<broker bfsRef="1771" ref="003">Birmingham South, Wolverhampton & West Midlands</broker>
<tenure>freehold</tenure>
<advertHeader>Prestigious Serviced Office Business</advertHeader>
<Price>1200000</Price>
<reasonForSale>This is a genuine retirement sale.</reasonForSale>
<turnoverperiod>Annual</turnoverperiod>
<established>28</established>
<catalogueDescription>This well-located and long-established serviced office</catalogueDescription>
<underOffer>No</underOffer>
<image1>https://www.business-partnership.com/uploads/business/businessimg15977.jpg</image1>
<keyfeature1>other connections</keyfeature1>
<keyfeature2> Investment Opportunity</keyfeature2>
<keyfeature3>Over 6,000 sq.ft.</keyfeature3>
<keyfeature4>Well-maintained </keyfeature4>
<keyfeature5>In-house services & IT provided</keyfeature5>
</business>
</businesses>`;
}
}
body {
margin: 1rem;
font: 12px/15px normal consolas, verdana, arial;
}
.hidden {
display: none;
}
th {
background-color: black;
color: white;
border: 1px solid transparent;
padding: 2px;
text-align: left;
}
td {
border: 1px solid #c0c0c0;
padding: 2px;
vertical-align: top;
}

Checking function for sliding puzzle javascript

I created a sliding puzzle with different formats like: 3x3, 3x4, 4x3 and 4x4. When you run my code you can see on the right side a selection box where you can choose the 4 formats. The slidingpuzzle is almost done. But I need a function which checks after every move if the puzzle is solved and if that is the case it should give out a line like "Congrantulations you solved it!" or "You won!". Any idea how to make that work?
In the javascript code you can see the first function loadFunc() is to replace every piece with the blank one and the functions after that are to select a format and change the format into it. The function Shiftpuzzlepieces makes it so that you can move each piece into the blank space. Function shuffle randomizes every pieces position. If you have any more question or understanding issues just feel free to ask in the comments. Many thanks in advance.
Since I don't have enough reputation I will post a link to the images here: http://imgur.com/a/2nMlt . These images are just placeholders right now.
Here is the jsfiddle:
http://jsfiddle.net/Cuttingtheaces/vkyxgwo6/19/
As always, there is a "hacky", easy way to do this, and then there is more elegant but one that requires significant changes to your code.
Hacky way
To accomplish this as fast and dirty as possible, I would go with parsing id-s of pieces to check if they are in correct order, because they have this handy pattern "position" + it's expected index or "blank":
function isFinished() {
var puzzleEl = document.getElementById('slidingpuzzleContainer').children[0];
// convert a live list of child elements into regular array
var pieces = [].slice.call(puzzleEl.children);
return pieces
.map(function (piece) {
return piece.id.substr(8); // strip "position" prefix
})
.every(function (id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == parseInt(id);
});
}
Now we need to check it somewhere, and naturally the best place would be after each move, so shiftPuzzlepieces() should be updated to call isFinished() function, and show the finishing message if it returns true:
function shiftPuzzlepieces(el) {
// ...
if (isFinished()) {
alert("You won!");
}
}
And voilà: live version.
How would I implement this game
For me, the proper way of implementing this would be to track current positions of pieces in some data structure and check it in similar way, but without traversing DOM or checking node's id-s. Also, it would allow to implement something like React.js application: onclick handler would mutate current game's state and then just render it into the DOM.
Here how I would implement the game:
/**
* Provides an initial state of the game
* with default size 4x4
*/
function initialState() {
return {
x: 4,
y: 4,
started: false,
finished: false
};
}
/**
* Inits a game
*/
function initGame() {
var gameContainer = document.querySelector("#slidingpuzzleContainer");
var gameState = initialState();
initFormatControl(gameContainer, gameState);
initGameControls(gameContainer, gameState);
// kick-off rendering
render(gameContainer, gameState);
}
/**
* Handles clicks on the container element
*/
function initGameControls(gameContainer, gameState) {
gameContainer.addEventListener("click", function hanldeClick(event) {
if (!gameState.started || gameState.finished) {
// game didn't started yet or already finished, ignore clicks
return;
}
if (event.target.className.indexOf("piece") == -1) {
// click somewhere not on the piece (like, margins between them)
return;
}
// try to move piece somewhere
movePiece(gameState, parseInt(event.target.dataset.index));
// check if we're done here
checkFinish(gameState);
// render the state of game
render(gameContainer, gameState);
event.stopPropagation();
return false;
});
}
/**
* Checks whether game is finished
*/
function checkFinish(gameState) {
gameState.finished = gameState.pieces.every(function(id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == id;
});
}
/**
* Moves target piece around if there's blank somewhere near it
*/
function movePiece(gameState, targetIndex) {
if (isBlank(targetIndex)) {
// ignore clicks on the "blank" piece
return;
}
var blankPiece = findBlankAround();
if (blankPiece == null) {
// nowhere to go :(
return;
}
swap(targetIndex, blankPiece);
function findBlankAround() {
var up = targetIndex - gameState.x;
if (targetIndex >= gameState.x && isBlank(up)) {
return up;
}
var down = targetIndex + gameState.x;
if (targetIndex < ((gameState.y - 1) * gameState.x) && isBlank(down)) {
return down;
}
var left = targetIndex - 1;
if ((targetIndex % gameState.x) > 0 && isBlank(left)) {
return left;
}
var right = targetIndex + 1;
if ((targetIndex % gameState.x) < (gameState.x - 1) && isBlank(right)) {
return right;
}
}
function isBlank(index) {
return gameState.pieces[index] == "blank";
}
function swap(i1, i2) {
var t = gameState.pieces[i1];
gameState.pieces[i1] = gameState.pieces[i2];
gameState.pieces[i2] = t;
}
}
/**
* Handles form for selecting and starting the game
*/
function initFormatControl(gameContainer, state) {
var formatContainer = document.querySelector("#formatContainer");
var formatSelect = formatContainer.querySelector("select");
var formatApply = formatContainer.querySelector("button");
formatSelect.addEventListener("change", function(event) {
formatApply.disabled = false;
});
formatContainer.addEventListener("submit", function(event) {
var rawValue = event.target.format.value;
var value = rawValue.split("x");
// update state
state.x = parseInt(value[0], 10);
state.y = parseInt(value[1], 10);
state.started = true;
state.pieces = generatePuzzle(state.x * state.y);
// render game
render(gameContainer, state);
event.preventDefault();
return false;
});
}
/**
* Renders game's state into container element
*/
function render(container, state) {
var numberOfPieces = state.x * state.y;
updateClass(container, state.x, state.y);
clear(container);
var containerHTML = "";
if (!state.started) {
for (var i = 0; i < numberOfPieces; i++) {
containerHTML += renderPiece("", i) + "\n";
}
} else if (state.finished) {
containerHTML = "<div class='congratulation'><h2 >You won!</h2><p>Press 'Play!' to start again.</p></div>";
} else {
containerHTML = state.pieces.map(renderPiece).join("\n");
}
container.innerHTML = containerHTML;
function renderPiece(id, index) {
return "<div class='piece' data-index='" + index + "'>" + id + "</div>";
}
function updateClass(container, x, y) {
container.className = "slidingpuzzleContainer" + x + "x" + y;
}
function clear(container) {
container.innerHTML = "";
}
}
/**
* Generates a shuffled array of id-s ready to be rendered
*/
function generatePuzzle(n) {
var pieces = ["blank"];
for (var i = 0; i < n - 1; i++) {
pieces.push(i);
}
return shuffleArray(pieces);
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
}
body {
font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Helvetica, Arial, sans-serif;
font-size: 12px;
color: #000;
}
#formatContainer {
position: absolute;
top: 50px;
left: 500px;
}
#formatContainer label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
}
#formatContainer select {
display: block;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
#formatContainer button {
display: inline-block;
width: 100%;
}
.piece {
width: 96px;
height: 96px;
margin: 1px;
float: left;
border: 1px solid black;
}
.slidingpuzzleContainer3x3,
.slidingpuzzleContainer3x4,
.slidingpuzzleContainer4x3,
.slidingpuzzleContainer4x4 {
position: absolute;
top: 50px;
left: 50px;
border: 10px solid black;
}
.slidingpuzzleContainer3x3 {
width: 300px;
height: 300px;
}
.slidingpuzzleContainer3x4 {
width: 300px;
height: 400px;
}
.slidingpuzzleContainer4x3 {
width: 400px;
height: 300px;
}
.slidingpuzzleContainer4x4 {
width: 400px;
height: 400px;
}
.congratulation {
margin: 10px;
}
}
<body onload="initGame();">
<div id="slidingpuzzleContainer"></div>
<form id="formatContainer">
<label for="format">select format:</label>
<select name="format" id="format" size="1">
<option value="" selected="true" disabled="true"></option>
<option value="3x3">Format 3 x 3</option>
<option value="3x4">Format 3 x 4</option>
<option value="4x3">Format 4 x 3</option>
<option value="4x4">Format 4 x 4</option>
</select>
<button type="submit" disabled="true">Play!</button>
</form>
</body>
Here we have the initGame() function that starts everything. When called it will create an initial state of the game (we have default size and state properties to care about there), add listeners on the controls and call render() function with the current state.
initGameControls() sets up a listener for clicks on the field that will 1) call movePiece() which will try to move clicked piece on the blank spot if the former is somewhere around, 2) check if after move game is finished with checkFinish(), 3) call render() with updated state.
Now render() is a pretty simple function: it just gets the state and updates the DOM on the page accordingly.
Utility function initFormatControl() handles clicks and updates on the form for field size selection, and when the 'Play!' button is pressed will generate initial order of the pieces on the field and call render() with new state.
The main benefit of this approach is that almost all functions are decoupled from one another: you can tweak logic for finding blank space around target piece, to allow, for example, to swap pieces with adjacent ids, and even then functions for rendering, initialization and click handling will stay the same.
$(document).on('click','.puzzlepiece', function(){
var count = 0;
var imgarray = [];
var test =[0,1,2,3,4,5,6,7,8,'blank']
$('#slidingpuzzleContainer img').each(function(i){
var imgalt = $(this).attr('alt');
imgarray[i] = imgalt;
count++;
});
var is_same = (imgarray.length == test.length) && imgarray.every(function(element, index) {
return element === array2[index];
});
console.log(is_same); ///it will true if two array is same
});
try this... this is for only 3*3.. you pass the parameter and makethe array value as dynamically..

javascript game ( 3 in line ) line check logic

i've been in a battle to sort this problem since yesterday and i fear that i've gotten tunnel vision.
The game:
first player to make a line of 3 of a kind (xxx or 000) wins.
http://jsfiddle.net/brunobliss/YANAW/
The catch:
Only the first horizontal line is working!!! I can make it all work using a lot of IFS but repeating the same code over and over again is often a good indicator that i'm doing somethin wrong
The problem:
bruno.checkWin(); will check if there's a line or not, the guy who presented me this game chalenge told me that it is possible to check the lines with a for loop and that i should use it instead of IFS. I can't solve this without IFS unfortunately...
<!doctype html>
<html>
<head>
<meta charset="iso-8859-1">
<title> </title>
<style>
#jogo {
border: #000 1px solid;
width: 150px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -75px;
margin-top: -75px;
}
#jogo div {
display: inline-block;
vertical-align: top;
width: 28px;
height: 28px;
padding: 10px;
font-size: 20px;
border: #000 1px solid;
border-collapse: collapse;
text-align: center;
}
#reset {
font-family: Verdana;
width: 153px;
height: 30px;
background-color: black;
color: white;
text-align: center;
cursor: pointer;
left: 50%;
top: 50%;
position: absolute;
margin-left: -76px;
margin-top: 100px;
}
</style>
<script> </script>
</head>
<body>
<div id="jogo"> </div>
<div id="reset"> RESET </div>
<script>
var ultimo = "0";
var reset = document.getElementById('reset');
var jogo = document.getElementById('jogo');
var cell = jogo.getElementsByTagName('div');
var bruno = {
init: function () {
var jogo = document.getElementById('jogo');
for ( i = 0 ; i < 9 ; i++ ) {
var cell = document.createElement('div');
cell.onclick = function () {
// variavel publica dentro do obj?
ultimo = (ultimo == "x") ? 0 : "x";
this.innerHTML = ultimo;
bruno.checkWin();
};
jogo.appendChild(cell);
}
},
checkWin: function () {
var jogo = document.getElementById('jogo');
var cell = jogo.getElementsByTagName('div');
// as diagonais nao verificar por loop
for ( i = 0 ; i < cell.length ; i=i+4 ) {
switch(i) {
case 0:
if (cell[0].innerHTML != '') {
bruno.checkFirst();
}
case 4:
if (cell[4].innerHTML != '') {
bruno.checkFirst();
}
case 8:
if (cell[8].innerHTML != '') {
bruno.checkFirst();
}
}
/*
} else
if (i == 4 && cell[4].innerHTML != '') {
bruno.checkCenter();
} else
if (i == 8 && cell[8].innerHTML != '') {
bruno.checkLast();
}*/
}
},
reset: function () {
var jogo = document.getElementById('jogo');
var cell = jogo.getElementsByTagName('div');
for ( j = 0 ; j < cell.length ; j++ ) {
cell[j].innerHTML = "";
}
},
checkFirst: function () {
if (cell[0].innerHTML == cell[1].innerHTML && cell[1].innerHTML == cell[2].innerHTML) {
alert("linha horizontal");
return false;
} else
if (cell[0].innerHTML == cell[3].innerHTML && cell[3].innerHTML == cell[6].innerHTML) {
alert("linha vertical");
return false;
}
},
checkMiddle: function () {
// check vertical and horizontal lines from the center
},
checkLast: function () {
// check last horizontal and right edge vertical
}
};
window.onload = function () {
bruno.init();
};
reset.onclick = function () {
bruno.reset();
};
</script>
</body>
</html>
I came up with a more 'compact' version of your code. No switch statements. Have a look:
http://jsfiddle.net/YANAW/1/
Here's the code, for those who prefer to read it here. Important/updated functions are checkWin() and checkCells().
var bruno = {
init: function () {
var jogo = document.getElementById('jogo');
for ( i = 0 ; i < 9 ; i++ ) {
var cell = document.createElement('div');
cell.onclick = function () {
// variavel publica dentro do obj?
ultimo = (ultimo == "x") ? 0 : "x";
this.innerHTML = ultimo;
bruno.checkWin();
};
jogo.appendChild(cell);
}
},
checkWin: function () {
var jogo = document.getElementById('jogo');
var cells = jogo.getElementsByTagName('div');
// Scan through every cell
var numRows = 3;
var numColumns = 3;
for (var i = 0; i < cells.length; i++)
{
// Determine cell's position
var isHorizontalFirstCell = ((i % numColumns) === 0);
var isVerticalFirstCell = (i < numColumns);
var isTopLeftCorner = (i == 0);
var isTopRightCorner = (i == 2);
// Check for horizontal matches
if (isHorizontalFirstCell
&& bruno.checkCells(
cells, i,
(i + 3), 1))
{
alert('Horizontal');
}
// Check for vertical matches
if (isVerticalFirstCell
&& bruno.checkCells(
cells, i,
(i + 7), 3))
{
alert('Vertical');
}
// Check for diagonal matches
if (isTopLeftCorner
&& bruno.checkCells(
cells, i,
(i + 9), 4))
{
alert('Diagonal');
}
if (isTopRightCorner
&& bruno.checkCells(
cells, i,
(i + 5), 2))
{
alert('Diagonal');
}
}
},
reset: function () {
var jogo = document.getElementById('jogo');
var cell = jogo.getElementsByTagName('div');
for ( j = 0 ; j < cell.length ; j++ ) {
cell[j].innerHTML = "";
}
},
checkCells: function(cells, index, limit, step) {
var sequenceChar = null;
for (var i = index; i < limit; i += step)
{
// Return false immediately if one
// of the cells in the sequence is empty
if (!cells[i].innerHTML)
return false;
// If this is the first cell we're checking,
// store the character(s) it holds.
if (sequenceChar === null)
sequenceChar = cells[i].innerHTML;
// Otherwise, confirm that this cell holds
// the same character(s) as the previous cell(s).
else if (cells[i].innerHTML !== sequenceChar)
return false;
}
// If we reached this point, the entire sequence
// of cells hold the same character(s).
return true;
}
};

Wrapping Sentences within <p> Tags with <span>'s, But Keep Other Tags

To give you an idea of what I need, I have been using the below code to parse content within tags and wrap each sentence within tags so I can then interact with sentences on a page.
$('p').each(function() {
var sentences = $(this)
.text()
.replace(/(((?![.!?]['"]?\s).)*[.!?]['"]?)(\s|$)/g,
'<span class="sentence">$1</span>$3');
$(this).html(sentences);
});
However, the following line demonstrates my problem:
<p>This is a link and it is removed with the above code! Here is another sentence.</p>
Nested tags such as <a>, <img> etc...within <p> tags that I'm searching through are removed with the code that I'm using. I need to keep these tags intact, so the content stays the same within the <p> tags.
I need:
<p><span class="sentence">This is a link and it is removed with the above code!</sentence><sentence>Here is another sentence.</sentence></p>
After reading this barn-burner about parsing HTML with regex, I've concluded that I need to use a combo of an HTML parser of some sort to traverse through sub-tags within a <p> tag, and then use a regex to find the sentences. I think the regex I have listed above should work for most of my uses, if that helps.
So: how should I do it?
It is really difficult to tokenise language, reliably, into sentences and that is without the added complexity of throwing html into the equation. There are some applications etc out there that attempt to deal with Natural Language Processing, an example would be the Stanford Tokenizer with runs on Java (not Javascript)
And as people keep mentioning, a regex is not the solution to this problem, language is not regular so don't expect a Regular Expression only solution.
There is a question here on SO, Basic NLP in CoffeeScript or JavaScript — Punkt tokenizaton, simple trained Bayes models — where to start? Which I think summarises things fairly simply for Javascript.
Anyway, to at least give you a little something that you could play with, I knocked up a little code for you. This works reasonable well until the markup/language begins to resemble anything slightly complex or different, but ultimately fails the mark by a long way. But, it may be enough for what you need, I don't know.
CSS
.emphasis {
font-style: italic;
}
.bold {
font-weight: bold;
}
.emphasis.bold {
font-style: italic;
font-weight: bold;
}
.unidentified {
background-color: pink;
}
.sentence0 {
background-color: yellow;
}
.sentence1 {
background-color: green;
}
.sentence2 {
background-color: red;
}
.whitespace {
white-space: pre;
background-color: blue;
}
Javascript
/*jslint maxerr: 50, indent: 4, browser: true */
(function () {
"use strict";
var rxOpen = new RegExp("<[^\\/].+?>"),
rxClose = new RegExp("<\\/.+?>"),
rxWhitespace = new RegExp("^\\s+?"),
rxSupStart = new RegExp("^<sup\\b[^>]*>"),
rxSupEnd = new RegExp("<\/sup>"),
sentenceEnd = [],
color = 0,
rxIndex;
sentenceEnd.push(new RegExp("[^\\d][\\.!\\?]+"));
sentenceEnd.push(new RegExp("(?=([^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*?$)"));
sentenceEnd.push(new RegExp("(?![^\\(]*?\\))"));
sentenceEnd.push(new RegExp("(?![^\\[]*?\\])"));
sentenceEnd.push(new RegExp("(?![^\\{]*?\\})"));
sentenceEnd.push(new RegExp("(?![^\\|]*?\\|)"));
//sentenceEnd.push(new RegExp("(?![^\\\\]*?\\\\)"));
//sentenceEnd.push(new RegExp("(?![^\\/.]*\\/)")); // all could be a problem, but this one is problematic
rxIndex = new RegExp(sentenceEnd.reduce(function (previousValue, currentValue) {
return previousValue + currentValue.source;
}, ""));
function indexSentenceEnd(html) {
var index = html.search(rxIndex);
if (index !== -1) {
index += html.match(rxIndex)[0].length - 1;
}
return index;
}
function pushSpan(array, className, string, classNameOpt) {
if (className === "sentence") {
className += color % 2;
if (classNameOpt) {
className += " " + classNameOpt;
}
color += 1;
}
array.push('<span class="' + className + '">' + string + '</span>');
}
function addSupToPrevious(html, array) {
var sup = html.search(rxSupStart),
end = 0,
last;
if (sup !== -1) {
end = html.search(rxSupEnd);
if (end !== -1) {
last = array.pop();
end = end + 6;
array.push(last.slice(0, -7) + html.slice(0, end) + last.slice(-7));
}
}
return html.slice(end);
}
function leadingWhitespaces(html, array) {
var whitespace = html.search(rxWhitespace),
count = 0;
if (whitespace !== -1) {
count = html.match(rxWhitespace)[0].length;
pushSpan(array, "whitespace", html.slice(0, count));
}
return html.slice(count);
}
function paragraphIsSentence(html, array) {
var index = indexSentenceEnd(html);
if (index === -1 || index === html.length) {
pushSpan(array, "sentence", html, "paragraphIsSentence");
html = "";
}
return html;
}
function paragraphNoMarkup(html, array) {
var open = html.search(rxOpen),
index = 0;
if (open === -1) {
index = indexSentenceEnd(html);
if (index === -1) {
index = html.length;
}
pushSpan(array, "sentence", html.slice(0, index += 1), "paragraphNoMarkup");
}
return html.slice(index);
}
function sentenceUncontained(html, array) {
var open = html.search(rxOpen),
index = 0,
close;
if (open !== -1) {
index = indexSentenceEnd(html);
if (index === -1) {
index = html.length;
}
close = html.search(rxClose);
if (index < open || index > close) {
pushSpan(array, "sentence", html.slice(0, index += 1), "sentenceUncontained");
} else {
index = 0;
}
}
return html.slice(index);
}
function sentenceContained(html, array) {
var open = html.search(rxOpen),
index = 0,
close,
count;
if (open !== -1) {
index = indexSentenceEnd(html);
if (index === -1) {
index = html.length;
}
close = html.search(rxClose);
if (index > open && index < close) {
count = html.match(rxClose)[0].length;
pushSpan(array, "sentence", html.slice(0, close + count), "sentenceContained");
index = close + count;
} else {
index = 0;
}
}
return html.slice(index);
}
function anythingElse(html, array) {
pushSpan(array, "sentence2", html, "anythingElse");
return "";
}
function guessSenetences() {
var paragraphs = document.getElementsByTagName("p");
Array.prototype.forEach.call(paragraphs, function (paragraph) {
var html = paragraph.innerHTML,
length = html.length,
array = [],
safety = 100;
while (length && safety) {
html = addSupToPrevious(html, array);
if (html.length === length) {
html = leadingWhitespaces(html, array);
if (html.length === length) {
html = paragraphIsSentence(html, array);
if (html.length === length) {
html = paragraphNoMarkup(html, array);
if (html.length === length) {
html = sentenceUncontained(html, array);
if (html.length === length) {
html = sentenceContained(html, array);
if (html.length === length) {
html = anythingElse(html, array);
}
}
}
}
}
}
length = html.length;
safety -= 1;
}
paragraph.innerHTML = array.join("");
});
}
guessSenetences();
}());
On jsfiddle
you need to use .html() instead of .text() if you want to keep tags intact.
Check below code and let me know if it doesn't work out.
DEMO
$('p').each(function() {
var sentences = $(this)
.html()
.replace(/(((?![.!?]['"]?\s).)*[.!?]['"]?)(\s|$)/g,
'<span class="sentence">$1</span>$3');
$(this).html(sentences);
});

Categories