Skip to content

Instantly share code, notes, and snippets.

@gallagherrchris
Created October 16, 2019 03:53
Show Gist options
  • Save gallagherrchris/37eb423f6e40c2489affe2341e14fac5 to your computer and use it in GitHub Desktop.
Save gallagherrchris/37eb423f6e40c2489affe2341e14fac5 to your computer and use it in GitHub Desktop.
Implementation of the classic tents and trees game
<!doctype html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta content="utf-8" http-equiv="encoding" />
<title>Tents and Trees</title>
<style>
html,
body {
height: 100%;
}
.grid {
display: flex;
flex-direction: column;
}
.row {
display: flex;
flex-direction: row;
}
.count {
width: 30px;
height: 30px;
margin: 1px;
display: flex;
justify-content: center;
align-items: center;
}
.count:after {
content: attr(data-count);
}
.count[data-col="-1"] {
visibility: hidden;
}
.col {
width: 30px;
height: 30px;
border: 1px solid black;
display: flex;
justify-content: center;
align-items: center;
}
.empty:after {
content: " ";
}
.grass {
background-color: #b2ff7f;
}
.grass:after {
content: " ";
}
.tent {
background-color: #b2ff7f;
}
.tent:after {
content: "\25b2";
color: goldenrod;
}
.tree {
background-color: #b2ff7f;
}
.tree:after {
content: "\1f332";
}
.error {
background-color: red;
}
.row[data-row="0"] .col {
border-top-width: 2px;
}
.row[data-row="7"] .col {
border-bottom-width: 2px;
}
.col[data-col="0"] {
border-left-width: 2px;
}
.col[data-col="7"] {
border-right-width: 2px;
}
</style>
</head>
<body>
<section class="grid"></section>
</body>
<script defer>
let checkWin = tents => {
const placedTents = document.querySelectorAll(".grid .row .col.tent");
const wrongItems = [];
if (placedTents) {
const tentPlaces = [...placedTents].map(pt => ({
row: Number.parseInt(pt.getAttribute("data-row"), 10),
col: Number.parseInt(pt.getAttribute("data-col"), 10)
}));
const wrongTents = tentPlaces.filter(
({ row, col }) =>
!tents.find(
({ row: trow, col: tcol }) => trow === row && tcol === col
)
);
wrongItems.push(...wrongTents);
}
const wrongGrass = tents.filter(
({ row, col }) =>
!!document.querySelector(
`.grid .row[data-row="${row}"] .col[data-col="${col}"].grass`
)
);
wrongItems.push(...wrongGrass);
for (let { row, col } of wrongItems) {
const cell = document.querySelector(
`.grid .row[data-row="${row}"] .col[data-col="${col}"]`
);
cell.classList.add("error");
}
};
const handleClick = e => {
const target = e.target;
const row = target.getAttribute("data-row");
const col = target.getAttribute("data-col");
if (row === null || col === null) {
// Cell was not clicked
return;
}
let mapping = [
{ cur: "empty", next: "grass" },
{ cur: "grass", next: "tent" },
{ cur: "tent", next: "empty" }
];
if (e.which === 3) {
// Right click
mapping = mapping.map(({ cur, next }) => ({ cur: next, next: cur }));
}
const classList = target.classList;
classList.remove("error");
for (let { cur, next } of mapping) {
if (classList.contains(cur)) {
classList.replace(cur, next);
break;
}
}
checkWin();
e.preventDefault();
return false;
};
const generateTrees = (size, tents) => {
const trees = [];
for (let tent of tents) {
const dir = Math.floor(Math.random() * Math.floor(3));
const options = [
{
row: tent.row - 1,
col: tent.col
},
{
row: tent.row,
col: tent.col + 1
},
{
row: tent.row + 1,
col: tent.col
},
{
row: tent.row,
col: tent.col - 1
}
].filter(
opt =>
!(opt.row >= size || opt.col >= size) &&
!(opt.row < 0 || opt.col < 0) &&
!trees.find(
({ row: trow, col: tcol }) => trow === opt.row && tcol === opt.col
) &&
!tents.find(
({ row: trow, col: tcol }) => trow === opt.row && tcol === opt.col
)
);
if (options.length < 1) {
console.error("Could not place tree", tents, trees, tent);
return null;
}
const choice =
options[Math.floor(Math.random() * Math.floor(options.length))];
trees.push(choice);
}
return trees;
};
const generateTents = (size, numTents) => {
let numTries = 0;
const tents = [];
for (let i = 0; i < numTents && numTries < 1000; i++) {
numTries++;
const row = Math.floor(Math.random() * Math.floor(size));
const col = Math.floor(Math.random() * Math.floor(size));
const exists = tents.find(
t => Math.abs(t.row - row) <= 1 && Math.abs(t.col - col) <= 1
);
if (!exists) {
tents.push({ row, col });
} else {
i--;
}
}
return tents;
};
const countTents = prop => (tents, val) =>
tents.filter(tent => tent[prop] === val).length;
const countTentsByRow = countTents("row");
const countTentsByCol = countTents("col");
(function() {
const size = 8;
const numTrees = 12;
const tents = generateTents(size, numTrees);
const grid = document.querySelector(".grid");
grid.addEventListener("contextmenu", handleClick);
grid.addEventListener("click", handleClick);
const countDiv = document.createElement("div");
countDiv.classList.add("row");
grid.appendChild(countDiv);
for (let i = -1; i < size; i++) {
const countCell = document.createElement("div");
countCell.setAttribute("data-col", i);
countCell.setAttribute("data-count", countTentsByCol(tents, i));
countCell.classList.add("count");
countDiv.appendChild(countCell);
}
for (let row = 0; row < size; row++) {
const rowDiv = document.createElement("div");
rowDiv.classList.add("row");
rowDiv.setAttribute("data-row", row);
grid.appendChild(rowDiv);
const rowCount = document.createElement("div");
rowCount.setAttribute("data-row", row);
rowCount.setAttribute("data-count", countTentsByRow(tents, row));
rowCount.classList.add("count");
rowDiv.appendChild(rowCount);
for (let col = 0; col < size; col++) {
const colDiv = document.createElement("div");
colDiv.classList.add("col");
colDiv.classList.add("empty");
colDiv.setAttribute("data-row", row);
colDiv.setAttribute("data-col", col);
rowDiv.appendChild(colDiv);
}
}
const trees = generateTrees(size, tents);
for (let tree of trees) {
const cell = document.querySelector(
`.grid .row[data-row="${tree.row}"] .col[data-col="${tree.col}"]`
);
cell.classList.remove("empty");
cell.classList.add("tree");
}
checkWin = checkWin.bind(null, tents);
})();
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment